比较两种复制技术的性能?
Comparing performance of two copying techniques?
为了将一个巨大的双精度数组复制到另一个数组,我有以下两种选择:
选项 1
copy(arr1, arr1+N, arr2);
选项 2
#pragma omp parallel for
for(int i = 0; i < N; i++)
arr2[i] = arr1[i];
我想知道对于较大的 N 值。以下哪项是更好的(花费更少的时间)选项以及何时?
系统配置:
内存:15.6 GiB
处理器:Intel® Core™ i5-4590 CPU @ 3.30GHz × 4
OS-类型:64 位
编译器:gcc (Ubuntu 4.9.2-0ubuntu1~12.04) 4.9.2
选项 1 更好。
RAM 是共享资源,不能简单地并行化。当一个核心使用 RAM 时,其他核心等待。
此外,RAM 通常比 CPU 慢 -- RAM 频率低于 CPU 频率,因此在上述情况下,即使单核也有只在 RAM 上等待的周期。
您也可以考虑 memcpy()
进行复制,它可能比 std::copy()
更快。它通常取决于实现。
最后但并非最重要的是,始终测量。首先,只需在您要测量的代码段前后放置 omp_get_wtime()
并查看差异。
实际,如果性能很重要,请衡量它。
std::copy
和memcpy
通常是高度优化的,using sophisticated performance tricks。您的编译器可能足够聪明/没有正确的配置选项来从原始循环实现该性能。
也就是说,理论上,并行化副本可以带来好处。在现代系统上,您必须使用多线程来充分利用内存和缓存带宽。看一下 these benchmark results,其中前两行比较并行与单线程缓存,最后两行比较并行与单线程主内存带宽。在像你这样的桌面系统上,差距不是很大。在面向高性能的系统中,尤其是具有多个套接字的系统中,更多线程对于利用可用带宽非常重要。
对于最佳解决方案,您必须考虑诸如不从多个线程写入相同的缓存行之类的事情。此外,如果您的编译器不能从原始循环中生成完美的代码,您可能必须在多个 threads/chunks 上实际 运行 std::copy
。在我的测试中,原始循环的性能要差得多,因为它不使用 AVX。只有英特尔编译器设法用 avx_rep_memcpy
实际替换 OpenMP 循环中的部分——有趣的是,它没有对非 OpenMP 循环执行此优化。内存带宽的最佳线程数通常也不是核心数,而是更少。
一般建议是:从一个简单的实现开始,在本例中是惯用的 std::copy
,然后分析您的应用程序以了解瓶颈的实际位置。不要投资于复杂的、难以维护的、系统特定的优化,这些优化可能只会影响整个代码的一小部分运行时间。如果事实证明这是您的应用程序的瓶颈,并且您的硬件资源没有得到很好的利用,那么您需要了解底层硬件的性能特征(local/shared 缓存、NUMA、预取器)并相应地调整您的代码.
为了将一个巨大的双精度数组复制到另一个数组,我有以下两种选择:
选项 1
copy(arr1, arr1+N, arr2);
选项 2
#pragma omp parallel for
for(int i = 0; i < N; i++)
arr2[i] = arr1[i];
我想知道对于较大的 N 值。以下哪项是更好的(花费更少的时间)选项以及何时?
系统配置:
内存:15.6 GiB
处理器:Intel® Core™ i5-4590 CPU @ 3.30GHz × 4
OS-类型:64 位
编译器:gcc (Ubuntu 4.9.2-0ubuntu1~12.04) 4.9.2
选项 1 更好。
RAM 是共享资源,不能简单地并行化。当一个核心使用 RAM 时,其他核心等待。
此外,RAM 通常比 CPU 慢 -- RAM 频率低于 CPU 频率,因此在上述情况下,即使单核也有只在 RAM 上等待的周期。
您也可以考虑 memcpy()
进行复制,它可能比 std::copy()
更快。它通常取决于实现。
最后但并非最重要的是,始终测量。首先,只需在您要测量的代码段前后放置 omp_get_wtime()
并查看差异。
实际,如果性能很重要,请衡量它。
std::copy
和memcpy
通常是高度优化的,using sophisticated performance tricks。您的编译器可能足够聪明/没有正确的配置选项来从原始循环实现该性能。
也就是说,理论上,并行化副本可以带来好处。在现代系统上,您必须使用多线程来充分利用内存和缓存带宽。看一下 these benchmark results,其中前两行比较并行与单线程缓存,最后两行比较并行与单线程主内存带宽。在像你这样的桌面系统上,差距不是很大。在面向高性能的系统中,尤其是具有多个套接字的系统中,更多线程对于利用可用带宽非常重要。
对于最佳解决方案,您必须考虑诸如不从多个线程写入相同的缓存行之类的事情。此外,如果您的编译器不能从原始循环中生成完美的代码,您可能必须在多个 threads/chunks 上实际 运行 std::copy
。在我的测试中,原始循环的性能要差得多,因为它不使用 AVX。只有英特尔编译器设法用 avx_rep_memcpy
实际替换 OpenMP 循环中的部分——有趣的是,它没有对非 OpenMP 循环执行此优化。内存带宽的最佳线程数通常也不是核心数,而是更少。
一般建议是:从一个简单的实现开始,在本例中是惯用的 std::copy
,然后分析您的应用程序以了解瓶颈的实际位置。不要投资于复杂的、难以维护的、系统特定的优化,这些优化可能只会影响整个代码的一小部分运行时间。如果事实证明这是您的应用程序的瓶颈,并且您的硬件资源没有得到很好的利用,那么您需要了解底层硬件的性能特征(local/shared 缓存、NUMA、预取器)并相应地调整您的代码.