Это означает именно то, что написано на консервной банке, - это измерение производительности чего-то «маленького», например, системного вызова ядра операционной системы.
Опасность заключается в том, что люди могут использовать любые результаты, полученные в результате микробенчмаркинга, для диктовки оптимизаций. И как мы все знаем:
Мы должны забыть о малой эффективности, скажем, в 97% случаев: преждевременная оптимизация является корнем
все зло "- Дональд Кнут
Может быть много факторов, которые искажают результат микробенчмарков. Оптимизация компилятора является одним из них. Если измеряемая операция занимает так мало времени, что все, что вы используете для измерения, занимает больше времени, чем сама фактическая операция, ваши микробенчмарки также будут искажены.
Например, кто-то может взять микробенч служебных данных циклов for
:
void TestForLoop()
{
time start = GetTime();
for(int i = 0; i < 1000000000; ++i)
{
}
time elapsed = GetTime() - start;
time elapsedPerIteration = elapsed / 1000000000;
printf("Time elapsed for each iteration: %d\n", elapsedPerIteration);
}
Очевидно, компиляторы могут видеть, что цикл абсолютно ничего не делает, и вообще не генерировать никакого кода для цикла. Поэтому значения elapsed
и elapsedPerIteration
практически бесполезны.
Даже если цикл что-то делает:
void TestForLoop()
{
int sum = 0;
time start = GetTime();
for(int i = 0; i < 1000000000; ++i)
{
++sum;
}
time elapsed = GetTime() - start;
time elapsedPerIteration = elapsed / 1000000000;
printf("Time elapsed for each iteration: %d\n", elapsedPerIteration);
}
Компилятор может увидеть, что переменная sum
не будет использоваться ни для чего, и оптимизировать ее, и оптимизировать также цикл for. Но ждать! Что если мы сделаем это:
void TestForLoop()
{
int sum = 0;
time start = GetTime();
for(int i = 0; i < 1000000000; ++i)
{
++sum;
}
time elapsed = GetTime() - start;
time elapsedPerIteration = elapsed / 1000000000;
printf("Time elapsed for each iteration: %d\n", elapsedPerIteration);
printf("Sum: %d\n", sum); // Added
}
Компилятор может быть достаточно умен, чтобы понять, что sum
всегда будет постоянным значением, и оптимизировать все это. В наши дни многие удивятся оптимизирующим возможностям компиляторов.
Но как насчет вещей, которые компиляторы не могут оптимизировать?
void TestFileOpenPerformance()
{
FILE* file = NULL;
time start = GetTime();
for(int i = 0; i < 1000000000; ++i)
{
file = fopen("testfile.dat");
fclose(file);
}
time elapsed = GetTime() - start;
time elapsedPerIteration = elapsed / 1000000000;
printf("Time elapsed for each file open: %d\n", elapsedPerIteration);
}
Даже это не полезный тест! Операционная система может видеть, что файл открывается очень часто, поэтому она может предварительно загрузить его в память для повышения производительности. Практически все операционные системы делают это. То же самое происходит, когда вы открываете приложения - операционные системы могут определить ~ 5 самых популярных приложений, которые вы открываете больше всего, и предварительно загрузить код приложения в память при загрузке компьютера!
Фактически, существует множество переменных, которые вступают в игру: местность ссылок (например, массивы и связанные списки), влияние кэшей и пропускной способности памяти, включение компилятора, реализация компилятора, переключатели компилятора, количество ядер процессора, оптимизации в уровень процессора, планировщики операционной системы, фоновые процессы операционной системы и т. д.
Таким образом, микробенчмаркинг не совсем полезный показатель во многих случаях. Это определенно не заменяет целые тесты программы четко определенными тестовыми примерами (профилирование). Сначала напишите читаемый код, а затем профиль, чтобы увидеть, что нужно сделать, если таковые имеются.
Я хотел бы подчеркнуть, что микробенчмарки - это не зло как таковые , но их нужно использовать осторожно (это верно для многих других вещей, связанных с компьютерами)