同时遍历两个容器时的缓存位置

时间:2018-10-31 18:45:29

标签: c++ memory-management cpu-cache

我了解到,当对容器的每个元素进行一些计算时,如果内存是连续的,则可以获得最佳性能。 但是,如果必须同时使用两个或多个大容器(以至于它们无法完全容纳高速缓存),该怎么办?

int main()
{
    const std::size_t BIG_SIZE = 2000000;  // A number such that both ivec and fvec won't fit the cache

    std::vector<int> ivec(BIG_SIZE, 0);
    int start = 0;
    for (auto& i : ivec)
        i = start++;


    std::vector<float> fvec(BIG_SIZE, 0.f);


    auto iit = ivec.cbegin();
    auto fit = fvec.begin();
    for (; iit != ivec.cend() && fit != fvec.end(); ++iit, ++fit) 
        *fit = *iit * 3.14;  // What happens here?
}

在最后一个循环中,缓存将同时加载*iit附近和*fit附近的内存块,还是每次访问*iit然后访问{{ 1}}?

如果是后者,我是否应该习惯性地为*fitivec分配交错模式以防止丢失?

1 个答案:

答案 0 :(得分:2)

查看最快速度的最简单方法是基准测试。答案取决于:硬件,输入大小和其他内容(编译器,标志等)。但是,出于本示例的目的,我将使用带有quick-bench.com的clang-6.0,C + + 17,-O3和libstdc ++。这是进行比较的代码:

static void One(benchmark::State& state) {
  for (auto _ : state) {
    const std::size_t BIG_SIZE = 20000000;

    std::vector<int> ivec(BIG_SIZE, 0);
    benchmark::DoNotOptimize(ivec);
    int start = 0;
    for (auto& i : ivec)
        i = start++;

    std::vector<float> fvec(BIG_SIZE, 0.f);
    benchmark::DoNotOptimize(fvec);

    auto iit = ivec.cbegin();
    auto fit = fvec.begin();
    for (; iit != ivec.cend() && fit != fvec.end(); ++iit, ++fit) 
        *fit = *iit * 3.14;
  }
}
BENCHMARK(One);

static void Two(benchmark::State& state) {
  for (auto _ : state) {
    const std::size_t BIG_SIZE = 20000000;

    std::vector<int> ivec(BIG_SIZE, 0);
    std::vector<float> fvec(BIG_SIZE, 0.f);
    benchmark::DoNotOptimize(ivec);
    benchmark::DoNotOptimize(fvec);
    int start = 0;
    auto fit = fvec.begin();
    for (auto& i : ivec) {
        i = start++;
        *fit = i * 3.14;
        ++fit;
    }
  }
}
BENCHMARK(Two);

第一个功能是原始代码,第二个功能是修改版本。 benchmark::DoNotOptimize只是简单地阻止了两个向量的优化。 N为2000的结果:

enter image description here

N为20000000的结果:

enter image description here

如您所见,对于较大的N,第二个示例受到影响。您将需要仔细地编写代码并执行基准测试,而不是进行假设(Google基准测试是quick-bench.com的基础技术)


通过使用标准库函数,您实际上可以提高性能。大概是因为它们已经针对不同的场景进行了优化,并委托了比您可以手动优化的更好的代码。这是一个示例:

static void Three(benchmark::State& state) {
  for (auto _ : state) {
    const std::size_t BIG_SIZE = 20000000;

    std::vector<int> ivec(BIG_SIZE, 0);
    std::vector<float> fvec(BIG_SIZE, 0.f);
    benchmark::DoNotOptimize(ivec);
    benchmark::DoNotOptimize(fvec);
    int start = 0;
    auto fit = fvec.begin();
    std::iota(ivec.begin(), ivec.end(), 0);
    std::transform(ivec.begin(), ivec.end(), 
      fvec.begin(),
      [] (const auto a) {
        return a * 3.14;
      });
  }
}
BENCHMARK(Three);

我们已用std::iotastd::transform替换了您的手动循环。 N大的结果:

enter image description here

如您所见,版本3比#1和#2更快(尽管有些微)。因此,请首先使用标准库函数,并且只有在过慢时才手动滚动它。