При компиляции кода теста ниже с -O3
Я был впечатлен разницей в задержке, поэтому я начал задаваться вопросом, а не «обманывает» ли компилятор, удаляя код каким-либо образом. Есть ли способ проверить это? Я в безопасности для сравнения с -O3
? Реально ли ожидать 15-кратного увеличения скорости?
Результаты без -O3
: Средний: 239 Нанос Мин: 230 нанос (9 миллионов итераций)
Результаты с-O3
: Средний: 14 нанос, мин: 12 нанос (9 миллионов итераций)
int iterations = stoi(argv[1]);
int load = stoi(argv[2]);
long long x = 0;
for(int i = 0; i < iterations; i++) {
long start = get_nano_ts(); // START clock
for(int j = 0; j < load; j++) {
if (i % 4 == 0) {
x += (i % 4) * (i % 8);
} else {
x -= (i % 16) * (i % 32);
}
}
long end = get_nano_ts(); // STOP clock
// (omitted for clarity)
}
cout << "My result: " << x << endl;
Примечание: я использую clock_gettime
измерять:
long get_nano_ts() {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec * 1000000000 + ts.tv_nsec;
}
Это может быть очень трудно измерить то, что вы думаете, что измеряете. В случае внутреннего цикла:
for (int j = 0; j < load; ++j)
if (i % 4 == 0)
x += (i % 4) * (i % 8);
else x -= (i % 16) * (i % 32);
Проницательный компилятор может увидеть это и изменить код на что-то вроде:
x = load * 174; // example only
Я знаю, что это не эквивалентно, но есть довольно простое выражение, которое может заменить этот цикл.
Чтобы быть уверенным, стоит использовать gcc -S
Опция компилятора и посмотрите на код сборки, который он генерирует.
Компилятор, безусловно, будет «обманывать» и удалять ненужный код при компиляции с включенной оптимизацией. На самом деле он идет очень долго, чтобы ускорить ваш код, что почти всегда приводит к впечатляющим ускорениям. Если бы он каким-то образом смог вывести формулу, которая вычисляет результат за постоянное время, вместо использования этого цикла, он бы это сделал. Постоянный коэффициент 15 не является чем-то необычным.
Но это делает не означает, что вы должны профилировать неоптимизированные сборки! Действительно, при использовании таких языков, как C и C ++, производительность неоптимизированных сборок практически не имеет смысла. Вам не нужно беспокоиться об этом вообще.
Конечно, это может помешать микро-тестам, как показано выше. Два момента к этому:
Поскольку вы, кажется, делаете это, у кода, который вы показываете, есть хорошие шансы стать разумным микро-эталоном. Одна вещь, которую вы должны остерегаться, это то, что ваш компилятор перемещает оба вызова get_nano_ts();
на той же стороне петли. Это разрешено делать, так как «время выполнения» не считается видимым побочным эффектом. (Стандарт даже не предписывает вашей машине работать на конечной скорости.) Утверждалось Вот что это обычно не проблема, хотя я не могу судить, является ли данный ответ верным или нет.
Если ваша программа не делает ничего дорогого, кроме того, что вы хотите сравнить (что она, по возможности, не должна делать в любом случае), вы также можете переместить измерение времени «за пределы», например. с время.
Вам следует всегда бенчмарк с включенной оптимизацией. Однако важно убедиться, что то, что вы хотите, не будет оптимизировано компилятором.
Один из способов сделать это — распечатать результаты расчетов после остановки таймера:
long long x = 0;
for(int i = 0; i < iterations; i++) {
long start = get_nano_ts(); // START clock
for(int j = 0; j < load; j++) {
if (i % 4 == 0) {
x += (i % 4) * (i % 8);
} else {
x -= (i % 16) * (i % 32);
}
}
long end = get_nano_ts(); // STOP clock
// now print out x so the compiler doesn't just ignore it:
std::cout << "check: " << x << '\n',
// (omitted for clarity)
}
При сравнении тестов для нескольких разных алгоритмов это также может служить проверкой того, что каждый алгоритм выдает одинаковые результаты.