一个例子可能是最好的,所以考虑一下this代码(忽略逻辑,这是无意义的):
#include <emmintrin.h>
auto foo(long long const a[], size_t n)
{
auto const v = _mm_set_epi64x(0xDEADBEEFDEADBEEF, 0xBAADF00DBAADF00D);
auto r = v;
for (size_t i = 0; i < n; i += sizeof(v) / sizeof(*a))
{
r = _mm_add_epi64(r, _mm_sub_epi64(_mm_load_si128(
reinterpret_cast<__m128i const*>(&a[i])), v));
}
return r;
}
注意每个编译器都会生成与此类似的内存访问:
.LCPI0_0:
.quad -4995072469926809587 # 0xbaadf00dbaadf00d
.quad -2401053088876216593 # 0xdeadbeefdeadbeef
...
movdqa xmm0, xmmword ptr [rip + .LCPI0_0]
尽管事实上我使用
_mm_set_epi64x
而不是 _mm_load_si128
来初始化变量。
这很容易破坏多个数据缓存行。也感觉很没有必要。
如何可靠地且高效地让编译器停止这样做,以便我可以将内容保留在指令流中并避免不必要的内存访问?
注意:我已经知道我可以引入
volatile
变量来解决这个问题,这不会消除内存访问,但至少引用堆栈内存而不是静态数据。我想知道是否有更好的方法来完全避免内存访问。
一种方法是从函数返回这些值。但将这些函数保留在不同的翻译单元中。保留在同一个翻译单元中并没有帮助,因为这些都是小函数,并且 clang 和 gcc 都不尊重
__attribute((noinline))
,这是不幸的。
// a.cpp
__attribute__((noinline))
long dead() {
return 0xDEADBEEFDEADBEEF;
}
__attribute__((noinline))
long baad() {
return 0xBAADF00DBAADF00D;
}
// b.cpp
#include <emmintrin.h>
auto foo(long long const a[], size_t n)
{
auto const v = _mm_set_epi64x(dead(), baad());
auto r = v;
for (size_t i = 0; i < n; i += sizeof(v) / sizeof(*a))
{
r = _mm_add_epi64(r, _mm_sub_epi64(_mm_load_si128(reinterpret_cast<__m128i const *>(&a[i])), v));
}
return r;
}