Я сравнивал производительность std::none_of
с тремя различными ручными реализациями, используя i) цикл for
, ii) цикл for
на основе диапазона и iii) итераторы. К моему удивлению, я обнаружил, что, хотя все три ручные реализации занимают примерно одно и то же время, std::none_of
значительно быстрее. Мой вопрос: почему это так?Почему std :: none_of быстрее, чем ручная петля?
Я использовал тестовую библиотеку Google и скомпилировал ее с помощью -std=c++14 -O3
. При запуске теста я ограничил близость процесса к одному процессору. Я получаю следующий результат, используя 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
На Clang 3.9 std::none_of
также быстрее, чем ручной for
цикла, хотя разница в скорости меньше. Вот код теста (только в том числе инструкции для цикла для краткости):
#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();
Обратите внимание, что генерирование данных с использованием генератора случайных чисел не имеет значения. Я получаю тот же результат, когда просто устанавливаю i
-й элемент на i
и проверяю, содержится ли значение N + 1
.
Почему бы не сравнить a) реализацию и b) сгенерированный код? –
Я не знаю, почему в вашем случае, но поскольку библиотека предоставляется реализацией компилятора, они могут использовать трюки, специфичные для платформы (например, подсказки для прогнозирования ветвей?), Чтобы обеспечить стандартное поведение на С ++. Поэтому этот результат (если он действителен) меня не удивит. – Galik
Кажется, что это связано с тем, что вы используете целые числа без знака: компиляторы могут предполагать, что для целых чисел со знаком никогда не будет переполнения (поскольку это неопределенное поведение в соответствии со стандартом) и использовать этот факт для агрессивной оптимизации. По-видимому, 'std :: none_of' имеет специализацию для целых чисел без знака, которые вы не получаете в своих рукописных функциях. Если вы используете «long» вместо 'size_t' и' unsigned', то ручная версия работает быстрее. – Corristo