Porównywałam wydajność std::none_of
z trzema różnymi implementacjami ręcznymi, używając i) pętli for
, ii) pętli opartych na pasmie z zakresu for
i iii) iteratorów. Ku mojemu zaskoczeniu odkryłem, że podczas gdy wszystkie trzy wdrożenia ręczne odbywają się mniej więcej w tym samym czasie, std::none_of
jest znacznie szybszy. Moje pytanie brzmi - dlaczego tak się dzieje?Dlaczego std :: none_of jest szybszy niż pętla ręczna?
Użyłem biblioteki testowej Google i skompilowałem ją z -std=c++14 -O3
. Podczas przeprowadzania testu ograniczyłem powinowactwo procesu do pojedynczego procesora. Pojawia się następujący wynik przy użyciu 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
Na Clang 3.9 std::none_of
jest również szybsze niż ręczne for
pętli choć różnica prędkości jest mniejszy. Oto kod testowy (tylko wraz z instrukcją do pętli dla zwięzłość):
#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();
Należy pamiętać, że generowanie danych za pomocą generatora liczb losowych, nie ma znaczenia. Otrzymuję taki sam wynik, gdy ustawiam i
-ty element na i
i sprawdzam, czy zawiera się wartość N + 1
.
Dlaczego nie porównać a) realizacji i b) wygenerowanego kodu? –
Nie wiem, dlaczego w twoim przypadku, ale ponieważ biblioteka jest dostarczana przez implementację kompilatora, są one w stanie używać sztuczek specyficznych dla platformy (np. Podpowiedzi do podpowiedzi rozgłoszeniowych?) W celu zapewnienia zachowania standardowego C++. Więc ten wynik (jeśli jest prawidłowy) nie zaskoczyłby mnie. – Galik
Wydaje się, że jest to związane z faktem, że używasz liczb całkowitych bez znaku: kompilatory mogą założyć, że dla liczb całkowitych ze znakiem nigdy nie występuje przepełnienie (ponieważ jest to niezdefiniowane zachowanie zgodne ze standardem) i wykorzystaj ten fakt do agresywnej optymalizacji. Najwyraźniej 'std :: none_of' ma specjalizację dla liczb całkowitych bez znaku, których nie dostajesz w swoich ręcznych funkcjach. Jeśli zamienisz na 'long' zamiast' size_t' i 'unsigned' wersja manualna jest w rzeczywistości szybsza. – Corristo