我了解到,当对容器的每个元素进行一些计算时,如果内存是连续的,则可以获得最佳性能。 但是,如果必须同时使用两个或多个大容器(以至于它们无法完全容纳高速缓存),该怎么办?
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}}?
如果是后者,我是否应该习惯性地为*fit
和ivec
分配交错模式以防止丢失?
答案 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的结果:
N为20000000的结果:
如您所见,对于较大的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::iota
和std::transform
替换了您的手动循环。 N大的结果:
如您所见,版本3比#1和#2更快(尽管有些微)。因此,请首先使用标准库函数,并且只有在过慢时才手动滚动它。