Ich Benchmarking die Leistung von std::none_of
gegen eine drei verschiedene manuelle Implementierungen mit i) a for
Schleife, ii) eine Bereich-basierte for
Schleife und iii) Iteratoren. Zu meiner Überraschung fand ich, dass, während alle drei manuellen Implementierungen ungefähr die gleiche Zeit nehmen, wesentlich schneller ist. Meine Frage ist - warum ist das der Fall?Warum ist std :: none_of schneller als eine handgerollte Schleife?
Ich verwendete die Google Benchmark-Bibliothek und kompilierte mit -std=c++14 -O3
. Beim Ausführen des Tests beschränkte ich die Affinität des Prozesses auf einen einzelnen Prozessor. Ich erhalte das folgende Ergebnis mit GCC 6.2:
Benchmark Time CPU Iterations
--------------------------------------------------------
benchmarkSTL 28813 ns 28780 ns 24283
benchmarkManual 46203 ns 46191 ns 15063
benchmarkRange 48368 ns 48243 ns 16245
benchmarkIterator 44732 ns 44710 ns 15698
Auf Clang 3.9 std::none_of
ist auch schneller als die manuelle for
Schleife wenn die Geschwindigkeitsdifferenz kleiner ist. Hier ist der Testcode (nur einschließlich des manuellen for-Schleife der Kürze halber):
#include <algorithm>
#include <array>
#include <benchmark/benchmark.h>
#include <functional>
#include <random>
const size_t N = 100000;
const unsigned value = 31415926;
template<size_t N>
std::array<unsigned, N> generateData() {
std::mt19937 randomEngine(0);
std::array<unsigned, N> data;
std::generate(data.begin(), data.end(), randomEngine);
return data;
}
void benchmarkSTL(benchmark::State & state) {
auto data = generateData<N>();
while (state.KeepRunning()) {
bool result = std::none_of(
data.begin(),
data.end(),
std::bind(std::equal_to<unsigned>(), std::placeholders::_1, value));
assert(result);
}
}
void benchmarkManual(benchmark::State & state) {
auto data = generateData<N>();
while (state.KeepRunning()) {
bool result = true;
for (size_t i = 0; i < N; i++) {
if (data[i] == value) {
result = false;
break;
}
}
assert(result);
}
}
BENCHMARK(benchmarkSTL);
BENCHMARK(benchmarkManual);
BENCHMARK_MAIN();
beachte, dass die Daten zum Erzeugen eines Zufallszahlengenerators verwendet, ist irrelevant. Ich bekomme das gleiche Ergebnis, wenn ich einfach das i
-te Element auf i
setze und überprüfe, ob der Wert N + 1
enthalten ist.
Warum nicht vergleichen a) die Implementierung und b) den generierten Code? –
Ich weiß nicht, warum in Ihrem Fall, aber weil die Bibliothek von der Compiler-Implementierung zur Verfügung gestellt wird, können sie plattformspezifische Tricks (zB Verzweigung Vorhersage Hinweise) verwenden, um Standard-C++ - Verhalten zur Verfügung zu stellen. Also würde dieses Ergebnis (wenn es gültig ist) mich nicht überraschen. – Galik
Es scheint mit der Tatsache zu tun, dass Sie unsigned Ganzzahlen verwenden: Compiler können davon ausgehen, dass für vorzeichenbehaftete Ganzzahlen nie einen Überlauf (wie es ist undefiniert Verhalten gemäß dem Standard) und verwenden Sie diese Tatsache, um aggressiv zu optimieren. Offenbar hat 'std :: none_of' eine Spezialisierung für vorzeichenlose Ganzzahlen, die Sie nicht in Ihren handschriftlichen Funktionen erhalten. Wenn Sie zu 'long' statt zu' size_t' und 'unsigned' wechseln, ist die manuelle Version tatsächlich schneller. – Corristo