Проверьте свои линейки
Разумные физики, не желающие следовать ложными тропами и заходить в тупики, действуют согласно давнишних принципов: никогда не браться за длительные вычисления, пока не будет известен диапазон значений, в пределах которого может находиться правильный ответ (также важен диапазон, в пределах которого правильный ответ находиться не может).Ганс Христиан фон Байер, Решение Ферми
Люди в строительном магазине могли подумать, что мой отец свихнулся. Прежде чем купить строительную линейку, он сверял каждую со своей собственной, которую приносил с собой. Когда он обнаруживал расхождения вплоть до сантиметра на метр, это уже выглядело не так странно. Такие расхождения в строительном деле недопустимы. Еще хуже, когда вы вообще не в курсе существования такой проблемы.
Репутация средств для измерения настолько прочна, что мы используем синонимы линеек в качестве метафор для описания других стандартов, например, «мерило цивилизации». Весь смысл их существования сводится к тому, что они должны быть одинаковыми. Как же это могло произойти? Ну, производственный допуск на неточность. Линейки делаются по другим линейкам, которые в свою очередь делались по другим. Наследование ошибок. Измерения по своей сути сложнее, чем их представляют.
Главная причина ошибок в том, что никто эти «линейки» не проверяет. Программное обеспечение для проверки может иметь столько же ошибок, как и любая программа, которую мы напишем сами. Поэтому, таким программам нужно уделять больше внимания, чем обычно. Ошибки в пользовательском коде являются результатом неправильного опыта. Ошибки в программах для измерений — результат неправильных решений.
Вот простая подсказка для проверки: отрицательные числа практически всегда ошибочны. Еще один тип ошибки, который не был бы так смешон, если бы не встречался так часто, заключается в слепой записи процессорного или физического времени 1.3 миллиарда секунд. Также, можно поводить дополнительные проверки между измерениями. Процессорное время никогда не должно превышать физическое в однопоточных программах, а сумма времени всех компонентов не должна превышать общее время.
Тяжелее всего уловить отсутствующие точки измерения приборов. Вот где нам пригодится хорошо организованная база кода, обслуживающая всего несколько вызовов функций. Также вы можете использовать разные уровни измерительных режимов для сравнения их между собой. Скажем, DERP записал в лог 1:1, а DERP-DB — 1:100. Время в db_count
базы DERP должно совпадать с суммой образцов DERP-DB. Все тоже и для ошибок сумм db_wall_time
или db_bytes_in
. Если суммы не совпадают, возможно, вы что-то упустили.
Лучше всего записывать вещи разными способами, принося свою линейку в строительный магазин. Для программиста делать одну вещь двумя разными способами — двойная работа. Для экспериментатора — подтверждение из независимых источников. Чем больше способов у вас в распоряжении, чтоб назвать что-либо правдой, тем больше вероятности что это и есть правда. Второй лог не должен быть каким-то особенным. В вашей базе, скорее всего, куча разной статистики, которую можно периодически мониторить и сверять со своими логами.
Или почему бы ей не быть особенной. Долгое время Джордан Альперин не был удовлетворен логами ошибок, которые создавались некоторыми программами. Длинные простыни логов не содержали в себе никакой информации для лучшего их понимания, ни номеров строк, ни даже номеров ошибок. Некоторые ошибки записывались на пользовательском языке: «x is niet gedefinieerd» (прим. пер. — с голл. «х не определен»).
Джордан решил взглянуть туда, откуда растут ноги, во встроенное событие window.onerror
. Оно должно было выводить три вещи: сообщение об ошибке, URL адрес и номер строки. Он заподозрил, что не все люди следуют спецификациям, и переписал обработчик так, чтоб тот записывал все аргументы вслепую, без обработки. Оказалось, некоторые передавали просто один объект, другие только два аргумента, кто-то четыре. И, конечно, Джордан обнаружил недочеты в коде обработчика ошибок, который генерировал больше ошибок, чем было на самом деле, только засоряя лог.
Запуская обе схемы логов одновременно, он установил новый порог для вывода ошибок. Только после всего этого Джордан начал разбираться в самих ошибках, в том числе и в тех, которые до этого были незаметны.
Вдобавок к увлекательной возможности написания багов собственноручно, вас могут подвести также измерения, которые вы проводите. Возьмем, к примеру, процессорное время. Идея прозрачна: На мультизадачных компьютерах программа может быть запущена и отложена неоднократно в течение одной секунды, даже перенесена на другое ядро. Процессорное время определяет время в миллисекундах, потраченное на запуск инструкций программы на чипе.
Но все ли миллисекунды одинаковы? Процессор более не является прямолинейным как часы, если он вообще когда-либо был таковым. Как видно из HHVM, иногда (почти всегда) время тратится на ожидание данных из ОЗУ, а не на непосредственную работу процессора. И тут даже дело не в том, насколько ваша программа хорошо работает с кэшем. Сервер в 90% случаев ведет себя в реальной жизни не так как на свободной машине с кучей ресурсов. А что если у вас сразу несколько поколений серверов, некоторые 2 ГГц, другие 3 ГГц? Вы не можете просто усреднить их показатели. И, на случай, если вы захотите измерять процессорное время в секундах, новые чипы могут регулировать скорость течения своих часов в зависимости от среды.
Есть только один выход из этой западни — провести еще одни измерения вне зависимости от процессорного времени: посчитать процессорные инструкции. В принципе, подсчет инструкций менее подвержен зависимости от прогресса. Этот тип измерений не так просто получить, вам придется покопаться в драйверах для ядра и специальных чипов. И не все инструкции одинаковы. Но для оптимизации на уровне приложений оно того стоит.