在磁盘上转换大型numpy矩阵



我有一个相当大的矩形(>1G行,1K列(Fortran风格的NumPy矩阵,我想将其转换为C风格。

到目前为止,我的方法对于下面的Rust片段来说相对微不足道,它使用源矩阵和目标矩阵的MMAPed切片,其中original_matrixtarget_matrix都是MMAPedPyArray2,Rayon处理并行化。

由于target_matrix必须由多个线程修改,所以我将其封装在UnsafeCell中。

let shared_target_matrix = std::cell::UnsafeCell::new(target_matrix);
original_matrix.as_ref().par_chunks(number_of_nodes).enumerate().for_each(|(j, feature)|{
feature.iter().copied().enumerate().for_each(|(i, feature_value)| unsafe {
*(shared_target_matrix.uget_mut([i, j])) = feature_value;
});
});

这种方法将形状为(~1G,100(、~120GB的矩阵在硬盘上转换大约需要3个小时。转换一个(~1G,1000(,~1200GB的矩阵并不像人们天真地预期的那样线性扩展到30小时,而是爆炸到几周。目前,我已经设法在2天内转换了大约100个功能,而且速度一直在放缓。

有几个方面,例如所使用的文件系统、HDD碎片以及MMAPed如何处理页面加载,而我的解决方案目前忽略了这些方面。

是否有已知的、更全面的解决方案考虑到了这些问题?

关于顺序和并行方法的说明

虽然直观地说,这种操作可能只受IO的限制,因此不会从任何并行化中受益,但我们已经通过实验观察到,当用形状(1G100(转换矩阵时,并行方法(在具有12个核和24个线程的机器上(确实比顺序方法快大约三倍。我们不确定为什么会出现这种情况。

关于使用两个HDD的注意事项

我们还尝试使用两个设备,一个提供Fortran风格的矩阵,另一个编写目标矩阵。两个硬盘都通过SATA电缆直接连接到电脑主板。我们预计业绩至少会翻一番,但没有变化。

虽然直观地说,这种操作可能只受到IO的限制,因此不会从任何并行化中受益,但我们通过实验观察到,并行方法确实比快三倍

这可能是由于IO队列利用率低所致。在没有预取的完全按顺序工作负载的情况下,您将在工作和空闲之间交替使用设备。如果你在飞行中进行多项操作,它将一直有效。

iostat -x <interval>检查

但并行性是实现HDD最佳利用率的次优方式,因为它可能会导致超出必要的磁头寻道。

我们还尝试使用两个设备,一个提供Fortran风格的矩阵,另一个用于编写目标矩阵。两个硬盘都通过SATA电缆直接连接到电脑主板。我们预计业绩至少会翻一番,但没有变化。

这可能是由于操作系统的写缓存,这意味着它可以非常有效地批量写入,而您在读取时大多会遇到瓶颈。再次使用iostat进行检查。

有几个方面,例如所使用的文件系统、HDD碎片以及MMAPed如何处理页面加载,而我的解决方案目前忽略了这些方面。是否有已知的、更全面的解决方案考虑到了这些问题?

是的,如果底层文件系统支持它,您可以使用FIEMAP获得磁盘上数据的物理布局,然后优化读取顺序,使其遵循物理布局而不是逻辑布局。您可以使用filefragCLI工具手动检查碎片数据,但该ioctl有rust绑定,因此您也可以通过编程方式使用它。

此外,您可以使用madvise(MADV_WILLNEED)通知内核在后台预取数据,以便进行接下来的几个循环迭代。对于HDD来说,最好是一次批量处理几兆字节。下一批应该在你完成当前一批的一半时发出。批量发布可以最大限度地减少系统调用开销,并且在中途启动下一个系统调用可以确保在到达当前IO结束之前有足够的时间来实际完成IO。

由于您将以物理顺序而非逻辑顺序手动发出预取,因此您也可以通过madvise(MADV_RANDOM)禁用默认的预读启发式(这会阻碍(

如果你有足够的可用磁盘空间,你也可以尝试一种更简单的方法:在对文件进行操作之前对其进行碎片整理。但即使这样,你仍然应该使用madvise来确保总是有IO请求在运行。

最新更新