Вынесение инвариантного кода может быть потенциальным источником ошибок. В цикле
int a[10], x, y;
for(i = 0; i < 10; i++)
if( y != 0 )
a[i] = x / y;
оптимизирующий компилятор может определить, что выражение x/y есть инвариант, и вынесет его за пределы цикла, игнорируя проверку на 0 и создавая возможность возникновения ситуации деления на 0.
Когда компилятор выполняет удаление переменных индукции цикла он может непреднамеренно породить ситуацию переполнения, потому что он может переструктурировать вычисления, включающие индексы цикла. В приведенном ранее примере, где выполняется оптимизация, используя вынесение инвариантного кода и удаление переменных индукции цикла, переменная индукции i была извлечена, в результате имеем:
T1 = j + k;
for(x = 0; x < T1 * v; x += T1);
В этом случае, поскольку значения j, k и v неизвестны, существует возможность переполнения для выражения T1 * v. Цикл может не закончиться.
Тестирование компиляторов
PC Tech Journal разработал тест оптимизации Си (см. листинг 1) как подспорье в оценке оптимизационных возможностей компиляторов Си. Тест проверяет степень оптимизации, проводимой компилятором. Для обеспечения основы для сравнения измерений времени выполнения для каждого компилятора запускался тест исполнения PC Tech Journal с ключами, разрешающими оптимизацию. Результаты его работы для каждого компилятора суммируются в таблице 1. Рисунок 6 демонстрирует опции оптимизации для каждого компилятора, которые использовались при компиляции обоих тестов. Характеристики выполнения программ можно сравнить с измерениями без оптимизации, приведенными в февральском номере за 1988 год (см. стр. 62 и 80).
Целью обоих тестов, исполнения и оптимизации, было получить наиболее быстрый код, который может дать каждый компилятор. Если компилятор предоставляет опции для генерации кода, они выбирались с приоритетом времени выполнения над размером программного кода, генерировались команды микропроцессора 80286 и непосредственные команды сопроцессора 80287, запрещалось проверять переполнение стека. Таким образом, минимальная конфигурация системы, требуемая для запуска тестов в том виде, в каком они компилировались, - машина с процессором 80286 и математическим сопроцессором 80287.
Многие компиляторы также имеют опции для генерации кода процессоров 80186 и NEC V20/V30, которые могут использоваться для машин класса XT (см. "Chips in transitions", Bob Smith, апрель 1986г., стр. 56). Эти процессоры имеют большинство средств 80286, исключая команды защищенного режима, так что сгенерированный для них код совпадает с кодом для 80286.
----------------------------------------------------¬
¦РИСУНОК 6: Командные строки ¦
+---------------------------------------------------+
¦ ¦
¦ BORLAND TURBO C 1.5 ¦
¦ : tcc -1 -f87 -N- -S -O -G -Z -r optbench.c ¦
¦ ¦
¦ COMPUTER INNOVATIONS C86PLUS 1.10 ¦
¦ : cc -DNO_ZERO_DIVIDE=1 -c -FPi87 -Oatx ¦
¦ -G2 -Fa optbench.c ¦
¦ ¦
¦ DATALIGHT OPTIMUM-C 3.14 ¦
¦ : dlc1 optbench.c -f-g ¦
¦ dlg optbench.tmp +vbe +all ¦
¦ dlc2 optbench.tmo ¦
¦ ¦
¦ LATTICE MS-DOS C 3.2 ¦
¦ : lc -d -k2 -f -v optbench.c ¦
¦ ¦
¦ MANX AZTEC C86 4.0 ¦
¦ : cc -A +A -B -T +F +2 +ef optbench.c ¦
¦ ¦
¦ METAWARE HIGH C 1.4 ¦
¦ : hc optbench.c -def NO_ZERO_DIVIDE=1 ¦
¦ pragma Off(Check_stack, Check_subscript) ¦
¦ pragma On(286, asm, auto_reg_alloc) ¦
¦ pragma On(floating_point, optimize_xjmp) ¦
¦ pragma On(optimize_xjmp_space, use_reg_vars) ¦
¦ ¦
¦ MICROSOFT C 5.0 ¦
¦ : cl -DNO_ZERO_DIVIDE=1 -c -G2 -Fc ¦
¦ -Ox optbench.c ¦
¦ ¦
¦ MICROSOFT QUICKC 1.0 ¦
¦ : qcl -c -G2 -FPi87 -Ox d:\optbench.c ¦
¦ ¦
¦ WATCOM C 6.0 ¦
¦ : wcc d:\optbench.c /d1 /oilt /s /2 /7 ¦
+---------------------------------------------------+
¦ Выполняемый код для тестов оптимизации и ¦
¦ исполнения, которые использованы в этой статье, ¦
¦ генерировался с помощью этих командных строк с ¦
¦ указанными директивами компиляторов. ¦
L----------------------------------------------------
Результаты теста исполнения для каждого компилятора в малой и большой моделях памяти приводятся в таблице 1. Тесты в наборе теста исполнения организованы в функции, которые вызывались из главной управляющей процедуры. Весь набор был скомпилирован и отредактирован в один файл EXE. Некоторые из процедур теста выполняются так быстро, что единственный вызов функции невозможно точно измерить. В этих случаях функции вызываются из управляющей процедуры многократно, чтобы увеличить время выполнения для получения возможности количественных измерений. В таблице 1 приводится количество итераций для каждого теста.