カウンターを増やしているか減らしているかよりもはるかに重要なことは、メモリを上げているか下げているかです。ほとんどのキャッシュは、ダウン メモリではなくアップ メモリ用に最適化されています。メモリ アクセス時間は、今日のほとんどのプログラムが直面しているボトルネックであるため、メモリを増やすようにプログラムを変更すると、カウンタをゼロ以外の値と比較する必要がある場合でも、パフォーマンスが向上する可能性があります。一部のプログラムでは、コードを変更してメモリを下げるのではなく、メモリを上げるように変更したところ、パフォーマンスが大幅に向上しました。
懐疑的?私が得た出力は次のとおりです。
sum up = 705046256
sum down = 705046256
Ave. Up Memory = 4839 mus
Ave. Down Memory = 5552 mus
sum up = inf
sum down = inf
Ave. Up Memory = 18638 mus
Ave. Down Memory = 19053 mus
このプログラムの実行から:
#include <chrono>
#include <iostream>
#include <random>
#include <vector>
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 RAI, class T>
inline void sum_abs_up(RAI first, RAI one_past_last, T &total) {
T sum = 0;
auto it = first;
do {
sum += *it;
it++;
} while (it != one_past_last);
total += sum;
}
template<class RAI, class T>
inline void sum_abs_down(RAI first, RAI one_past_last, T &total) {
T sum = 0;
auto it = one_past_last;
do {
it--;
sum += *it;
} while (it != first);
total += sum;
}
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;
}
int main() {
std::size_t num_repititions = 1 << 10;
{
typedef int ValueType;
auto lower = std::numeric_limits<ValueType>::min();
auto upper = std::numeric_limits<ValueType>::max();
std::vector<ValueType> vec(1 << 24);
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 << "sum up = " << sum_up << '\n';
std::cout << "sum down = " << sum_down << '\n';
std::cout << "Ave. Up Memory = " << time_up/(num_repititions * 1000) << " mus\n";
std::cout << "Ave. Down Memory = "<< time_down/(num_repititions * 1000) << " mus"
<< std::endl;
}
{
typedef double ValueType;
auto lower = std::numeric_limits<ValueType>::min();
auto upper = std::numeric_limits<ValueType>::max();
std::vector<ValueType> vec(1 << 24);
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 << "sum up = " << sum_up << '\n';
std::cout << "sum down = " << sum_down << '\n';
std::cout << "Ave. Up Memory = " << time_up/(num_repititions * 1000) << " mus\n";
std::cout << "Ave. Down Memory = "<< time_down/(num_repititions * 1000) << " mus"
<< std::endl;
}
return 0;
}
どちらも同じことsum_abs_up
をsum_abs_down
行い、同じようにタイミングが取られますが、唯一の違いは、sum_abs_up
メモリが上がる一方でメモリsum_abs_down
が下がることです。vec
両方の関数が同じメモリ位置にアクセスできるように、参照渡しもしています。それにもかかわらず、sum_abs_up
は一貫して より高速ですsum_abs_down
。自分で実行してみてください (g++ -O3 でコンパイルしました)。
参考までに、これらの変更が将来のタイミングに影響を与えないようにしながら、変更をvec_original
容易にし、それらを変更できるようにするために、実験のためにそこにいます.sum_abs_up
sum_abs_down
vec
私がタイミングを計っているループがどれほどタイトであるかに注意することが重要です。ループの本体が大きい場合、ループの本体の実行にかかる時間が完全に支配される可能性が高いため、反復子がメモリを上下するかどうかは問題になりません。また、いくつかのまれなループでは、メモリを下に移動する方が上に移動するよりも速い場合があることに注意してください。しかし、そのようなループであっても、上昇が下降よりも常に遅いというケースはめったにありません (メモリを上昇するループとは異なり、メモリを上昇するループは、同等のダウン メモリ ループよりも常に高速です。ほんの一握りの場合、それらは 40 でさえありました)。 +% 速い)。
ポイントは、経験則として、オプションがある場合、ループの本体が小さく、ループがメモリを下に移動するのではなくメモリを上に移動することにほとんど違いがない場合は、メモリを上に移動する必要があるということです。