最近,在对我的项目运行基准测试后,我发现严格字节串的直接构造比构建器的构建速度快一个数量级。
,例如,编码器实现,它使用构建器:
encoder :: Int64 -> Data.ByteString.ByteString
encoder =
Data.ByteString.Lazy.toStrict .
Data.ByteString.Builder.toLazyByteString .
Data.ByteString.Builder.int64BE
执行的情况比直接构造字节串的情况差10倍,并且有进一步优化的几种可能性:
encoder :: Int64 -> Data.ByteString.ByteString
encoder =
unpackIntBySize 8
unpackIntBySize :: (Bits a, Integral a) => Int -> a -> Data.ByteString.ByteString
unpackIntBySize n x =
Data.ByteString.pack $ map f $ reverse [0..n - 1]
where
f s =
fromIntegral $ shiftR x (8 * s)
所以我的问题是双重的:
为什么没有从Builder
到严格ByteString
的直接转换?这很烦人,因为我经常需要导入Data.ByteString.Lazy
才能使用其toStrict
功能,因为Data.ByteString.Builder
只会公开toLazyByteString
。
然而,所提到的经历让我感到奇怪,如果它不是有原因的话。原因是我完全使用了不正确的使用模式。那么,它确实是不正确的,还有更好的选择吗?顺便说一句,我知道Data.ByteString.Builder.Prim
,但我怀疑在上述案例中使用它会产生很大的不同。
答案 0 :(得分:7)
Builder不是零成本抽象,它针对大型惰性字符串进行了优化。来自构建器docs:
当前的实现调整为4kb到32kb之间的平均块大小
在您的情况下,构建器仅分配整个4k块以产生8个字节。
与计算必要缓冲区大小的pack
进行比较,分配它然后在循环中填充它。低效率的唯一来源是预先分配的8 Word8
列表。可能unfoldrN
会更有效率。
使用构建器来构造小的严格字节串有时很方便,但有更好的方法。
答案 1 :(得分:3)
尝试使用toLazyByteStringWith
中的Data.ByteString.Builder.Extra
来调整您的ByteString
构造。这需要AllocationStrategy
,可以调整缓冲区大小和增长率。 p>