акробатика для программиста

КРИС КАСПЕРСКИ

Спецвыпуск: Хакер, номер #071, стр. 071-056-1

МОЩЬ И БЕСПОМОЩНОСТЬ АВТОМАТИЧЕСКОЙ ОПТИМИЗАЦИИ

К КОНЦУ 90-Х ГОДОВ КОМПИЛЯТОРЫ ПО СВОЕЙ ЭФФЕКТИВНОСТИ ВПЛОТНУЮ ПРИБЛИЗИЛИСЬ К АССЕМБЛЕРУ, ОДНАКО, ВСЕ ЕЩЕ СУЩЕСТВУЕТ МНОЖЕСТВО КОНСТРУКЦИЙ, НЕПОДДАЮЩИХСЯ АВТОМАТИЧЕСКОЙ ОПТИМИЗАЦИИ, НО ЛЕГКО ТРАНСФОРМИРУЕМЫХ ВРУЧНУЮ. ПОКАЖЕМ, КАК НАДО И КАК НЕ НАДО ОПТИМИЗИРОВАТЬ ПРОГРАММЫ НА ПРИМЕРЕ MICROSOFT VISUAL C++, INTEL C++, BORLAND BUILDER, GCC И HEWLETT-PACKARD C++

С точки зрения прикладного программиста, компилятор — это черный ящик, заглатывающий исходный текст и выплевывающий двоичный файл. Какие процессы протекают в его «пищеварительном тракте» — неизвестно. Разработчики компиляторов крайне поверхностно описывают механизмы оптимизации в прилагаемой документации, так и не давая ответа на вопрос: что именно оптимизирует компилятор, а что - нет.

Как следствие — одни разработчики пишут ужасно кривой код, надеясь, что все огрехи исправит компилятор (ведь он же «оптимизирующий!»). Другие же, наоборот, пытаются помочь компилятору, оптимизируя программу вручную и производя кучу глупых и ненужных действий, например, заменяя a = b/4 на a = b >> 2, хотя любой компилятор сделает это и сам. А вот поместить в регистр переменную, переданную по ссылке, он уже не решается (почему — см. «удаление лишних обращений к памяти»), то же самое относится и к выносу инвариантных функций из тела цикла.

Для достижения наивысшей эффективности необходимо помочь компилятору, придерживаясь определенных правил программирования (кстати говоря, не описанных в штатной документации). Если ты недоволен быстродействием откомпилированной программы, не спеши переписывать ее на ассемблер. Попробуй сначала оптимизировать код путем реконструкции исходного текста: в большинстве случаев получишь тот же самый результат, но потратишь меньше времени и сохранишь переносимость.

[что не надо оптимизировать.]

Начнем с того, что не надо оптимизировать, позволяя транслятору сделать это за нас (нехай делает). В частности, практически все оптимизирующие компиляторы умеют вычислять константы на стадии трансляции. В различных русскоязычных источниках этот прием оптимизации называется как «сверткой», так и «размножением» констант, что соответствует английским терминам «constant folding/propagation». Еще один английский термин из той же кучи - «constant elimination» (буквально — «изгнание констант»). Все это синонимы, и описывают они один и тот же механизм вычисления константных выражений (как целочисленных, так и вещественных), в результате чего a = 2 * 2 превращается в a = 4, а x = 4*y/2 - в x = 2*y.

Побочным эффектом оптимизации становится потеря переполнения (если таковое имело место быть). С точки зрения математика, выражения foo = bar/4*4 и foo = bar полностью эквивалентны, но если переменные foo и bar целые, то неоптимизированный вариант обнуляет два младших бита bar! Некоторые программисты умышленно используют этот прием, вместо того чтобы воспользоваться «foo = bar & (~3)», а потом ругаются на «глючный» оптимизатор!

За исключением Intel C++, все рассматриваемые компиляторы поддерживают «улучшенную свертку констант» («advanced constant folding/propagation»), заменяя все константные переменные их непосредственным значением, в результате чего выражение a = 2; b = 2 * a; c = b a; превращается в c = 2, а переменные a и b (если они нигде более не используются) уничтожаются.

Содержание  Вперед на стр. 071-056-2
ttfb: 3.3018589019775 ms