Luc*_*tti 6 python transpose numpy matrix rust
我有一个相当大的矩形(>1G 行,1K 列)Fortran 风格的 NumPy 矩阵,我想将其转置为 C 风格。
到目前为止,我的方法相对简单,使用以下 Rust 代码片段,使用源矩阵和目标矩阵的 MMAPed 切片,其中 和original_matrix都是target_matrixMMAPPed PyArray2,并由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;
});
});
Run Code Online (Sandbox Code Playgroud)
这种方法转置形状为 (~1G, 100) 的矩阵,~120GB 在 HDD 磁盘上需要~3 小时。转置 (~1G, 1000)、~1200GB 矩阵不会像人们天真地期望的那样线性扩展到 30 小时,而是会爆炸到几周。就目前情况而言,我已经在 2 天内成功地转置了大约 100 个功能,而且速度一直在减慢。
有几个方面,例如所使用的文件系统、HDD 碎片以及 MMAPed 如何处理页面加载,我的解决方案目前忽略了这些方面。
是否存在考虑到这些问题的已知的、更全面的解决方案?
虽然直观上,这种操作可能只受 IO 限制,因此不会从任何并行化中受益,但我们通过实验观察到,并行方法确实比顺序方法快三倍(在具有 12 个内核和 24 个线程的机器上)。转置形状为 (1G, 100) 的矩阵时的方法。我们不确定为什么会出现这种情况。
我们还尝试使用两种设备,一种提供 Fortran 样式矩阵,另一种提供目标矩阵。两个 HDD 均通过 SATA 电缆直接连接到计算机主板。我们预计性能至少会翻倍,但它们保持不变。
虽然直观上,这种操作可能只受 IO 限制,因此不会从任何并行化中受益,但我们通过实验观察到并行方法确实快了大约三倍
这可能是由于 IO 队列利用率不佳造成的。对于没有预取的完全顺序工作负载,您将在工作和空闲之间交替设备。如果您在飞行中进行多项操作,它将始终正常工作。
检查与iostat -x <interval>
但并行性并不是实现 HDD 最佳利用的次优方法,因为它可能会导致不必要的磁头搜索。
我们还尝试使用两种设备,一种提供 Fortran 样式矩阵,另一种提供目标矩阵。两个 HDD 均通过 SATA 电缆直接连接到计算机主板。我们预计性能至少会翻倍,但它们保持不变。
这可能是由于操作系统的写入缓存造成的,这意味着它可以非常有效地批量写入,而您主要在读取方面遇到瓶颈。再次检查iostat。
有几个方面,例如所使用的文件系统、HDD 碎片以及 MMAPed 如何处理页面加载,我的解决方案目前忽略了这些方面。是否存在考虑到这些问题的已知的、更全面的解决方案?
是的,如果底层文件系统支持,您可以使用FIEMAP获取磁盘上数据的物理布局,然后优化读取顺序以遵循物理布局而不是逻辑布局。您可以使用filefragCLI 工具手动检查碎片数据,但该 ioctl 有 rust 绑定,因此您也可以以编程方式使用它。
此外,您还可以用来madvise(MADV_WILLNEED)通知内核在后台预取数据以用于接下来的几次循环迭代。对于 HDD,理想情况下,这应该以一次价值几兆字节的批次完成。下一批应该在当前一批完成一半时发出。批量发出它们可以最大限度地减少系统调用开销,并在中途开始下一个,确保在到达当前 IO 结束之前有足够的时间来实际完成 IO。
由于您将按照物理顺序而不是逻辑顺序手动发出预取,因此您还可以通过以下方式禁用默认的预读启发式(这会妨碍)madvise(MADV_RANDOM)
如果您有足够的可用磁盘空间,您还可以尝试一种更简单的方法:在操作文件之前对文件进行碎片整理。但即便如此,您仍然应该使用 madvise 来确保始终有 IO 请求在进行中。
| 归档时间: |
|
| 查看次数: |
159 次 |
| 最近记录: |