Многое можно было сделать лучше, например:
Это не оптимизация, а всего лишь небольшие усовершенствования простого генератора кода. Нельзя сказать, что все это дает очень много, но кое-что дает.
Ниже приведены результаты некоторых тестов, включающих компиляцию программы, сжатие данных методом LZSS (без использования индекса или хеш-таблицы) и расчет хода в игре ним (перебором). Тесты не претендуют на полноту, но они дают некоторое представление о качестве компилятора и позволили его улучшить. Кроме того, они были выполнены почти на всех процессорах, когда-либо использовавшихся в IBM PC и могут быть интересны уже этим.
Почти во всех тестах время измерялось по таймеру машины, соответственно, погрешность измерения не превосходит 0.11 сек, как правило это меньше случайной погрешности. Каких-либо мер для устранения последней не предпринималось.
Использовались следующие компиляторы:
MIX | Context.pas |
CTX | Context.ctx |
OPT | Context.opt |
TP1 | Turbo Pascal 1.00A |
TP3 | Turbo Pascal 3.02A |
BP7 | Borland Pascal 7.0 |
BC3 | Borland C++ 3.1 |
Context.pas - первая версии context'а, написанная осенью 1994 года. Так как его входной язык очень ограничен, компилятор участвует лишь в одном тесте (и демонстрирует худший результат).
Ранние версии Turbo Pascal взяты в музее фирмы Borland, который находится на сайте community.borland.com. Наверное, наибольший интерес представляет Turbo Pascal 1.0. Третью версию я видел в 1989 году, а первую увидел лишь недавно. Это совсем маленькая программа turbo.com, включающая редактор текста, собственно компилятор и небольшую библиотеку функций. Все это как-то умещается в файле размером 33280 байт и может работать при наличии менее 64K памяти.
Тесты выполнялись на различных ПЭВМ с процессорами от 286 до Pentium 4 и Athlon XP. На этих машинах были установлены разные ОС, поэтому условия выполнения тестов не совсем одинаковы. На 286/12 установлена MS-DOS 3.30 (без smartdrv), на 386/40 - MS-DOS 6.22 (также без smartdrv), на остальных машинах - одна из систем Windows - Windows 95, Windows 98, Windows NT 4.0, Windows 2000 или Windows XP. Согласно SysInfo (Norton Utilities 8) относительное быстродействие процессора 286/12 - 9.2 (цифра странная, эта машина примерно в шесть раз быстрее IBM PC, но за неимением последней проверить это нельзя). Некоторые результаты трудно объяснить, но большая часть результатов соответствует ожиданиям.
Для этого теста использовалась промежуточная версия компилятора context.tmp (99832 байта, 4628 строк). Это немного уменьшеный context.opt, сделанный только из-за имевшей место ошибки в ассемблере, который выдавал лишние префиксы замены сегмента, из-за этого код самого context.opt не умещался в 64 килобайта.
Длины исполняемых модулей context.ctx/context.cpp, байт:
CTX | OPT | CPP |
61438 | 52017 | 56464 |
При компиляции программы context.cpp была задана оптимизация по скорости. При оптимизации по размеру кода получается файл размером 42048 байт и скорость его работы не ухудшается.
Время компиляции программы context.tmp, сек:
CTX | OPT | BC3 | |
Celeron/1400 | 0.7 | 0.6 | 0.5 |
P4/2400/533 | 0.8 | 0.7 | 0.5 |
Celeron/1000 | 1.0 | 1.0 | 0.5 |
Celeron/2000 | 0.8 | 0.7 | 0.6 |
Athlon XP/2000+/1667 | 0.5 | 0.4 | 0.7 |
Athlon/900 | 1.0 | 0.9 | 0.7 |
Duron/600* | 1.2 | 1.0 | 0.8 |
P4/1500 | 2.8 | 2.6 | 0.9 |
Celeron/667 | 3.6 | 3.4 | 1.0 |
Celeron/433 | 2.0 | 1.9 | 1.1 |
PII/350 | 1.6 | 1.5 | 1.3 |
MII-PR233/187 | 2.9 | 2.5 | 1.4 |
PII/233 | 2.4 | 2.2 | 1.7 |
K6/300 | 7.2 | 7.0 | 1.8 |
K6/200 | 3.0 | 2.6 | 1.9 |
P/200MMX | 2.6 | 2.2 | 2.1 |
Celeron/266 | 2.6 | 2.3 | 2.8 |
P/133 | 4.0 | 3.4 | 3.0 |
Am486DX/66 | 28.7 | 26.8 | 10.2 |
Am386DX/40 | 42.5 | 35.1 | 42.2 |
286/12 | 197.6 | 171.1 | 182.0 |
Сравнение с Borland C++ не вполне корректно, т.к. компилятор context.cpp не эквивалентен context.tmp. Вообще, тест неудачен - в нем очень большую роль играет вывод на экран, особенно это заметно на Am486DX/66 с ISA VGA. Но с другой стороны - есть некая программа и мы не вдаваясь в особенности реализации определяем скорость ее работы на разных машинах. Результаты, отмеченные звездочкой немного занижены из-за упомянутой ошибки в ассемблере. Результат K6/300 совершенно непонятен.
Время компиляции просто огромно - для сравнения Borland Pascal 7.0 на 286/12 компилирует программу context.pas (~2400 строк текста) за пять с небольшим секунд. Конечно, на современных машинах это не заметно, но на 286 и, тем более, 8088 скорость компилятора сложно было игнорировать. На той же 286/12 Borland C++ 2.0 тратил на полную компиляцию прототипа edit64k (всего 400 строк) примерно 45 секунд.
Столь плохой результат не связан с алгоритмом компиляции - большую часть времени компилятор тратит на выполнение второстепенных действий. Да и объем ассемблерного листинга много больше объема машинного кода, что тоже отрицательно влияет на скорость. Сделав три незначтельных изменения
Дальнейшее повышение быстродействия может быть достигнуто за счет реализации быстрого поиска в таблицах компилятора и прямой генерации машиного кода. Последнее стоило сделать сразу, но тогда я решил, что генерация ассемблерного листинга проще. Это не совсем верно, но что сделано, то сделано...
Для следующего теста использовался построенный на иснове context.min прямой генератор кода. Длины исполняемых модулей, байт:
CTX | OPT |
34711 | 27064 |
Время компиляции context.min (63838 байт, 3549 строк) и context.pas (<=65001 байт, <=2395 строк - эти цифры немного различны для разных версий Turbo Pascal), сек:
CMP | CTX | OPT | TP1 | TP3 | BP7 | |
P4/2400/533 | 0.45 | 0.02 | 0.02 | н/д | н/д | 0.02 |
Celeron/2000 | 0.46 | 0.02 | 0.02 | н/д | н/д | 0.02 |
Athlon XP/2000+/1667 | 3.30 | 0.03 | 0.03 | н/д | н/д | 0.02 |
Celeron/1400 | 0.38 | 0.03 | 0.03 | н/д | н/д | 0.03 |
Celeron/1000 | н/д | 0.04 | 0.03 | н/д | н/д | 0.03 |
P4/1500 | 1.60 | 0.05 | 0.04 | н/д | н/д | 0.03 |
Athlon/900 | 0.64 | 0.06 | 0.04 | н/д | н/д | 0.04 |
Celeron/667 | 1.90 | 0.09 | 0.07 | н/д | н/д | 0.05 |
PII/350 | 1.01 | 0.08 | 0.07 | н/д | н/д | 0.05 |
Celeron/433 | 1.20 | 0.10 | 0.08 | н/д | н/д | 0.06 |
PII/233 | 1.49 | 0.14 | 0.11 | н/д | н/д | 0.07 |
K6/300 | 4.08 | 0.12 | 0.10 | н/д | н/д | 0.09 |
K6/200 | 1.75 | 0.12 | 0.09 | н/д | н/д | 0.10 |
Celeron/266 | 2.05 | 0.13 | 0.09 | н/д | н/д | 0.13 |
MII-PR233/187 | 1.66 | 0.15 | 0.13 | н/д | н/д | 0.10 |
P/200MMX | 1.67 | 0.18 | 0.13 | н/д | н/д | 0.13 |
P/133 | 2.61 | 0.22 | 0.16 | н/д | н/д | 0.18 |
Am486DX/66 | 15.73 | 0.78 | 0.62 | 0.80 | н/д | 0.41 |
Am386DX/40 | 26.24 | 2.23 | 1.65 | 2.50 | 1.40 | 1.33 |
286/12 | 119.34 | 9.48 | 7.28 | 9.50 | 5.50 | 5.32 |
В первой колонке (CMP) для сравнения приведено время работы компилятора CTX из предыдущего теста. Время выполнения тестов TP1 и TP3 измерялось c меньшей точностью, чем указано в таблице, лишний ноль приписан только для выравнивания чисел. В остальных случаях погрешность измерения времени не боле 0.01 секунды (тест выполнялся десять раз подряд, измерялось общее время). Скорость Turbo Pascal не достигнута, но результат сопоставим и разницу уже можно списать на недостаточную эффективность самого компилятора. К тому же есть основания полагать, что все версии Turbo Pascal написаны на ассемблере.
Компилятор context.pas не эквивалентен context.min (последний чуть меньше, но содержит на треть больше лексем), специально писать большие одинаковые программы на разных языках нет возможности...
Компиляция context.202 для Windows с помощью context.201 (DOS) и context.202 (Win32), сек:
201 | 202 | |
P4/2400/533 | 0.07 | 0.04 |
Athlon XP/2000+/1667 | 0.08 | 0.04 |
Celeron/2000 | 0.08 | 0.05 |
Celeron/1400 | 0.09 | 0.07 |
Celeron/667 | 0.20 | 0.41 |
PII/350 | 0.32 | 0.25 |
K6/200 | 0.46 | 0.35 |
Celeron/266 | 0.43 | 0.37 |
P/133 | 0.83 | 0.69 |
Am486DX/66 | 2.68 | 2.42 |
286/12 | 38.11 | н/д |
Этот тест также более корректный, поскольку context.201 выполняет только последовательное чтение и последовательную запись файла. На верхнем уровне погрешность измерения 0.01 секунды слишком велика, выполнение теста сто раз подряд дало следующие результаты:
201 | 202 | |
P4/2400/533 | 0.066 | 0.036 |
Celeron/2000 | 0.083 | 0.052 |
Для этого теста программа arcdemo.ctx была переведена на Pascal и С++. Длины исполняемых модулей arcdemo.ctx/arcdemo.pas/arcdemo.cpp следующие, байт:
CTX | OPT | BP7 | CPP |
4560 | 3495 | 3888 | 8638 |
Программы, созданные с помощью компиляторов Turbo Pascal и C++ не содержат функции раскрытия архива, полные программы были бы несколько больше. Длины исполняемых модулей, созданных ранними версиями Turbo Pascal не приводятся, т.к. они включают полную библиотеку функций.
Время сжатия файла context.tmp (99832 байт) с помощью программы arcdemo, сек:
CTX | OPT | TP1 | TP3 | BP7 | BC3 | |
P4/2400/533 | 3.4 | 1.3 | 5.7 | 2.5 | 1.5 | 0.6 |
Celeron/2000 | 4.1 | 1.6 | 6.8 | 3.0 | 1.9 | 0.7 |
Athlon XP/2000+/1667 | 3.7 | 1.9 | 3.7 | 3.1 | 2.4 | 0.9 |
Celeron/1400 | 2.7 | 1.4 | 6.4 | 2.5 | 1.6 | 1.0 |
P4/1500 | 5.5 | 2.3 | 15.1 | 4.6 | 2.5 | 1.0 |
Celeron/1000 | 3.8 | 2.0 | 9.4 | 3.5 | 2.3 | 1.5 |
Athlon/900 | 7.2 | 3.5 | 6.7 | 5.5 | 5.1 | 1.8 |
Celeron/667 | 5.7 | 3.0 | 7.3 | 5.3 | 3.5 | 2.2 |
Duron/600* | 9.8 | 4.9 | н/д | 7.5 | 6.9 | 2.5 |
Celeron/433 | 8.7 | 4.6 | 21.7 | 7.8 | 5.3 | 3.2 |
PII/350 | 10.9 | 5.8 | 26.9 | 9.6 | 6.6 | 4.0 |
Celeron/266 | 14.3 | 7.7 | 18.2 | 12.8 | 8.7 | 5.4 |
K6/300 | 17.0 | 8.1 | 14.2 | 12.8 | 9.3 | 5.4 |
PII/233 | 15.9 | 8.5 | 39.8 | 14.2 | 9.6 | 5.9 |
K6/200 | 26.5 | 13.5 | 21.3 | 19.3 | 14.1 | 8.1 |
MII-PR233/187 | 24.9 | 12.2 | 24.5 | 22.6 | 12.2 | 9.5 |
P/200MMX | 27.2 | 13.5 | 35.7 | 22.9 | 16.5 | 11.3 |
P/133 | 37.1 | 20.6 | 54.4 | 36.1 | 25.5 | 17.8 |
Am486DX/66 | 144.0 | 78.5 | 180.9 | 137.7 | 81.4 | 56.5 |
Am386DX/40 | 592.6 | 356.6 | 631.1 | 503.1 | 347.2 | 230.9 |
286/12 | 2413.3 | 1528.4 | 2251.4 | 2087.5 | 1406.5 | 942.1 |
Трудно объяснить результаты, показанные Turbo Pascal - созданный им код плохо подходит для новых процессоров (Context'а это тоже касается). Этот тест также имеет недостаток - в цикле поиска совпадений есть лишние действия.
MIX | CTX | OPT | TP1 | TP3 | BP7 | BC3 | |
Athlon XP/2000+/1667 | 5.2 | 4.4 | 2.7 | 5.9 | 4.0 | 3.0 | 1.4 |
P4/2400/533 | 3.7 | 4.1 | 2.5 | 15.7 | 4.9 | 2.6 | 1.5 |
Celeron/1400 | 3.9 | 3.8 | 2.7 | 6.0 | 3.8 | 2.7 | 1.8 |
Celeron/2000 | 4.4 | 4.9 | 2.9 | 18.8 | 5.9 | 3.1 | 1.8 |
Celeron/1000 | 10.3 | 5.3 | 4.1 | 8.1 | 5.2 | 3.8 | 2.4 |
P4/1500 | 6.3 | 7.1 | 4.2 | 32.7 | 8.1 | 4.3 | 2.4 |
Athlon/900 | 9.6 | 8.2 | 5.5 | 10.9 | 7.2 | 5.5 | 2.5 |
Celeron/667 | 15.4 | 8.0 | 6.0 | 12.1 | 7.7 | 5.7 | 3.7 |
Celeron/433 | 23.7 | 12.2 | 9.2 | 18.7 | 11.8 | 8.7 | 5.7 |
K6/300 | 21.2 | 18.9 | 11.0 | 19.6 | 15.5 | 10.5 | 6.9 |
PII/350 | 29.4 | 15.3 | 11.5 | 23.2 | 14.6 | 10.8 | 6.9 |
Celeron/266 | 38.6 | 20.1 | 15.1 | 30.3 | 19.2 | 14.2 | 9.2 |
PII/233 | 43.2 | 22.5 | 16.9 | 33.9 | 21.4 | 15.9 | 10.2 |
K6/200 | 49.0 | 28.6 | 19.1 | 29.2 | 23.6 | 16.0 | 10.3 |
P/200MMX* | 30.7 | 29.1 | 18.6 | 47.2 | 26.5 | 17.4 | 11.2 |
MII-PR233/187 | 60.2 | 31.1 | 20.1 | 34.5 | 27.5 | 18.6 | 12.8 |
P/133 | 102.5 | 50.6 | 33.0 | 73.3 | 44.2 | 32.7 | 18.9 |
Am486DX/66 | 293.0 | 158.9 | 108.3 | 204.2 | 136.2 | 104.9 | 57.5 |
Am386DX/40 | 922.5 | 563.0 | 378.0 | 736.6 | 490.4 | 326.1 | 204.9 |
286/12 | 3870.9 | 2317.0 | 1655.0 | 2688.7 | 2101.7 | 1415.6 | 885.0 |
В первой колонке (MIX) приведено время работы программы, скомпилированой посредством context 0.5.
Тот же тест, но использовались компиляторы Context 1.4, Context 2.04 (в версии 2.02 нет поддержки целых со знаком), Context 2.07 и MS Visual C++ 4.2 (странно, но созданный им код оказался лучше, чем код MSVC 6.0, особенно это заметно на K6):
140 | 204 | 207 | VC4 | |
P4/2400/533 | 1.3 | 1.5 | 1.1 | 0.7 |
Celeron/2000 | 1.5 | 1.7 | 1.3 | 0.9 |
Athlon/900 | н/д | н/д | 3.0 | 1.6 |
Celeron/667 | 5.4 | 5.3 | 4.6 | 2.6 |
PII/350 | 9.9 | 9.9 | 7.9 | 4.8 |
Celeron/266 | 13.1 | 12.9 | 10.5 | 6.3 |
K6/200 | 17.2 | 16.8 | 14.4 | 6.5 |
P/133 | 35.0 | 40.7 | 33.7 | 13.0 |
Am486DX/66 | 108.1 | 130.8 | 100.0 | 52.1 |
Видно, что 32-х разрядный код выполняется заметно быстрее 16-и разрядного, причем разница может быть значительна. По-видимому, лишь результат Сontext 1.4 можно прямо сопоставлять с результатом 16-и разрядного теста (Context 1.3/OPT), поскольку в них используются сходные методы генерации кода.
Тесты не полны, поэтому сделанные из них выводы могут быть не вполне справедливы, но все же:
Для Pentium 4 очень важно выравнивание кода, более важно чем для предшествующих процессоров.
На основе полученных результатов был построен график зависимости быстродействия процессоров от времени. Использовались результаты из последних колонок (Borland C++ 3.1). Результаты привязаны к датам выпуска микропроцессоров а не машин, относительное быстродействие 286/12 принято равным шести, для получения быстродействия первого процессора каждого семейства (8086, 80286 и т.д.) результаты экстраполировались (исключение - Athlon, его быстродействие приведено к 1000 МГц):
За двадцать лет рост примерно в тысячу раз (1.4 раза в год), реальный рост несколько больше, т.к. 32-х разрядный код выполняеся бысрее 16-разрядного. Но полное представление о росте быстродействия может дать только 16-разрядный код, поскольку на старых машинах может быть выполнен только он. Выполнить тесты на 8086 к сожалению пока нет возможности.
Это может показаться удивительным, но во время выполнения тестов были обнаружены неожиданные ошибки в MS-DOS и Turbo Pascal.
При запуске следующего командного файла
в MS-DOS 3.30 два раза выполняется программа n0.com (и перестановка строк ничего не меняет).
Turbo Pascal 1.0 неправильно компилирует программу Pascal-S (маленький компилятор подмножества Pascal и интерпретатор P-кода, первоначально использовался вместо context.pas). Ошибка связана с обработкой массивов строк:
Вопрос лишь в том, ошибки это или так было задумано? Да, видно программ без ошибок не бывает...