c++ - 如何在 ArrayFire 中正确使用固定内存?
问题描述
在 ArrayFire 中使用固定内存时,性能会降低。
我尝试了各种创建固定内存并从中创建数组的方法,例如。cudaMallocHost。使用带有 cudaMemcpy 的 cudaMallocHost 方式非常快(几百微秒),但随后创建/初始化 arrayfire 数组非常慢(~ 2-3 秒)。最后我想出了以下方法,分配大约需要 2-3 秒,但可以移动到其他地方。使用主机数据初始化阵列是令人满意的(100 - 200 微秒),但现在操作(在这种情况下为 FFT)非常慢:~ 400 毫秒。我应该添加输入信号的大小是可变的,但是对于时间我使用了 64K 样本(复数)。另外,为了简洁,我没有提供我的计时功能,但这不是问题,我已经使用其他方法计时并且结果是一致的。
// Use the Frequency-Smoothing method to calculate the full
// Spectral Correlation Density
// currently the whole function takes ~ 2555 msec. w/ signal 64K samples
// and window_length = 400 (currently not implemented)
void exhaustive_fsm(std::vector<std::complex<double>> signal, uint16_t window_length) {
// Allocate pinned memory (eventually move outside function)
// 2192 ms.
af::af_cdouble* device_ptr = af::pinned<af::af_cdouble>(signal.size());
// Init arrayfire array (eventually move outside function)
// 188 us.
af::array s(signal.size(), device_ptr, afDevice);
// Copy to device
// 289 us.
s.write((af::af_cdouble*) signal.data(), signal.size() * sizeof(std::complex<double>), afHost);
// FFT
// 351 ms. equivalent to:
// af::array fft = af::fft(s, signal.size());
af::array fft = zrp::timeit(&af::fft, s, signal.size());
fft.eval();
// Convolution
// Copy result to host
// free memory (eventually move outside function)
// 0 ms.
af::freePinned((void*) s.device<af::af_cdouble>());
// Return result
}
正如我上面所说,FFT 需要大约 400 毫秒。这个使用犰狳的功能需要大约 110 毫秒。包括卷积,使用 FFTW 的 FFT 大约需要 5 毫秒。同样在我的机器上使用 ArrayFire FFT 示例我得到以下结果(修改为使用 c64)
A = randu(1, N, c64);)
基准 1×N CX fft
1 x 128: time: 29 us.
1 x 256: time: 31 us.
1 x 512: time: 33 us.
1 x 1024: time: 41 us.
1 x 2048: time: 53 us.
1 x 4096: time: 75 us.
1 x 8192: time: 109 us.
1 x 16384: time: 179 us.
1 x 32768: time: 328 us.
1 x 65536: time: 626 us.
1 x 131072: time: 1227 us.
1 x 262144: time: 2423 us.
1 x 524288: time: 4813 us.
1 x 1048576: time: 9590 us.
所以我能看到的唯一区别是使用固定内存。知道我哪里出错了吗?谢谢。
编辑
我注意到在运行 AF FFT 示例时,在第一次打印出来之前有一个明显的延迟(即使时间不包括这个延迟)。所以我决定创建一个类并将所有的分配/解除分配到 ctor/dtor 中。出于好奇,我还在 ctor 中放了一个 FFT,因为我还注意到,如果我运行第二个 FFT,大约需要 600 微秒。与我的基准一致。果然,运行“初步” FFT 似乎“初始化”了某些东西,随后的 FFT 运行得更快。必须有更好的方法,我一定错过了一些东西。
解决方案
我是 pradeep,也是 ArrayFire 的开发者之一。
首先,所有 ArrayFire 函数(CUDA 和 OpenCL)后端都有一些启动成本,其中包括设备预热和/或内核缓存(内核在第一次调用特定函数时被缓存)。这就是原因,您注意到第一次运行后运行时间更好。这也是原因,我们几乎总是强烈建议使用我们内置的 timeit函数来计时 arrayfire 代码,因为它在一组运行中平均,而不是使用第一次运行。
正如您已经从实验中推测的那样,以受控方式保持固定内存分配总是更好。如果您还没有意识到使用固定内存时所涉及的权衡取舍,您可以从 NVIDIA 的这篇博客文章开始(它同样适用于 OpenCL 后端的固定内存,当然有任何供应商特定的限制)。超链接帖子中建议的一般准则如下:
您不应该过度分配固定内存。这样做会降低整体系统性能,因为它会减少操作系统和其他程序可用的物理内存量。很难提前知道多少是太多,因此与所有优化一样,测试您的应用程序和它们运行的系统以获得最佳性能参数。
如果可能,以下是我为您的 FFT 使用固定内存的路线
- 将固定分配/释放封装成 RAII 格式,您现在已经从您编辑的描述中执行此操作。
- 如果可能,只进行一次固定内存分配 - 如果您的数据大小是静态的。
除此之外,我认为您的功能在几个方面是不正确的。我将按行顺序介绍该功能。
af::af_cdouble* device_ptr = af::pinned(signal.size());
此调用不会在设备/GPU 上分配内存。它是主机上的页面锁定内存,RAM。
af::array s(signal.size(), device_ptr, afDevice);
因为,af::pinned 不分配设备内存,所以它不是设备指针,枚举是 afHost。所以,电话将是af::array s(signal.size(), ptr);
您s.write
自己使用正确,但我相信您的用例不需要它。
以下是我会做的。
- 对返回的指针使用 RAII 构造,
af::pinned
并且只分配一次。确保您没有太多这些页面锁定的分配。 - 使用页面锁定分配作为常规主机分配,而不是
std::vector<complex>
因为这是主机内存,只是页面锁定。std::vector
如果您以某种方式进行操作,这将涉及在主机端编写一些额外的代码。否则,您可以只使用 RAIIed-pinned-pointer 来存储您的数据。 - 所有,您需要将您的 fft 数据传输到设备是
af::array s(size, ptr)
此时,您必须花时间进行的操作是从固定内存转移到 GPU,这是上面列表中的最后一个调用;fft 执行;复制回主机。
推荐阅读
- cookies - 在 cookie 中处理 > 4K 数据
- android-studio - Android Studio 进度对话框未显示进度?
- javascript - 从 puppeteer .innertext js 获取数据时遇到问题
- vue.js - Vue.js 超链接基于道具数据而改变
- matlab - 如何在matlab中居中对齐合适的列?
- java - 如何解决 JTree 中的显示问题?
- python - 将数据帧中的 600M 转换为 600,000,000 和 7K 转换为 7000
- c# - 使用瀑布方法 C# Bot 框架 V4 时从机器人提示符获取值
- telerik - RadGrid - 单击时不触发批量编辑模式
- c++ - 传递参数的问题