【问题标题】:How to do fast data deserialization in Haskell如何在 Haskell 中进行快速数据反序列化
【发布时间】:2023-04-05 09:38:01
【问题描述】:

基准测试表明,cereal 库反序列化我的数据结构(下文详述)所需的时间比从驱动器读取相同数据所需的时间长 100 倍:

benchmarking Read
mean: 465.7050 us, lb 460.9873 us, ub 471.0938 us, ci 0.950
std dev: 25.79706 us, lb 22.19820 us, ub 30.81870 us, ci 0.950
found 4 outliers among 100 samples (4.0%)
  4 (4.0%) high mild
variance introduced by outliers: 53.460%
variance is severely inflated by outliers

benchmarking Read + Decode
collecting 100 samples, 1 iterations each, in estimated 6.356502 s
mean: 68.85135 ms, lb 67.65992 ms, ub 70.05832 ms, ci 0.950
std dev: 6.134430 ms, lb 5.607914 ms, ub 6.755639 ms, ci 0.950
variance introduced by outliers: 74.863%
variance is severely inflated by outliers

在我的一个程序中分析此数据结构的典型反序列化使用情况也支持这一点,其中 98% 的时间用于反序列化数据,1% 是 IO 加上核心算法:

COST CENTRE                    MODULE               %time %alloc

getWord8                       Data.Serialize.Get    30.5   40.4
unGet                          Data.Serialize.Get    29.5   17.9
getWord64be                    Data.Serialize.Get    14.0   10.7
getListOf                      Data.Serialize.Get    10.2   12.8
roll                           Data.Serialize         8.2   11.5
shiftl_w64                     Data.Serialize.Get     3.4    2.9
decode                         Data.Serialize         2.9    3.1
main                           Main                   1.3    0.6

我要反序列化的数据结构是IntMap [Triplet Atom],组件类型的定义如下:

type Triplet a = (a, a, a)

data Point = Point {
    _x :: {-# UNPACK #-} !Double ,
    _y :: {-# UNPACK #-} !Double ,
    _z :: {-# UNPACK #-} !Double }

data Atom = Atom {
    _serial :: {-# UNPACK #-} !Int    ,
    _r      :: {-# UNPACK #-} !Point  ,
    _n      :: {-# UNPACK #-} !Word64 }

我正在使用cereal 提供的默认IntMap(,,)[] 实例,以及我的自定义类型的以下类型和实例:

instance Serialize Point where
    put (Point x y z) = do
        put x
        put y
        put z
    get = Point <$> get <*> get <*> get

instance Serialize Atom where
    put (Atom s r n) = do
        put s
        put r
        put n
    get = Atom <$> get <*> get <*> get

所以我的问题是:

  1. 为什么反序列化一般这么慢?
  2. 有什么方法可以改变我的数据结构(即IntMap/[])以加快反序列化速度?
  3. 有什么方法可以更改我的数据类型(即Atom/Point)以加快反序列化速度?
  4. 在 Haskell 中是否有比 cereal 更快的替代方案,或者我应该将数据结构存储在 C 域中以进行更快速的反序列化(即使用 mmap)?

我正在反序列化的这些文件被用于搜索引擎的子索引,因为完整索引无法放入目标计算机(消费级桌面)的内存中,因此我将每个子索引存储在磁盘上并读取+解码驻留在内存中的初始全局索引指向的子索引。另外,我不关心序列化速度,因为搜索索引是最终用户的瓶颈,而cereal 当前的序列化性能对于生成和更新索引来说是令人满意的。

编辑:

尝试了 Don 提出的使用节省空间的三元组的建议,这使速度提高了四倍:

benchmarking Read
mean: 468.9671 us, lb 464.2564 us, ub 473.8867 us, ci 0.950
std dev: 24.67863 us, lb 21.71392 us, ub 28.39479 us, ci 0.950
found 2 outliers among 100 samples (2.0%)
  2 (2.0%) high mild
variance introduced by outliers: 50.474%
variance is severely inflated by outliers

benchmarking Read + Decode
mean: 15.04670 ms, lb 14.99097 ms, ub 15.10520 ms, ci 0.950
std dev: 292.7815 us, lb 278.8742 us, ub 308.1960 us, ci 0.950
variance introduced by outliers: 12.303%
variance is moderately inflated by outliers

但是,它仍然是使用 25 倍于 IO 的时间的瓶颈。另外,任何人都可以解释为什么唐的建议有效吗?这是否意味着如果我切换到列表以外的其他东西(比如数组?),它也可能会带来改进?

编辑 #2:刚刚切换到最新的 Haskell 平台并重新运行谷物分析。信息相当详细,我提供了一个hpaste

【问题讨论】:

  • 你可能会用更节省空间的类型替换Triplet,例如Triplet !Atom !Atom !Atom。这就是-O2 的全部内容(因为优化实例对性能非常敏感)?最后,Data.Binary 会得到什么结果——它可以更快,因为它会产生惰性输出。 IntMap 的实现也可能效率低下——替代序列化可能更快。
  • 是的,这就是-O2。我不认为懒惰会带来显着的优势,因为很多这些文件(不是我上面基准测试的文件,而是其他文件)都在 ByteString 的块大小范围内,并且它们仍然给出相同的比率 decodeIO 速度。不过,我会继续测试Binary,看看我得到了什么。
  • Data.Binary 可以更快的主要原因是它可以填充更小的字节串块,并且重新分配更少。也就是说,我会仔细研究谷物中 IntMap 的实例。
  • 好的,我刚刚切换到最新的 Haskell 平台,它提供了WAY better profiling information
  • 您可以查看 Vector 的可存储实例。它们通常比数组更有用;并有 mmap 支持。

标签: performance haskell serialization


【解决方案1】:

好的。用建议的摘要来回答这个问题。对于数据的快速反序列化:

  • 使用cereal(严格字节串输出)或binary(延迟字节串输出)
  • 确保使用 -O2 进行编译,因为这些库依靠内联来消除开销
  • 使用密集数据类型,例如将多态元组替换为未打包的专用形式。
  • 避免将数据类型转换为列表以对其进行序列化。如果你有字节串,这会被处理。对于未打包的数组类型,您通常会获得非常快的 IO,但值得仔细检查实例
  • 您也许可以使用 mmap'd IO
  • 对于双重数据,请考虑使用更高效的双读器。
  • 使用针对性能进行了优化的现代阵列和容器类型,以及更新的 GHC 版本。

【讨论】:

  • 非常感谢您的帮助。最后一件事。您能否修复vector-binary-instances,以免触发重叠实例。在我的项目中还有一个地方我决定使用非Storable 对象的Vector,而您的vector-binary-instances 无法使用,即使有扩展也是如此。我必须复制并粘贴源代码并将其专门用于 Vector 类型才能使其正常工作。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-05-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-18
相关资源
最近更新 更多