信不信由你,它可以产生巨大的影响(即使考虑到缓存命中,使得对同一元素的一次又一次访问非常有效)。
这是一些测试代码:
#include <vector>
#include <cstdint>
#include <iostream>
static inline std::uint64_t RDTSC()
{
unsigned int hi, lo;
__asm__ volatile("rdtsc" : "=a" (lo), "=d" (hi));
return ((uint64_t)hi << 32) | lo;
}
void v1(const std::vector<double> & v, std::vector<double> & ov)
{
for(int i=0 ; i<100000000 ; ++i)
ov.push_back(v[5]);
}
void v2(const std::vector<double> & v, std::vector<double> & ov)
{
auto fixed_var = v[5];
for(int i=0 ; i<100000000 ; ++i)
ov.push_back(fixed_var);
}
void v3(const std::vector<double> & v, std::vector<double> & ov)
{
const double fixed_var = v[5];
for(int i=0 ; i<100000000 ; ++i)
ov.push_back(fixed_var);
}
void flush_cache()
{
//Flush L1 and L2 cache by thrashing it with garbage
const int cache_size = 256*1024*1024;
auto garbage = new char[cache_size];
for(int i=0 ; i < 48; ++i)
{
for (int j=0 ; j<cache_size ; j++)
garbage[j] = i*j;
}
delete[] garbage;
std::cout << "flushed cache\n";
}
int main(void)
{
std::vector<double> v;
std::vector<double> ov;
for(int i=0 ; i<10000000 ; ++i)
{
v.push_back(i/(i+1000000));
}
//try v1
auto start = RDTSC();
v1(v,ov);
auto end = RDTSC();
auto v1t = end-start;
std::cout << "V1: 1.0x\n";
//flush and clear
ov.clear();
flush_cache();
//try v2
start = RDTSC();
v2(v,ov);
end = RDTSC();
auto v2t = end-start;
std::cout << "V2: " << ((double)v2t)/v1t << "x\n";
//flush and clear
ov.clear();
flush_cache();
//try v3
start = RDTSC();
v3(v,ov);
end = RDTSC();
auto v3t = end-start;
std::cout << "V3: " << ((double)v3t)/v1t << "x\n";
}
我们看到,幼稚的方法和使用变量之间实际上存在巨大差异:
V1: 1.0x
flushed cache
V2: 0.221311x
flushed cache
V3: 0.222199x
我仔细检查了程序集,以确保没有因为我们没有使用结果而被优化掉。
我们还使用 RTDSC 指令来确保 CPU 周期的时序一致。
V2 和 V3 之间的差异并不显着:它们在任何给定的运行中交换位置。但是不每次都访问数组绝对是速度提升了 70-80%。
请注意,如果您在运行之间不刷新 L1/L2 缓存并在 V2 和 V3 之后运行 V1,它们都会有相似的时序。因此,刷新缓存非常重要。
更多测试表明,g++ 和 c++ 不会对此进行优化(只是将值存储在寄存器中),但英特尔 C++ 会。去图...