我有一个功能。它会在很多时间内访问向量中的相同元素。我想知道访问元素的性能。例如
Void f()
{
// v[i] appears many times, each time use v[i] to access it
// the other way is introduce a variable to record it like
auto& e = v[i];
}
哪一个更好?或者他们是一样的。
答案 0 :(得分:1)
通常,当您使用v[i]
时,您将调用成员函数std::vector::operator[]
。如果多次使用它,则多次调用函数。多次调用非内联函数会导致堆栈切换,因此需要更多时间。将结果(引用)存储在变量中仅将函数调用一次。
现在,大多数体面的编译器都可以优化调用(甚至内联调用),所以很可能你不会看到差异。但是,最好的方法是使用分析器对其进行测试并查看结果。
EDIT1 请阅读this。它讨论了类似的内容,其中多次在循环条件内调用函数可能会导致性能下降。
EDIT2 我在我的机器上测试了你的代码(gcc5,i5 8GB RAM)并定时,代码如下。打开优化器后,没有区别(g ++)。如果没有优化,参考版本的速度是原来的两倍。
#include <iostream>
#include <chrono>
#include <vector>
auto timing = [](auto && F, auto && ... params)
{
auto start = std::chrono::steady_clock::now();
std::forward<decltype(F)>(F)(std::forward<decltype(params)>(params)...);
return std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start).count();
};
std::vector<int> v(1024);
long long f(std::size_t i) // we pick the i-th element
{
auto& e = v[i];
long long sum = 0;
for(volatile std::size_t k = 0 ; k < 1000000000; ++k)
sum += e;
return sum;
}
long long g(std::size_t i)
{
long long sum = 0;
for(volatile std::size_t k = 0 ; k < 1000000000; ++k)
sum += v[i];
return sum;
}
int main()
{
for(std::size_t i = 0; i < 1024; ++i)
v[i] = i * i;
auto time0 = timing(f, 42);
auto time1 = timing(g, 42);
std::cout << time0 << std::endl;
std::cout << time1 << std::endl;
}
Live on Coliru, with optimizations turned on Live on Coliru, no optimizations
答案 1 :(得分:1)
信不信由你,它可以产生巨大的差异(甚至考虑到缓存命中使得相同元素的访问非常有效)。
这是一些测试代码:
#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 ++会。去图......