如何解决Haskell 中可变的非整数类型的可变列表
我正在尝试从二进制解析一个巨大的 3d 数据数组。稍后这应该变成 l
矩阵 (n x m
)。因为我要处理这些矩阵,所以我仅限于矩阵库 - hmatrix 似乎很有前途。
数据布局不是我所要求的格式,因此我必须在位置 (i,j,k) -> (k,i,j)
处跳转,其中 i
和 j
是 n
和 m
的元素和 k
的 l
元素。
我认为阅读此内容的唯一方法是使用可变变量,否则我最终会得到几个 Terrabytes 的垃圾。我的想法是使用盒装互矩阵或互矩阵向量(来自 Numeric.Linearalgebra.Devel 的 STMatrix),所以我最终得到了类似的结果:
data MVector s (STMatrix s t)
但我不确定如何正确使用它们: 我可以使用 modify 修改 MVector 的一个元素:
modify :: PrimMonad m => MVector (Primstate m) a -> (a -> a) -> Int -> m ()
或者使用 modifyM(奇怪:在堆栈中 vector-0.12.3.0 没有 modifyM...)
modifyM :: PrimMonad m => MVector (Primstate m) a -> (a -> m a) -> Int -> m ()
所以我可以使用函数调用 (a -> a)
到 runST 例程来修改 SMatrix。我不确定,是否应该将 ST 放入 IO (?)
尽管如此 - 我认为,这应该有效,但只有在我想修改整个矩阵时才有用,调用这个 (a->a)
-routine n x m x l
- 时间会有点开销(也许它将被优化出来......)。
所以我最终会在编组 Array 时通过指针 (i,j)
修改内容并通过 Matrix 读取所有 Matrix - 但这感觉不对,我想避免这种肮脏的技巧。
你有什么想法可以做到这一点,但更......干净吗? 类型
编辑: 感谢 K. A. Buhr。他的解决方案到目前为止有效。现在,我只遇到了一些性能影响。如果我比较解决方案:
{-# LANGUAGE BangPatterns #-}
module Main where
import Data.List
import Numeric.Linearalgebra
import qualified Data.Vector as V
import qualified Data.Vector.Storable as VS
import qualified Data.Vector.Storable.Mutable as VSM
-- Create an l-length list of n x m hmatrix Matrices
toMatrices :: Int -> Int -> Int -> [C] -> [Matrix C]
toMatrices l n m dats = map (reshape m) $ VS.createT $ do
mats <- V.replicateM l $ VSM.unsafeNew (m*n)
sequence_ $ zipwith (\(i,k) x ->
VSM.unsafeWrite (mats V.! k) (loc i j) x) idxs (dats ++ repeat 0)
return $ V.toList mats
where idxs = (,) <$> [0..n-1] <*> [0..m-1] <*> [0..l-1]
loc i j = i*m + j
test1 = toMatrices 1000 1000 100 (fromIntegral <$> [1..])
main = do
let !a = test1
print "done"
使用最简单的 C 代码:
#include <stdlib.h>
#include <stdio.h>
void main()
{
const int n = 1000;
const int m = 1000;
const int l = 100;
double *src = malloc(n*m*l * sizeof(double));
for (int i = 0; i < n*m*l; i++) {
src[i] = (double)i;
}
double *dest = malloc(n*m*l * sizeof(double));
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
for (int k = 0; k < l; k++) {
dest[k*n*m+i*m+j] = src[i*m*l+j*l+k];
}
}
}
printf("done: %f\n",dest[n*m*l - 1]); // Need to access the array,otherwise it'll get lost by -O2
free(src);
free(dest);
}
两者都用 -O2 编译给出了以下性能猜测:
real 0m5,611s
user 0m14,845s
sys 0m2,759s
对比
real 0m0,441s
user 0m0,200s
sys 0m0,240s
VSM.unsafeWrite (mats V.! k) (loc i j) x
是昂贵的函数。 由于我将在类似分钟的时间间隔内使用此过程,因此我希望将解析时间保持为与磁盘访问时间一样低。我会看看,如果我能加快速度
PS:这是为了一些测试,如果我可以将常用的 DSP 从 C 类移动到 Haskell
编辑 2 : 好的,这是我尝试求和后得到的:
{-# LANGUAGE BangPatterns #-}
module Main where
import Data.List
import qualified Data.Vector as V
import qualified Data.Vector.Storable as VS
import qualified Data.Vector.Storable.Mutable as VSM
import Numeric.Linearalgebra
-- Create an l-length list of n x m hmatrix Matrices
toMatrices :: Int -> Int -> Int -> VS.Vector C -> V.Vector (Matrix C)
toMatrices l n m dats =
V.map (reshape m) newMat
where
newMat = VS.createT $
V.generateM l $ \k -> do
curMat <- VSM.unsafeNew (m * n)
VS.mapM_
(\i ->
VS.mapM_
(\j -> VSM.unsafeWrite curMat (loc i j) (dats VS.! (oldLoc i j k)))
idjs)
idis
return curMat
loc i j = i * m + j
oldLoc i j k = i * m * l + j * l + k
!idis = VS.generate n (\a->a)
!idjs = VS.generate m (\a->a)
test1 = toMatrices 100 1000 1000 arr
where
arr = VS.generate (1000 * 1000 * 100) fromIntegral :: VS.Vector C
main = do
let !a = test1
print "done"
它给出了一些关于:
real 0m1,816s
user 0m1,636s
sys 0m1,120s
,因此比 C 代码慢约 4 倍。我想我可以忍受这个。 我想,我正在用这段代码破坏向量的所有流功能。如果有任何建议以可比的速度恢复它们,我将不胜感激!
解决方法
据我所知,您有一组 i
-major、j
-middling、k
-minor 顺序的“巨大”数据,并且您想将其加载到由 k
索引的矩阵,其元素具有 i
索引的行和 j
索引的列,对吗?所以,你想要一个类似的函数:
import Numeric.LinearAlgebra
-- load into "l" matrices of size "n x m"
toMatrices :: Int -> Int -> Int -> [C] -> [Matrix C]
toMatrices l n m dats = ...
请注意,您已经在上面编写了 n x m
矩阵,将 i
与 n
相关联,将 j
与 m
相关联。翻转 n
和 m
的角色会更常见,但我一直坚持你的符号,所以请注意这一点。
如果整个数据列表 [C]
可以轻松地放在内存中,您可以通过编写以下内容来不变地做到这一点:
import Data.List
import Data.List.Split
import Numeric.LinearAlgebra
toMatrices :: Int -> Int -> Int -> [C] -> [Matrix C]
toMatrices l n m = map (reshape m . fromList) . transpose . chunksOf l
这将输入数据分解为 l
大小的块,将它们转置为 l
列表,并将每个列表转换为矩阵。如果有某种方法可以并行强制所有 Matrix C
值,则可以通过一次遍历数据来完成,而无需保留整个列表。不幸的是,单个 Matrix C
值只能一个一个地强制,整个列表需要保留,直到所有这些值都可以被强制。
因此,如果“巨大的”[C]
列表对于内存来说太大了,那么您可能需要将数据加载到(部分)可变结构中是正确的。代码编写起来有点困难,但它的最终形式还不错。我相信以下方法会起作用:
import Data.List
import Numeric.LinearAlgebra
import qualified Data.Vector as V
import qualified Data.Vector.Storable as VS
import qualified Data.Vector.Storable.Mutable as VSM
-- Create an l-length list of n x m hmatrix Matrices
toMatrices :: Int -> Int -> Int -> [C] -> [Matrix C]
toMatrices l n m dats = map (reshape m) $ VS.createT $ do
mats <- V.replicateM l $ VSM.unsafeNew (m*n)
sequence_ $ zipWith (\(i,j,k) x ->
VSM.unsafeWrite (mats V.! k) (loc i j) x) idxs (dats ++ repeat 0)
return $ V.toList mats
where idxs = (,) <$> [0..n-1] <*> [0..m-1] <*> [0..l-1]
loc i j = i*m + j
test1 = toMatrices 4 3 2 (fromIntegral <$> [1..24])
test2 = toMatrices 1000 1000 100 (fromIntegral <$> [1..])
main = do
print $ test1
print $ norm_Inf . foldl1' (+) $ test2
使用 -O2
编译,最大驻留时间约为 1.6Gigs,这与在内存中保存 100 个 16 字节复数值的 100 个矩阵所需的内存相匹配,因此看起来正确。
无论如何,由于使用了三种不同的向量变体,这个版本的 toMatrices
变得有些复杂。 Vector
中有 hmatrix
,与 VS.Vector
中的不可变可存储 vector
相同;然后还有来自 vector
的另外两种类型:不可变的盒装 V.Vector
和可变的可存储 VSM.Vector
。
do
-block 创建一个 V.Vector
的 VSM.Vector
并使用跨索引/值对执行的一系列 monadic 操作填充它们。您可以通过修改 idxs
的定义以匹配数据流的顺序以任何顺序加载数据。 do
块返回列表中最后的 VSM.Vector
,辅助函数 VS.createT
将它们全部冻结到 VS.Vector
(即 Vector
从 {{ 1}}),hmatrix
被映射到向量上,将它们转换为 reshape
列矩阵。
请注意,在实际应用程序中,您必须注意,从文件中读取的数据项列表不会由 m
以外的代码保留,无论是原始文本形式还是解析的数字形式。这应该不太难做到,但您可能希望在将计算机锁定在真实数据集上之前对中等规模的测试输入进行测试。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。