目前,我在我的函数中发现了一些奇怪的行为。我有一个转换函数,它从(x,a,b) -> (x,y,z)
. 我正在使用Data.Vector.Storable因为我正在与一些外部库进行通信。我估计测试数据集的传递函数的时间不到十几秒。但是我的实现速度太慢了!这里,是某种:
import Data.Complex
import System.Random
import qualified Data.Vector as V
import qualified Data.Vector.Storable as VS
nx = 128
ny = 128
nz = 128
na = 128
nb = 16
transform :: VS.Vector (Complex Float) -> VS.Vector (Complex Float)->VS.Vector (Complex Float)->VS.Vector (Complex Float)
transform !inData !transfAArray !transfBArray= VS.concatMap
(\x ->
VS.concatMap
(\y ->
VS.map
(\z ->
calcSum y z x) $
VS.enumFromN 0 nz) $
VS.enumFromN 0 ny ) $
VS.enumFromN 0 nx
where
calcSum :: Int -> Int -> Int -> Complex Float
calcSum y z x =
VS.sum $
VS.map
(\b ->
(*) (transfB y b) $ dotProd (transfA z) (inDataSlice x b) ) $
-- (*) (transfB y b) $ dotProd (transfA z) (transfA z) ) $ Is fast
VS.enumFromN 0 nb
dotProd !a1 !a2 = VS.sum $ VS.zipWith (*) a1 a2
inDataSlice x b = VS.slice (x*na*nb + b*na) na inData
transfA z = VS.slice (z * na) na transfAArray
transfB y b = VS.unsafeIndex transfBArray (y * nb + b)
randomComplex :: IO (Complex Float)
randomComplex = (:+) <$> randomIO <*> randomIO
main :: IO ()
main = do
inData <- VS.generateM (nx * na * nb) (\i -> randomComplex)
transfAArray <- VS.generateM (nz * na) (\i -> randomComplex)
transfBArray <- VS.generateM (ny * nb) (\i -> randomComplex)
let !outData = transform inData transfAArray transfBArray
print $ VS.sum outData
这需要几分钟才能完成!如果我激活第 35 行,它的工作速度非常快 - 所以我猜肯定存在流融合的问题(分析显示了很多 >>= 在此过程中)。我在 c++ 中重新创建了代码并获得了大约 5s 的运行时(单线程):
#include <string>
#include <iterator>
#include <iostream>
#include <algorithm>
#include <vector>
#include <complex>
#include <cmath>
#include <numeric>
int main()
{
// transformation [nx*na*nb] -> [nx*ny*nz]
// [x][y][z], with dimensions 128 128 128
const unsigned int nx = 128;
const unsigned int ny = 128;
const unsigned int nz = 128;
std::vector<std::complex<float>> out(nx*ny*nz);
const unsigned int na = 128;
const unsigned int nb = 16;
std::vector<std::complex<float>> in(nx * na * nb);
std::iota (std::begin(in), std::end(in), 0);
std::vector<std::complex<float>> transA(na * nz);
std::iota (std::begin(transA), std::end(transA), 0);
std::vector<std::complex<float>> transB(nb * ny);
std::iota (std::begin(transB), std::end(transB), 0);
for (int x = 0; x < nx; x++) {
for (int y = 0; y < ny; y++) {
for (int z = 0; z < nz; z++) {
for (int b = 0; b < nb; b++) {
std::complex<float> sum = 0;
for (int a = 0; a < na; a++) {
sum += transA[z*na + a] * in[x*na*nb+b*na+a];
}
out[x*ny*nz + y*nz + z]= sum * transB[b * ny + y];
}
}
}
}
std::cout << out[12] << "done\n";
}
在我绝望的最后几天,我发现了以下奇怪的行为:如果我用 Data.Vector.Unboxed 替换 Data.Vector.Storable,它的运行速度与 c++ 代码一样快!
苏……为什么?
- 据我所知,未装箱和可存储都使用连续的内存区域 - 所以缓存/内存访问时间应该没有问题。
- 在 Storable-version 中分配了 TB 用于堆,而 Unboxed-version 只使用了几 MB。我感觉可存储版本不断在内存中移动块。
有任何想法吗?有没有一种简单的方法可以通过 Storage 实现类似的速度?(我正在使用 Stack 中的 ghc-8.10.4)
(PS:总是使用 -O2 编译,有时也使用 -fllvm -optlo-O2 :)