Это совсем не тривиально. Нам всем нравится, когда наше программное обеспечение работает быстро, мы любим спорить о том, как заставить его работать быстрее, мы любим писать высокопроизводительный код и хвастаться этим. К сожалению, это скорее веселое социальное занятие, чем инженерное дело.

Дело в том, что производительность не является неотъемлемым свойством кода. Все гораздо сложнее. Рассмотрим коренные деревья. Чем больше основание — тем короче ключ, следовательно, меньше операций чтения, верно? Правильно. Но увеличение основания также сильно увеличивает размер всей структуры. В какой-то момент он больше не может эффективно использовать кеш, и из-за этого меньшее количество операций занимает просто больше времени на каждую.

Ситуация усугубляется, учитывая, что разное оборудование имеет разный размер кеша и даже разные уровни кеша. Все ваши обоснования, подкрепленные подробными измерениями, в пользу использования «лучшей» системы счисления для конкретного алгоритма исчезают через пару лет только потому, что ваши клиенты регулярно обновляют свое оборудование.

Но это не значит, что вы не должны измерять. Вам следует. На самом деле, у вас просто нет лучших альтернатив.

Вы, конечно, можете просто предположить, что какой-то фрагмент кода должен работать быстрее, чем другой, но в большинстве случаев это будет не более чем самообман. Да, это самый простой способ, но это не значит, что он самый дешевый. Все неверные решения, принятые исключительно на основе предположений, обходятся отрасли примерно в 1,5 миллиарда долларов в год* только за счет затрат на техническое обслуживание.

Что мы тогда измеряем?

Время от времени я получаю вопросы, заслуживающие судебного разбирательства. Последний был о стандартных объектах функций C++. Допустим, мы решили использовать один из них в качестве обратного вызова, который может быть определен или не определен во время выполнения. Каждый раз, когда мы хотим вызвать эту функцию, мы должны проверять, существует ли она. Но не будет ли эффективнее предопределить какую-нибудь пустую лямбду по умолчанию и запускать обратный вызов каждый раз, когда мы захотим, независимо от того, было ли оно преднамеренно переопределено или нет?

Что ж, давайте узнаем.

Компилятор C++ по умолчанию для моего дистрибутива Linux — g++ 5.4.0. Это довольно сложная вещь, состоящая из миллионов строк исходного кода. Он разработан, чтобы сделать мой код максимально эффективным, и мы также хотим принять это во внимание.

Обратная сторона этой возможности оптимизации заключается в том, что мы должны очень тщательно планировать наш эксперимент. Если мы позволим вводу быть статическим, то он может просто предварительно вычислить части нашего алгоритма во время компиляции. Тогда часть его не будет отображаться на измерении. И если мы сделаем наш вывод нерелевантным, он может вообще не выполнять вычисления. Если нас не волнует результат, то ничего не делать — лучший способ все сделать.

Я придумал следующие фрагменты кода. Один для проверки во время выполнения:

#include <cstdlib>
#include <functional>
#include <iostream>
#include <limits>

int main(int argc,  char** argv){
	std::function<void(int)> check;
	if( argc == 2 ){
		int check_against = atoi(argv[1]);
		check = [=](int number) -> void {
			if( check_against == number )
				std::cout << number;
		};
	}
	for(int i = 0; i < std::numeric_limits<int>::max(); ++i)
		if(check)
			check(i);
}

И один для предопределенного функционального объекта:

#include <cstdlib>
#include <functional>
#include <iostream>
#include <limits>

int main(int argc,  char** argv){
	std::function<void(int)> check = [](int){};
	if( argc == 2 ){
		int check_against = atoi(argv[1]);
		check = [=](int number) -> void {
			if( check_against == number )
				std::cout << number;
		};
	}
	for(int i = 0; i < std::numeric_limits<int>::max(); ++i)
		check(i);
}

Мы получаем возможность измерить каждый из них в двух сценариях: один, когда check установлен и текстовая программа снабжена аргументом и, и другой — когда это не так.

C++ имеет достаточно опций для тестирования нашего кода. Но так как мы хотим исследовать Linux, мы начнем с time.

Он просто запускает программу, а затем записывает, сколько времени требуется для запуска. Он учитывает системное время так же, как и время пользователя. В Linux системное время — это время, которое наша программа проводит в ядре системы, и время пользователя — в пользовательском пространстве, где выполняются все вычисления, специфичные для задачи. Поскольку мы особо ничего особо из ядра не вызываем, системное время для нашей программы близко к 0.

Теперь, конечно, для правильной оценки результатов нам пришлось бы провести некоторый статистический анализ, чтобы исключить стохастическую ошибку. Но это конкретное измерение в любом случае очень грубое. Это не дает нам надежного эталона, но в любом случае может дать некоторую интересную информацию.

Вот что я получил на своем ПК.

okaleniuk@bbn:~/performance_experiments$ time ./fun_check
real 0m2.092s
user 0m2.088s
sys 0m0.000s
okaleniuk@bbn:~/performance_experiments$ time ./fun_default
real 0m7.034s
user 0m7.032s
sys 0m0.000s
okaleniuk@bbn:~/performance_experiments$ time ./fun_check -1
real 0m7.023s
user 0m7.020s
sys 0m0.004s
okaleniuk@bbn:~/performance_experiments$ time ./fun_default -1
real 0m6.992s
user 0m6.988s
sys 0m0.004s

В заключении

Можно сделать вывод, что когда функция не установлена, то бесспорно выигрывает вариант проверки. И когда это так, то значение по умолчанию кажется немного лучше. Мы можем закончить эту другую часть, но мы не должны! Мы измерили это только на моей машине, с моим компилятором и с помощью грубого инструмента. Лучший вывод, который мы можем сделать из этого эксперимента, заключается в том, что нам нужно больше данных.

И мы получим некоторые во второй части.

  • — Я полностью «предполагаю» это число. Вы видите, как это просто и бесполезно.

Hacker Noon — это то, как хакеры начинают свой день. Мы являемся частью семьи @AMI. Сейчас мы принимаем заявки и рады обсудить рекламные и спонсорские возможности.

Чтобы узнать больше, прочитайте нашу страницу о нас, лайкните/отправьте нам сообщение на Facebook или просто твитните/DM @HackerNoon.

Если вам понравилась эта история, мы рекомендуем прочитать наши последние технические истории и актуальные технические истории. До следующего раза, не принимайте реалии мира как должное!