【发布时间】:2017-05-25 21:50:10
【问题描述】:
使用intrinsics 是SIMDizing 的常用方法。例如,我可以通过_mm256_add_epi32 对八个整数执行一条加法指令。添加后需要两个_mm256_load_si256和一个_mm256_store_si256,如下:
__m256i vec1 = _mm256_load_si256((__m256i *)&A[0]); // almost 5 cycles
__m256i vec2 = _mm256_load_si256((__m256i *)&B[0]); // almost 5 cycles
__m256i vec3 = _mm256_add_epi32( vec1 , vec2); // almost 1 cycle
_mm256_store_si256((__m256i *)&C[0], vec3); // almost 5
它在 CPU 的单核上执行指令。我的 Core i7 有 8 个核心(4 个实心);我想像这样将操作发送到所有核心:
int i_0, i_1, i_2, i_3, i_4, i_5, i_6, i_7 ; // These specify the values in memory
//core 0
__m256i vec1_0 = _mm256_load_si256((__m256i *)&A[i_0]);
__m256i vec2_0 = _mm256_load_si256((__m256i *)&B[i_0]);
__m256i vec3_0 = _mm256_add_epi32( vec1 , vec2);
_mm256_store_si256((__m256i *)&C[i_0], vec3_0);
//core 1
__m256i vec1_1 = _mm256_load_si256((__m256i *)&A[i_1]);
__m256i vec2_1 = _mm256_load_si256((__m256i *)&B[i_1]);
__m256i vec3_1 = _mm256_add_epi32( vec1 , vec2);
_mm256_store_si256((__m256i *)&C[i_1], vec3_1);
//core 2
__m256i vec1_2 = _mm256_load_si256((__m256i *)&A[i_2]);
__m256i vec2_2 = _mm256_load_si256((__m256i *)&B[i_2]);
__m256i vec3_2 = _mm256_add_epi32( vec1 , vec2);
_mm256_store_si256((__m256i *)&C[i_2], vec3_2);
//core 3
__m256i vec1_3 = _mm256_load_si256((__m256i *)&A[i_3]);
__m256i vec2_3 = _mm256_load_si256((__m256i *)&B[i_3]);
__m256i vec3_3 = _mm256_add_epi32( vec1 , vec2);
_mm256_store_si256((__m256i *)&C[i_3], vec3_3);
//core 4
__m256i vec1_4 = _mm256_load_si256((__m256i *)&A[i_4]);
__m256i vec2_4 = _mm256_load_si256((__m256i *)&B[i_4]);
__m256i vec3_4 = _mm256_add_epi32( vec1 , vec2);
_mm256_store_si256((__m256i *)&C[i_4], vec3_4);
//core 5
__m256i vec1_5 = _mm256_load_si256((__m256i *)&A[i_5]);
__m256i vec2_5 = _mm256_load_si256((__m256i *)&B[i_5]);
__m256i vec3_5 = _mm256_add_epi32( vec1 , vec2);
_mm256_store_si256((__m256i *)&C[i_5, vec3_5);
//core 6
__m256i vec1_6 = _mm256_load_si256((__m256i *)&A[i_6]);
__m256i vec2_6 = _mm256_load_si256((__m256i *)&B[i_6]);
__m256i vec3_6 = _mm256_add_epi32( vec1 , vec2);
_mm256_store_si256((__m256i *)&C[i_6], vec3_6);
//core 7
__m256i vec1_7 = _mm256_load_si256((__m256i *)&A[i_7]);
__m256i vec2_7 = _mm256_load_si256((__m256i *)&B[i_7]);
__m256i vec3_7 = _mm256_add_epi32( vec1 , vec2);
_mm256_store_si256((__m256i *)&C[i_7], vec3_7);
POSIX Thread 可用,openMP 在这种情况下也很有用。但是,与此操作的几乎5+5+1 cyles 相比,创建和维护线程花费了太多时间。因为,所有数据都是依赖的,所以我不需要查看共享内存。实现此操作的最快显式方法是什么?
因此,我从事 GPP 工作,GPU 可能不是答案。我还想实现一个库,因此编译器基础解决方案可能是一个挑战者。这个问题对于多线程来说已经足够大了。这是为了我的研究,因此我可以改变问题以适应这个概念。我想实现一个库并将其与 OpenMP 等其他解决方案进行比较,希望我的库将比其他当前解决方案更快。
GCC 6.3/clang 3.8, Linux Mint, Skylake
提前致谢。
【问题讨论】:
-
除非这是在一个深度循环的底部,在另一个循环被多次调用,并且你有一个显示它是瓶颈的配置文件,那么你可以选择的任何方法都将是最快的。在不知道是否确实存在问题的情况下编写最快的代码时请小心。如果这是需要更快的代码,那么继续进行实验,看看分析器说了什么。我什至不知道上面的代码会按原样使用多个 CPU - 相反,我相信它会将它们全部排在同一个核心上。
-
@MichaelDorgan,谢谢,我把问题改得更笼统了。它不是在一个循环中,但是,它可以是。我正在为我的应用程序实现一个多线程 SIMD 库,它是我的问题的简化版本。
-
一个问题是所有内核都在竞争相同的内存和 L3 和/或 L4 缓存。如果进程的内存带宽受限,只有 1 或 2 个内核,那么使用额外的内核将无济于事。
-
把你的问题分解成块。如果在将其拆分为多个内核时,您的内存访问模式是分散的,那么您从多线程中获得的好处很少。相反,如果您在内存中进行了严格的操作,那么将其划分到多个内核上并在较小的块上操作可能会有所帮助。您可能需要添加一些预取指令来帮助提前准备加载。
-
只有当问题足够大时,在多核上拆分计算才有意义。在你的情况下,它绝对没有。所以你需要使用操作系统提供的线程功能。如果您想减少线程创建开销,请考虑使用线程池。
标签: c multithreading x86 simd intrinsics