カウンターを増やすか減らすかよりも重要なのは、メモリを増やすか減らすかです。ほとんどのキャッシュは、メモリのダウンではなく、メモリのアップのために最適化されています。メモリアクセス時間は今日のほとんどのプログラムが直面するボトルネックであるため、これは、プログラムを変更してメモリを増やすと、カウンターをゼロ以外の値と比較する必要がある場合でも、パフォーマンスが向上する可能性があることを意味します。一部のプログラムでは、コードを変更してメモリを減らすのではなくメモリを増やすことで、パフォーマンスが大幅に向上しました。
懐疑的?メモリをアップ/ダウンするループをタイムループするプログラムを書くだけです。これが私が得た出力です:
Average Up Memory = 4839 mus
Average Down Memory = 5552 mus
Average Up Memory = 18638 mus
Average Down Memory = 19053 mus
(「mus」はマイクロ秒を表します)このプログラムの実行から:
#include <chrono>
#include <iostream>
#include <random>
#include <vector>
//Sum all numbers going up memory.
template<class Iterator, class T>
inline void sum_abs_up(Iterator first, Iterator one_past_last, T &total) {
T sum = 0;
auto it = first;
do {
sum += *it;
it++;
} while (it != one_past_last);
total += sum;
}
//Sum all numbers going down memory.
template<class Iterator, class T>
inline void sum_abs_down(Iterator first, Iterator one_past_last, T &total) {
T sum = 0;
auto it = one_past_last;
do {
it--;
sum += *it;
} while (it != first);
total += sum;
}
//Time how long it takes to make num_repititions identical calls to sum_abs_down().
//We will divide this time by num_repitions to get the average time.
template<class T>
std::chrono::nanoseconds TimeDown(std::vector<T> &vec, const std::vector<T> &vec_original,
std::size_t num_repititions, T &running_sum) {
std::chrono::nanoseconds total{0};
for (std::size_t i = 0; i < num_repititions; i++) {
auto start_time = std::chrono::high_resolution_clock::now();
sum_abs_down(vec.begin(), vec.end(), running_sum);
total += std::chrono::high_resolution_clock::now() - start_time;
vec = vec_original;
}
return total;
}
template<class T>
std::chrono::nanoseconds TimeUp(std::vector<T> &vec, const std::vector<T> &vec_original,
std::size_t num_repititions, T &running_sum) {
std::chrono::nanoseconds total{0};
for (std::size_t i = 0; i < num_repititions; i++) {
auto start_time = std::chrono::high_resolution_clock::now();
sum_abs_up(vec.begin(), vec.end(), running_sum);
total += std::chrono::high_resolution_clock::now() - start_time;
vec = vec_original;
}
return total;
}
template<class Iterator, typename T>
void FillWithRandomNumbers(Iterator start, Iterator one_past_end, T a, T b) {
std::random_device rnd_device;
std::mt19937 generator(rnd_device());
std::uniform_int_distribution<T> dist(a, b);
for (auto it = start; it != one_past_end; it++)
*it = dist(generator);
return ;
}
template<class Iterator>
void FillWithRandomNumbers(Iterator start, Iterator one_past_end, double a, double b) {
std::random_device rnd_device;
std::mt19937_64 generator(rnd_device());
std::uniform_real_distribution<double> dist(a, b);
for (auto it = start; it != one_past_end; it++)
*it = dist(generator);
return ;
}
template<class ValueType>
void TimeFunctions(std::size_t num_repititions, std::size_t vec_size = (1u << 24)) {
auto lower = std::numeric_limits<ValueType>::min();
auto upper = std::numeric_limits<ValueType>::max();
std::vector<ValueType> vec(vec_size);
FillWithRandomNumbers(vec.begin(), vec.end(), lower, upper);
const auto vec_original = vec;
ValueType sum_up = 0, sum_down = 0;
auto time_up = TimeUp(vec, vec_original, num_repititions, sum_up).count();
auto time_down = TimeDown(vec, vec_original, num_repititions, sum_down).count();
std::cout << "Average Up Memory = " << time_up/(num_repititions * 1000) << " mus\n";
std::cout << "Average Down Memory = " << time_down/(num_repititions * 1000) << " mus"
<< std::endl;
return ;
}
int main() {
std::size_t num_repititions = 1 << 10;
TimeFunctions<int>(num_repititions);
std::cout << '\n';
TimeFunctions<double>(num_repititions);
return 0;
}
sum_abs_up
とsum_abs_down
はどちらも同じことを行い(数値のベクトルを合計します)、同じ方法で時間を計測しますが、唯一の違いは、sum_abs_up
メモリをsum_abs_down
増やしながらメモリを減らします。vec
両方の関数が同じメモリ位置にアクセスできるように、参照渡しもしています。それにもかかわらず、sum_abs_up
よりも一貫して高速ですsum_abs_down
。自分で実行します(私はg ++ -O3でコンパイルしました)。
私がタイミングをとっているループがどれほどタイトであるかに注意することが重要です。ループの本体が大きい場合、ループの本体の実行にかかる時間が完全に支配される可能性が高いため、イテレータがメモリを上下するかどうかは問題になりません。また、いくつかのまれなループでは、メモリをダウンする方が、アップするよりも速い場合があることにも言及することが重要です。しかし、そのようなループがあっても、メモリを増やすのが常にダウンするよりも遅いというケースは決してありませんでした(メモリを増やす小さなボディのループとは異なり、その反対は頻繁に当てはまります。実際には、少数のループの場合)時間を計ったところ、メモリを増やすことによるパフォーマンスの向上は40 +%でした)。
経験則として、オプションがある場合、ループの本体が小さい場合、およびループをダウンさせるのではなくメモリを増やすことの違いがほとんどない場合は、メモリを増やす必要があります。
FYI vec_original
は実験のためにあり、変更sum_abs_up
を容易にsum_abs_down
し、変更vec
を可能にする一方で、これらの変更が将来のタイミングに影響を与えないようにします。私は非常にで遊んでお勧めしますsum_abs_up
とsum_abs_down
、その結果をタイミング。