向量元素如何访问性能

时间:2015-12-02 22:55:12

标签: c++ performance stl

我有一个功能。它会在很多时间内访问向量中的相同元素。我想知道访问元素的性能。例如

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];
}

哪一个更好?或者他们是一样的。

2 个答案:

答案 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 ++会。去图......