我在GPU(OpenCL)的帮助下使用arrayfire来加速一些C++代码。我有 600MB 的 af::array 以及更多,我需要沿着列维度翻转,然后转置它。
到目前为止,我几乎按照C++例行程序完成了这些操作。但是,我现在想使用 AF 执行此操作,但注意到 AF 库的内存使用过多。我对此有两个问题:
1)我完全不明白为什么300MB数组上的任何操作(例如flip或T)应该使用超过900MB的内存。 2)我想知道如何避免创建数组foo的副本。我认为通过将操作封装在一个单独的函数中,我将摆脱任何副本。
我有这样的代码:
void prepare_array(af::array &a) {
af::array b = af::flip(a, 1); // ~1400MB
a = b.T(); // ~3000MB
}
af::array foo = af::randn(768,16384,3,1,c64); // ~300MB
prepare_array(foo);
af::deviceGC(); // ~600MB
我只需要执行此操作一次,因此速度不如内存使用重要,但我更愿意在AF框架内执行此操作。
(所有内存使用统计数据都是使用 gpustat 从 debian 上的 NVIDIA 内核驱动程序包中读出的。
CPU 后端的内存使用量也同样过多。
感谢 umar-arshad 的回复:当我上次在 CPU 上运行代码时,我分析了内存使用情况 - 假设它的行为相同。我在GPU上仔细检查了测量结果,并使用了gpustat和nvidia-smi。事实上,代码的测量是不同的,正如你所解释的。现在一切都非常有意义 - 至少是 GPU 部分。
也许在 CPU 上 foo 起初只有 f64,因为只使用真实部分,并且通过使用翻转或换位成为 c64。
"分配触发隐式设备在某些平台上的所有队列上同步"这一事实与本网站一起:http://forums.accelereyes.com/forums/viewtopic.php?f=17&t=43097&p=61730&hilit=copy+host+memory+into+an+array#p61727 和 af::p rintMemInfo(); 帮助我最终弄清楚了AF的大部分内存处理。大大加快了我的程序。
但是,目前仍然使用以下方法(或尽可能少的开销)就地执行这两个操作的唯一替代方法:
// Generate/store data in std::array<af::cdouble> foo_unwrap = new af::cdouble[768*16384*3*1];
// Flip/Transpose foo_unwrap in plain C/C++, like in:
// for(column = 0; column < max_num_column/2; column++)
// swap column with max_num_column-1-column
//
// http://www.geeksforgeeks.org/inplace-m-x-n-size-matrix-transpose/
// but for Column-Major Order matrices
//
// and afterwards whenever needed do ad-hoc:
af::cdouble* first_elem = (af::cdouble*) &(foo_unwrap[0]); // to ensure correct type detection via AF
af::array foo = af::array(768,16384,3,1, first_elem, afDevice);
然而,这很麻烦,因为我不想为行/列-主要格式和索引魔术而烦恼。所以我仍然在这里寻找建议。
ArrayFire 使用内存管理器来避免不必要的分配和解除分配。这是必需的,因为所有分配都会在某些平台上的所有队列上触发隐式设备同步。这可能会变得非常昂贵,因此ArrayFire将跟踪越界af::array
并在必要时重复使用它们。ArrayFire 还会在启动时分配内存。
在您的情况下,您在 randu 调用处分配 ~600MB(c64
是一个复数双精度,因此每个元素为 16 字节)。翻转操作还有另一个 600MB 的分配,存储在b
中。转置将分配 600MB,但将保留较旧的值以供重复使用。此时,由于这些操作,您分配了大约 1800 MB 的内存。
当您从prepared_array
函数返回时,调用 b 将超出范围,并将被标记为删除。此时,您有 3 个缓冲区,每个缓冲区 600MB。其中两个缓冲区未使用,但 ArrayFire 可以在将来的操作中使用这些缓冲区。调用deviceGC
函数后,两个未使用的数组都将被释放,尽管您可能会分配类似大小的数组,因此保留这些数组很有用。
您可以使用af::printMemInfo()
函数跟踪 ArrayFire 所做的分配。
声明者:我是ArrayFire的开发者之一。