矢量化 - SSE、AVX 和 AVX2 的预期加速
Vectorization - Speed up expected for SSE, AVX and AVX2
我正在使用以下 i7 处理器在 MacOS 上进行矢量化基准测试:
$ sysctl -n machdep.cpu.brand_string
Intel(R) Core(TM) i7-4960HQ CPU @ 2.60GHz
我的 MacBook Pro 是 2014 年年中的。
我尝试使用不同的标志选项进行矢量化:我感兴趣的 3 个是 SSE、AVX 和 AVX2。
对于我的基准测试,我添加了 2 个数组的每个元素并将总和存储在第三个数组中。
我必须让你注意到我正在为这些数组使用 double
类型。
以下是我的基准代码中使用的函数:
1*) 首先使用 SSE 向量化:
#ifdef SSE
#include <x86intrin.h>
#define ALIGN 16
void addition_tab(int size, double *a, double *b, double *c)
{
int i;
// Main loop
for (i=size-1; i>=0; i-=2)
{
// Intrinsic SSE syntax
const __m128d x = _mm_load_pd(a); // Load two x elements
const __m128d y = _mm_load_pd(b); // Load two y elements
const __m128d sum = _mm_add_pd(x, y); // Compute two sum elements
_mm_store_pd(c, sum); // Store two sum elements
// Increment pointers by 2 since SSE vectorizes on 128 bits = 16 bytes = 2*sizeof(double)
a += 2;
b += 2;
c += 2;
}
}
#endif
2*) 其次是 AVX256 矢量化:
#ifdef AVX256
#include <immintrin.h>
#define ALIGN 32
void addition_tab(int size, double *a, double *b, double *c)
{
int i;
// Main loop
for (i=size-1; i>=0; i-=4)
{
// Intrinsic AVX syntax
const __m256d x = _mm256_load_pd(a); // Load two x elements
const __m256d y = _mm256_load_pd(b); // Load two y elements
const __m256d sum = _mm256_add_pd(x, y); // Compute two sum elements
_mm256_store_pd(c, sum); // Store two sum elements
// Increment pointers by 4 since AVX256 vectorizes on 256 bits = 32 bytes = 4*sizeof(double)
a += 4;
b += 4;
c += 4;
}
}
#endif
For SSE vectorization, I expect a Speedup equal around 2 because I align data on 128bits = 16 bytes = 2* sizeof(double).
我得到的 SSE 向量化结果如下图所示:
所以,我认为这些结果是有效的,因为 SpeedUp 大约是因子 2。
现在对于AVX256,我得到下图:
For AVX256 vectorization, I expect a Speedup equal around 4 because I align data on 256bits = 32 bytes = 4* sizeof(double).
但是如您所见,我仍然得到 factor 2
而不是 4
的 SpeedUp。
我不明白为什么我使用 SSE 和 AVX 获得相同的加速结果
矢量化。
它是否来自 "compilation flags",来自我的处理器型号,......我不知道。
以下是我为上述所有结果所做的编译命令行:
上交所:
gcc-mp-4.9 -DSSE -O3 -msse main_benchmark.c -o vectorizedExe
对于 AVX256:
gcc-mp-4.9 -DAVX256 -O3 -Wa,-q -mavx main_benchmark.c -o vectorizedExe
此外,对于我的处理器型号,我可以使用 AVX512 向量化吗? (一旦这个问题的问题就解决了)。
感谢您的帮助
更新 1
我尝试了 @Mischa
的不同选项,但仍然无法通过 AVX 标志和选项获得 4 倍的加速。您可以在 http://example.com/test_vectorization/main_benchmark.c.txt (with .txt extension for direct view into browser) and the shell script for benchmarking is http://example.com/test_vectorization/run_benchmark 上查看我的 C 源代码。
如@Mischa 所述,我尝试应用以下命令行进行编译:
$GCC -O3 -Wa,-q -mavx -fprefetch-loop-arrays main_benchmark.c -o
vectorizedExe
但生成的代码没有 AVX 指令。
如果你能看看这些文件,那就太好了。谢谢。
您在缓存->ram 传输上碰壁了。你的 core7 有一个 64 字节的缓存行。对于 sse2,16 字节存储需要 64 字节加载、更新和排队返回到 ram。 16 字节升序加载受益于自动预取预测,因此您获得了一些加载优势。添加 mm_prefetch 个目标内存;比方说,比下一个存储早 256 个字节。这同样适用于 avx2 32 字节存储。
NP。有选项:
(1) x86 特定代码:
#include <emmintrin.h>
...
for (int i=size; ...) {
_mm_prefetch(256+(char*)c, _MM_HINT_T0);
...
_mm256_store_pd(c, sum);
(2) gcc 特定代码:
for (int i=size; ...) {
__builtin_prefetch(c+32);
...
(3) gcc -fprefetch-array-loops
--- 编译器最清楚。
如果您的 gcc 版本支持,(3) 是最好的。
(2) 次优,如果您在同一硬件上编译和 运行。
(1) 可移植到其他编译器。
不幸的是,“256”是一个猜测,并且依赖于硬件。最小值 128,最大值 512,具体取决于您的 CPU:RAM 速度。如果您切换到 _mm512*()
,则将这些数字加倍。
如果您正在处理一系列处理器,我可以建议以涵盖所有情况的方式进行编译,然后测试 cpuid(ax=0)>=7,然后是 cpuid(ax=7,cx=0) :bx & 0x04000010 在 运行 时间(AVX2 为 0x10,AVX512 为 0x04000000,包括预取)。
顺便说一句,如果您使用 gcc 并指定 -mavx 或 -msse2,编译器会为您定义内置宏 __AVX__ 或 __SSE2__;不需要 -DAVX256。为了支持古老的 32 位处理器,-m32 不幸地禁用了 __SSE2__ 因此有效地禁用了 \#include <emmintrin.h>
:-P
HTH
我正在使用以下 i7 处理器在 MacOS 上进行矢量化基准测试:
$ sysctl -n machdep.cpu.brand_string
Intel(R) Core(TM) i7-4960HQ CPU @ 2.60GHz
我的 MacBook Pro 是 2014 年年中的。
我尝试使用不同的标志选项进行矢量化:我感兴趣的 3 个是 SSE、AVX 和 AVX2。
对于我的基准测试,我添加了 2 个数组的每个元素并将总和存储在第三个数组中。
我必须让你注意到我正在为这些数组使用 double
类型。
以下是我的基准代码中使用的函数:
1*) 首先使用 SSE 向量化:
#ifdef SSE
#include <x86intrin.h>
#define ALIGN 16
void addition_tab(int size, double *a, double *b, double *c)
{
int i;
// Main loop
for (i=size-1; i>=0; i-=2)
{
// Intrinsic SSE syntax
const __m128d x = _mm_load_pd(a); // Load two x elements
const __m128d y = _mm_load_pd(b); // Load two y elements
const __m128d sum = _mm_add_pd(x, y); // Compute two sum elements
_mm_store_pd(c, sum); // Store two sum elements
// Increment pointers by 2 since SSE vectorizes on 128 bits = 16 bytes = 2*sizeof(double)
a += 2;
b += 2;
c += 2;
}
}
#endif
2*) 其次是 AVX256 矢量化:
#ifdef AVX256
#include <immintrin.h>
#define ALIGN 32
void addition_tab(int size, double *a, double *b, double *c)
{
int i;
// Main loop
for (i=size-1; i>=0; i-=4)
{
// Intrinsic AVX syntax
const __m256d x = _mm256_load_pd(a); // Load two x elements
const __m256d y = _mm256_load_pd(b); // Load two y elements
const __m256d sum = _mm256_add_pd(x, y); // Compute two sum elements
_mm256_store_pd(c, sum); // Store two sum elements
// Increment pointers by 4 since AVX256 vectorizes on 256 bits = 32 bytes = 4*sizeof(double)
a += 4;
b += 4;
c += 4;
}
}
#endif
For SSE vectorization, I expect a Speedup equal around 2 because I align data on 128bits = 16 bytes = 2* sizeof(double).
我得到的 SSE 向量化结果如下图所示:
所以,我认为这些结果是有效的,因为 SpeedUp 大约是因子 2。
现在对于AVX256,我得到下图:
For AVX256 vectorization, I expect a Speedup equal around 4 because I align data on 256bits = 32 bytes = 4* sizeof(double).
但是如您所见,我仍然得到 factor 2
而不是 4
的 SpeedUp。
我不明白为什么我使用 SSE 和 AVX 获得相同的加速结果 矢量化。
它是否来自 "compilation flags",来自我的处理器型号,......我不知道。
以下是我为上述所有结果所做的编译命令行:
上交所:
gcc-mp-4.9 -DSSE -O3 -msse main_benchmark.c -o vectorizedExe
对于 AVX256:
gcc-mp-4.9 -DAVX256 -O3 -Wa,-q -mavx main_benchmark.c -o vectorizedExe
此外,对于我的处理器型号,我可以使用 AVX512 向量化吗? (一旦这个问题的问题就解决了)。
感谢您的帮助
更新 1
我尝试了 @Mischa
的不同选项,但仍然无法通过 AVX 标志和选项获得 4 倍的加速。您可以在 http://example.com/test_vectorization/main_benchmark.c.txt (with .txt extension for direct view into browser) and the shell script for benchmarking is http://example.com/test_vectorization/run_benchmark 上查看我的 C 源代码。
如@Mischa 所述,我尝试应用以下命令行进行编译:
$GCC -O3 -Wa,-q -mavx -fprefetch-loop-arrays main_benchmark.c -o vectorizedExe
但生成的代码没有 AVX 指令。
如果你能看看这些文件,那就太好了。谢谢。
您在缓存->ram 传输上碰壁了。你的 core7 有一个 64 字节的缓存行。对于 sse2,16 字节存储需要 64 字节加载、更新和排队返回到 ram。 16 字节升序加载受益于自动预取预测,因此您获得了一些加载优势。添加 mm_prefetch 个目标内存;比方说,比下一个存储早 256 个字节。这同样适用于 avx2 32 字节存储。
NP。有选项:
(1) x86 特定代码:
#include <emmintrin.h>
...
for (int i=size; ...) {
_mm_prefetch(256+(char*)c, _MM_HINT_T0);
...
_mm256_store_pd(c, sum);
(2) gcc 特定代码:
for (int i=size; ...) {
__builtin_prefetch(c+32);
...
(3) gcc -fprefetch-array-loops
--- 编译器最清楚。
(3) 是最好的。 (2) 次优,如果您在同一硬件上编译和 运行。 (1) 可移植到其他编译器。
不幸的是,“256”是一个猜测,并且依赖于硬件。最小值 128,最大值 512,具体取决于您的 CPU:RAM 速度。如果您切换到 _mm512*()
,则将这些数字加倍。
如果您正在处理一系列处理器,我可以建议以涵盖所有情况的方式进行编译,然后测试 cpuid(ax=0)>=7,然后是 cpuid(ax=7,cx=0) :bx & 0x04000010 在 运行 时间(AVX2 为 0x10,AVX512 为 0x04000000,包括预取)。
顺便说一句,如果您使用 gcc 并指定 -mavx 或 -msse2,编译器会为您定义内置宏 __AVX__ 或 __SSE2__;不需要 -DAVX256。为了支持古老的 32 位处理器,-m32 不幸地禁用了 __SSE2__ 因此有效地禁用了 \#include <emmintrin.h>
:-P
HTH