Я выбрал Дэвида, потому что он был единственным, кто представил решение для разницы в циклах без флагов оптимизации. Другие ответы показывают, что происходит при установке флажков оптимизации.
Ответ Джерри Коффина объясняет, что происходит при установке флажков оптимизации для этого примера. Остается без ответа, почему superCalculationA работает медленнее, чем superCalculationB, когда B выполняет одну дополнительную ссылку на память и одно дополнение для каждой итерации. Сообщение Nemo показывает выход ассемблера. Я подтвердил эту компиляцию с флагом -S
на моем ПК, Sandy Bridge 2,9 ГГц (i5-2310), запуском Ubuntu 12.04 64-бит, как это предлагает Matteo Italia.
Я экспериментировал с производительностью for-loops, когда я наткнулся на следующий случай.
У меня есть следующий код, который выполняет одно и то же вычисление двумя разными способами.
#include <cstdint>
#include <chrono>
#include <cstdio>
using std::uint64_t;
uint64_t superCalculationA(int init, int end)
{
uint64_t total = 0;
for (int i = init; i < end; i++)
total += i;
return total;
}
uint64_t superCalculationB(int init, int todo)
{
uint64_t total = 0;
for (int i = init; i < init + todo; i++)
total += i;
return total;
}
int main()
{
const uint64_t answer = 500000110500000000;
std::chrono::time_point<std::chrono::high_resolution_clock> start, end;
double elapsed;
std::printf("=====================================================\n");
start = std::chrono::high_resolution_clock::now();
uint64_t ret1 = superCalculationA(111, 1000000111);
end = std::chrono::high_resolution_clock::now();
elapsed = (end - start).count() * ((double) std::chrono::high_resolution_clock::period::num / std::chrono::high_resolution_clock::period::den);
std::printf("Elapsed time: %.3f s | %.3f ms | %.3f us\n", elapsed, 1e+3*elapsed, 1e+6*elapsed);
start = std::chrono::high_resolution_clock::now();
uint64_t ret2 = superCalculationB(111, 1000000000);
end = std::chrono::high_resolution_clock::now();
elapsed = (end - start).count() * ((double) std::chrono::high_resolution_clock::period::num / std::chrono::high_resolution_clock::period::den);
std::printf("Elapsed time: %.3f s | %.3f ms | %.3f us\n", elapsed, 1e+3*elapsed, 1e+6*elapsed);
if (ret1 == answer)
{
std::printf("The first method, i.e. superCalculationA, succeeded.\n");
}
if (ret2 == answer)
{
std::printf("The second method, i.e. superCalculationB, succeeded.\n");
}
std::printf("=====================================================\n");
return 0;
}
Компиляция этого кода с помощью
g++ main.cpp -o output --std = С++ 11
приводит к следующему результату:
=====================================================
Elapsed time: 2.859 s | 2859.441 ms | 2859440.968 us
Elapsed time: 2.204 s | 2204.059 ms | 2204059.262 us
The first method, i.e. superCalculationA, succeeded.
The second method, i.e. superCalculationB, succeeded.
=====================================================
Мой первый вопрос: почему второй цикл работает на 23% быстрее, чем первый?
С другой стороны, если я скомпилирую код с помощью
g++ main.cpp -o output --std = С++ 11 -O1
Результаты значительно улучшаются,
=====================================================
Elapsed time: 0.318 s | 317.773 ms | 317773.142 us
Elapsed time: 0.314 s | 314.429 ms | 314429.393 us
The first method, i.e. superCalculationA, succeeded.
The second method, i.e. superCalculationB, succeeded.
=====================================================
и разница во времени почти исчезает.
Но я не мог поверить своим глазам, когда я установил флаг -O2,
g++ main.cpp -o output --std = С++ 11 -O2
и получил следующее:
=====================================================
Elapsed time: 0.000 s | 0.000 ms | 0.328 us
Elapsed time: 0.000 s | 0.000 ms | 0.208 us
The first method, i.e. superCalculationA, succeeded.
The second method, i.e. superCalculationB, succeeded.
=====================================================
Итак, мой второй вопрос: Что делает компилятор, когда я устанавливаю флаги -O1 и -O2, что приводит к этому гигантскому повышению производительности?
Я проверил Оптимизированный вариант - с использованием коллекции компиляторов GNU (GCC), но это не разъясняло.
Кстати, я компилирую этот код с g++ (GCC) 4.9.1.
РЕДАКТИРОВАТЬ, чтобы подтвердить предположение Базиле Старинкевича
Я редактировал код, теперь main
выглядит так:
int main(int argc, char **argv)
{
int start = atoi(argv[1]);
int end = atoi(argv[2]);
int delta = end - start + 1;
std::chrono::time_point<std::chrono::high_resolution_clock> t_start, t_end;
double elapsed;
std::printf("=====================================================\n");
t_start = std::chrono::high_resolution_clock::now();
uint64_t ret1 = superCalculationB(start, delta);
t_end = std::chrono::high_resolution_clock::now();
elapsed = (t_end - t_start).count() * ((double) std::chrono::high_resolution_clock::period::num / std::chrono::high_resolution_clock::period::den);
std::printf("Elapsed time: %.3f s | %.3f ms | %.3f us\n", elapsed, 1e+3*elapsed, 1e+6*elapsed);
t_start = std::chrono::high_resolution_clock::now();
uint64_t ret2 = superCalculationA(start, end);
t_end = std::chrono::high_resolution_clock::now();
elapsed = (t_end - t_start).count() * ((double) std::chrono::high_resolution_clock::period::num / std::chrono::high_resolution_clock::period::den);
std::printf("Elapsed time: %.3f s | %.3f ms | %.3f us\n", elapsed, 1e+3*elapsed, 1e+6*elapsed);
std::printf("Results were %s\n", (ret1 == ret2) ? "the same!" : "different!");
std::printf("=====================================================\n");
return 0;
}
Эти изменения действительно увеличили время вычисления, как для -O1
, так и -O2
. Оба дают мне около 620 мс. Что доказывает, что -O2 действительно выполнял некоторые вычисления во время компиляции.
Я все еще не понимаю, что делают эти флаги для повышения производительности, а -Ofast
делает еще лучше, примерно на 320 мс.
Также обратите внимание, что я изменил порядок, в котором функции A и B вызываются для проверки предположения Джерри Коффина. Компиляция этого кода без флагов оптимизатора по-прежнему дает мне около 2.2 секунд в B и 2.8 секунды в A. Поэтому я полагаю, что это не кеш. Просто подтверждая, что я не говорю об оптимизации в первом случае (тот, у которого нет флагов), я просто хочу знать, что заставляет цикл секунд работать быстрее первого.