我正在测试Haskell的数组库(array和vector包)以找到存储大量数据的最佳方法。我使用criterion作为基准测试工具。
简而言之,我的代码只是分配了一个向量,然后用简单的结构体填充它(分别为100万、1000万和1亿个元素)。当我将Haskell的基准测试时间与我用C编写的简单参考实现进行比较时,Haskell要快几倍,我觉得这很可疑:C代码是一个简单的循环,用结构体填充数组。
问题是:Haskell的vector库是否可能在性能方面击败C?还是说我的基准测试有缺陷/某些内容未被评估/存在一些“陷阱”?
另一个问题是如何确保Haskell向量实际上已被评估?
更详细的说明:任务是用大量结构体填充向量。它们具有Storable实例,使用的向量是Data.Vector.Storable。
该 Gist 包含其他基准测试,尝试以不同的方式强制评估。
可以在这里(Gist)找到执行更多或更少相同操作的参考C代码。主要逻辑如下:
简而言之,我的代码只是分配了一个向量,然后用简单的结构体填充它(分别为100万、1000万和1亿个元素)。当我将Haskell的基准测试时间与我用C编写的简单参考实现进行比较时,Haskell要快几倍,我觉得这很可疑:C代码是一个简单的循环,用结构体填充数组。
问题是:Haskell的vector库是否可能在性能方面击败C?还是说我的基准测试有缺陷/某些内容未被评估/存在一些“陷阱”?
另一个问题是如何确保Haskell向量实际上已被评估?
更详细的说明:任务是用大量结构体填充向量。它们具有Storable实例,使用的向量是Data.Vector.Storable。
data Foo = Foo Int Int deriving (Show, Eq, Generic, NFData)
这些 Storable
实例的样子是这样的:
chunkSize :: Int
chunkSize = sizeOf (undefined :: Int)
{-# INLINE chunkSize #-}
instance Storable Foo where
sizeOf _ = 2 * chunkSize ; {-# INLINE sizeOf #-}
alignment _ = chunkSize ; {-# INLINE alignment #-}
peek ptr = Foo
<$> peekByteOff ptr 0
<*> peekByteOff ptr chunkSize
{-# INLINE peek #-}
poke ptr (Foo a b) = do
pokeByteOff ptr 0 a
pokeByteOff ptr chunkSize b
{-# INLINE poke #-}
序列化本身似乎正常工作。然后分配了向量:
mkFooVec :: Int -> IO (Vector Foo)
mkFooVec !i = unsafeFreeze =<< new (i + 1)
并填充结构体:
populateFooVec :: Int -> Vector Foo -> IO (Vector Foo)
populateFooVec !i !v = do
v' <- unsafeThaw v
let go 0 = return ()
go j = unsafeWrite v' j (Foo j $ j + 1) >> go (j - 1)
go i
unsafeFreeze v'
基准测试是衡量标准的一项:
defaultMain [
bgroup "Storable vector (mutable)"
$ (\(i :: Int) -> env (mkFooVec (10 ^ i))
$ \v -> bench ("10e" <> show i)
$ nfIO (populateFooVec (10 ^ i) v)) <$> [6..8]
]
该 Gist 包含其他基准测试,尝试以不同的方式强制评估。
可以在这里(Gist)找到执行更多或更少相同操作的参考C代码。主要逻辑如下:
Foo *allocFoos(long n) {
return (Foo *) malloc(n * sizeof(Foo));
}
// populate the array with structs:
void createFoos(Foo *v, long n) {
for (long i = 0; i < n; ++i) {
v[i].name = i;
v[i].id = i + 1;
}
}
运行该命令:gcc -O2 -o bench benchmark.c && ./bench
现在当我运行基准测试时,C代码大约需要50毫秒,而Criterion报告的结果约为800皮秒(!)。这让我想知道:也许我解释结果的方式是错误的?也许向量实际上没有被评估(尽管如果您查看Haskell Gist,我会尝试以不同的方式强制进行评估)。我做错了什么吗?如果没有错的话-如何让vector
在C中击败一个简单的for循环(GCC进一步展开)?
请原谅我这个非常冗长的问题,我想尽可能提供整个背景 ;)
forall
语义。 - Davislor-S
编译并检查汇编代码,但也许C版本会受益于一个#pragma omp
指令。 - Davislor