【发布时间】:2016-11-21 11:00:13
【问题描述】:
我尝试与 SSE 合作,但遇到了一些奇怪的行为。
我编写了简单的代码,用于将两个字符串与 SSE Intrinsics 进行比较,运行它就可以了。但后来我明白了,在我的代码中,指针之一仍未对齐,但我使用 _mm_load_si128 指令,该指令要求指针在 16 字节边界上对齐。
//Compare two different, not overlapping piece of memory
__attribute((target("avx"))) int is_equal(const void* src_1, const void* src_2, size_t size)
{
//Skip tail for right alignment of pointer [head_1]
const char* head_1 = (const char*)src_1;
const char* head_2 = (const char*)src_2;
size_t tail_n = 0;
while (((uintptr_t)head_1 % 16) != 0 && tail_n < size)
{
if (*head_1 != *head_2)
return 0;
head_1++, head_2++, tail_n++;
}
//Vectorized part: check equality of memory with SSE4.1 instructions
//src1 - aligned, src2 - NOT aligned
const __m128i* src1 = (const __m128i*)head_1;
const __m128i* src2 = (const __m128i*)head_2;
const size_t n = (size - tail_n) / 32;
for (size_t i = 0; i < n; ++i, src1 += 2, src2 += 2)
{
printf("src1 align: %d, src2 align: %d\n", align(src1) % 16, align(src2) % 16);
__m128i mm11 = _mm_load_si128(src1);
__m128i mm12 = _mm_load_si128(src1 + 1);
__m128i mm21 = _mm_load_si128(src2);
__m128i mm22 = _mm_load_si128(src2 + 1);
__m128i mm1 = _mm_xor_si128(mm11, mm21);
__m128i mm2 = _mm_xor_si128(mm12, mm22);
__m128i mm = _mm_or_si128(mm1, mm2);
if (!_mm_testz_si128(mm, mm))
return 0;
}
//Check tail with scalar instructions
const size_t rem = (size - tail_n) % 32;
const char* tail_1 = (const char*)src1;
const char* tail_2 = (const char*)src2;
for (size_t i = 0; i < rem; i++, tail_1++, tail_2++)
{
if (*tail_1 != *tail_2)
return 0;
}
return 1;
}
我打印两个指针的对齐方式,其中一个 wal 对齐但第二个 - 不是。并且程序仍然可以正确快速地运行。
然后我像这样创建合成测试:
//printChars128(...) function just print 16 byte values from __m128i
const __m128i* A = (const __m128i*)buf;
const __m128i* B = (const __m128i*)(buf + rand() % 15 + 1);
for (int i = 0; i < 5; i++, A++, B++)
{
__m128i A1 = _mm_load_si128(A);
__m128i B1 = _mm_load_si128(B);
printChars128(A1);
printChars128(B1);
}
正如我们所料,它在第一次迭代时崩溃,当尝试加载指针 B 时。
有趣的事实是,如果我将target 切换到sse4.2,那么我对is_equal 的实现将会崩溃。
另一个有趣的事实是,如果我尝试对齐第二个指针而不是第一个(因此第一个指针将不对齐,第二个 - 对齐),那么 is_equal 将崩溃。
所以,我的问题是:“如果我启用 avx 指令生成,为什么 is_equal 函数仅在第一个指针对齐的情况下工作正常?”
UPD:这是C++ 代码。我在 Windows、x86 下使用MinGW64/g++, gcc version 4.9.2 编译我的代码。
编译字符串:g++.exe main.cpp -Wall -Wextra -std=c++11 -O2 -Wcast-align -Wcast-qual -o main.exe
【问题讨论】:
-
采用内存操作数(不包括对齐的移动)的 VEX 编码指令不需要对齐。指定 AVX 将使编译器使用 VEX 编码的指令。 IOW,当你打开 AVX 时它碰巧工作时,你得到了(不)幸运。如果 GCC 决定使用任何正常(对齐)移动,它仍然可能崩溃。
-
如果负载被卷入一个参数,它会失去对齐要求(除了传统编码),反汇编确认
-
但不一样,那里没有指令可以将负载卷入,与第一种情况不同。
-
@Olaf 这个问题对 C 和 C++ 都有效。
-
@Olaf 我只是说演员阵容对于这个问题并不重要。这个问题是关于 SSE 和对齐的,而不是关于正确的 C++ 编码风格。但无论如何,我无法阻止任何人对此进行吹毛求疵。