Минимизация и невидимость 31_ Результат — мир стал прорисовываться на 10% быстрей, но тормозить не перестал. И тут я увидел самое слабое место — вывод на экран. Мой движок просчитывал сцены достаточно быстро, а пробоиной был именно вывод изображения. Тогда еще не было шины AGP, и я использовал про- стую PCI-видеокарту от S3 с 1 Мбайтом памяти. Пара часов колдовства, и я выжал из PCI все возможное. Откомпилировав движок, я снова загру- зился в свой виртуальный мир. Одно нажатие клавиши "вперед", и я очутил- ся у противоположной стены. Никаких тормозов, сумасшедшая скорость просчета и моментальный вывод на экран. Как видите, моя ошибка была в том, что вначале я неправильно определил слабое место своего движка. Я месяц потратил месяц на оптимизацию мате- матики, и что в результате? Мизерные 10% прироста в производительности. Но когда я реально нашел слабое звено, то смог повысить производитель- ность в несколько раз. Именно поэтому я говорю, что надо начинать оптимизировать только со слабых мест. Если вы ускорите работу самого слабого звена вашей програм- мы, то, может быть, и не понадобится ускорять другие места. Вы можете потратить дни на оптимизацию сильных сторон и увеличить производитель- ность только на 10% (что может оказаться недостаточным), или несколько часов на улучшение слабой части, и получить улучшение в 10 раз! Слабые места компьютера Меня поражают люди, которые гонятся за мегагерцами процессора и сидят на доисторической видеокарте от S3, жестком диске на 5400 оборотов и с 32 Мбайтами памяти. Посмотрите в корпус своего компьютера и оцени- те его содержимое. Если вы увидели, что памяти у вас не более 64 Мбайт, то встаньте и громко произнесите: "Уважаемый DIMM, команда выбрала вас. Вы сегодня — самое слабое звено, и должны покинуть мой компьютер". После этого покупаете себе 128, а лучше 256, а еще лучше 512 Мбайт памяти и наслаждаетесь ускорением работы Delphi, Photoshop и других "тяжелых" программ. В данном случае наращивание сотни мегагерц у процессора даст более ма- ленький прирост в скорости. Если вы используете тяжелые приложения при нехватке памяти, то процессор начинает тратить слишком много времени на загрузку и выгрузку данных. Ну а если в вашем компьютере достаточно опе- ративной памяти, то процессор уже занимается только расчетами и не рас- ходуется по лишним загрузкам-выгрузкам. То же самое с видеоадаптером. Если видеокарта у вас слабенькая, то процессор будет просчитывать сцены быстрей, чем они будут выводиться на экран. А это грозит простоями и минимальным приростом производительности. 32 Глава 1 ЗАКОН № 3 Следующим шагом вы должны разобрать все операции по косточкам и выяс- нить, где происходят регулярно повторяющиеся операции. Начинать оптимиза- цию нужно именно с них. Опять начнем рассмотрение этого закона с программирования. Допустим, что у вас есть следующий код (приведена просто логика, а не реальная программа): 1. А:=А*2; 2. Б:=1| 3. Х:=Х+М[Б]; 4. Б:=Б+1; 5. Если Б<100 то перейти на шаг 3. Любой программист скажет, что здесь слабым местом является первая стро- ка, потому что там используется умножение. Это действительно так. Умно- жение всегда выполняется дольше, и если заменить его на сложение (А:=А+А) ИЛИ еще лучше на сдвиг, то вы выиграете пару тактов процессорного времени. Но только пару тактов, и для процессора это будет незаметно. Теперь посмотри еще раз на наш код. Больше ничего не видишь? А я вижу. В этом коде используется цикл: "Пока Б<100, будет выполняться операция Х:=Х+М[Б|". Это значит, что процессору придется выполнить 100 переходов с шага 5 на шаг 3. А это уже немало. Как можно здесь что-то оптимизиро- вать? Очень легко. Здесь у нас внутри цикла выполняется две строки: 3 и 4. А что, если мы внутри цикла размножим их 2 раза: 1. Б:=1| 2. Х:=Х+М[Б]; 3. Б:=Б+1; 4. Х:=Х+М[Б]; 5. Б:=Б+1; 6. Если Б<50 то перейти на шаг 3; Здесь я разложил цикл на более маленький. Вторую и третью операцию я повторил два раза. Это значит, что за один проход цикла я выполню два раза строки 3 и 4, и только после этого перейду на строку 3, чтобы повто- рить операцию. Такой цикл уже нужно повторить только 50 раз (потому что за один раз выполняется два действия). Это значит, что я сэкономил 50 опе- раций переходов. Неплохо? А это уже несколько сотен тактов процессор- ного времени. Минимизация и невидимость 33 А что, если внутри цикла написать строки 2 и 3 десять раз? Это значит, что за один проход цикла строки 2 и 3 будут вычисляться 10 раз, и мне понадо- бится повторить такой цикл только 10 раз, чтобы получить в результате 100. А это уже экономия 90 операций переходов. Недостаток этого подхода — увеличился код моей программы, зато повыси- лась скорость, и очень значительно. Этот подход очень хорош, но им не стоит злоупотреблять. С одной стороны, увеличивается скорость, а с другой — увеличивается размер. А большой размер — это враг любой программы. В любом деле главное — разумная достаточность. Чем больше вы увеличи- ваете код ради оптимизации скорости, тем меньше результирующий эффект от оптимизации. В жизни таких примеров намного больше. Любую циклическую операцию можно оптимизировать. Хотите пример? Пожалуйста. Допустим, у вашего провайдера Интернета есть несколько телефонов доступа. Вы каждый день перезваниваете на каждый из них в надежде найти свободный. Начинающий тут же скажет, что провайдер обязан оптимизировать свои пулы модемов в один, чтобы не надо было трезвонить по всем номерам сразу. Но опытный пользователь должен знать, что не у каждого пользователя хорошая связь с любой телефонной станцией города. Поэтому провайдеры держат пулы на разных станциях, чтобы вы могли выбрать тот, с которым связь лучше. По- ставьте программу-дозвонщик (таких сейчас полно в Интернете), которая сама будет перебирать номера телефонов. А теперь другой пример — вам на 1 час досталась карточка какого-то нового провайдера. Заносить ее в программу дозвона не имеет смысла, потому что вы можете больше никогда не позвонить ему. Из-за этой одноразовой опе- рации вам придется перенастраивать свой дозвонщик на нового провайдера и потом обратно, а выигрыш практически нулевой, потому что пока вы ме- няете настройки, уже можно было дозвониться стандартными средствами Windows. Отсюда сразу же напрашивается вывод — нужно правильно выби- рать средства для выполнения необходимых задач. ЗАКОН № 4 (Этот закон — расширение предыдущего.) Оптимизировать одноразовые операции — это только потеря времени. Сто раз подумай, прежде чем начать мучиться с редкими операциями. Полгодика назад я прочитал рассказ в Интернете "Записки жены програм- миста" (http://www.exler.ru/novels/wife.htm). Очень даже некислый и жиз- ненный рассказ. Когда я его читал, у меня было ощущение, что его написа- ла моя жена. Слава "Красной Шапочке", что она на такую подлость не способна. Так вот там была такая ситуация. Очаровательная девушка выходит замуж за программиста, и им надо разо- слать приглашения на свадьбу. Вместо того чтобы набрать их на печатной 34 Глава 1 машинке, программист кричит, что он крутой, и пишет специальную про- грамму. Ее написание заняло один день, и столько же — ее отладка. Главная ошибка — неправильная оптимизация своего труда. Легче набрать шаблон в любом текстовом редакторе и потом только менять фамилии приглашенных на этот траурный день (это я сужу по себе). Но даже если нет текстового редактора, писать программу действительно нет смысла. За- траты большие, а пользоваться ей будешь только один раз. Здесь действи- тельно легче будет даже набрать на печатной машинке. Получается, что одноразовые операции оптимизировать просто бессмыслен- но. Затраты в этом случае себя не окупают, поэтому не стоит тратить свои нервы на этот бессмысленный труд. В самом начале этого раздела я раскритиковал вас как человека, который ленится хоть что-нибудь делать. Так вот именно здесь вы можете проявлять свою врожденную леность в полном объеме. В данном случае крутым счита- ется не тот, кто целый день промучился и ничего не добился, а тот, кто вы- полнил свою работу наиболее быстро и эффективно. И эти две вещи путать нельзя. ЗАКОН № 5 Нужно знать внутренности компьютера и принципы его работы. Чем лучше вы знаете, каким образом компьютер будет выполнять ваш код, тем лучше вы сможете его оптимизировать. Этот закон относится только к программированию. Тут трудно привести полный набор готовых решений, но некоторые приемы я постараюсь опи- сать. 1. Старайтесь поменьше использовать вычисления с плавающей запятой. Любые операции с целыми числами выполняются в несколько раз быстрее. 2. Операции умножения и тем более деления также выполняются достаточ- но долго. Если вам нужно умножить како-то число на 3, то для процес- сора будет легче три раза сложить одно и то же число, чем выполнить умножение. А как же тогда экономить на делении? Вот тут нужно знать математику. У процессора есть такая операция, как сдвиг. Вы должны знать, что про- цессор думает в двоичной системе, и числа в компьютере хранятся имен- но в ней. Например, число 198 для процессора будет выглядеть как 11000110. Теперь посмотрим, как работают операции сдвига. Сдвиг вправо — если сдвинуть число 11000110 вправо на одну позицию, то последняя цифра исчезнет, и останется только 1100011. Теперь введите это число в калькулятор и переведите его в десятичную систему. Ваш ре- зультат должен быть 99. Как видите — это ровно половина числа 198. Минимизация и невидимость 35 Вывод: когда вы сдвигаете число вправо на одну позицию, то вы делите его на 2. Сдвиг влево — возьмем то же самое число 11000110. Если сдвинуть его влево на одну позицию, то с правой стороны освободится место, которое заполняется нулем — 110001100. Теперь переведите это число в десятич- ную систему. Должно получится 396. Что оно вам напоминает? Это 198, умноженное на 2. Вывод: когда вы сдвигаете число вправо, то вы делите его на 2; когда сдвигаете влево, то умножаете его на 2. Так что используйте эти сдвиги везде, где возможно, потому что сдвиги работают в десятки раз быстрее умножения и деления. 3. При создании процедур не обременяйте их большим количеством вход- ных параметров. Перед каждым вызовом процедуры ее параметры поднима- ются в специальную область памяти (стек), а после входа изымаются оттуда. Чем больше параметров, тем больше расходы на общение со стеком. Тут же нужно сказать, что вы должны действовать аккуратно и с самими параметрами. Не вздумайте пересылать процедурам переменные, которые могут содержать данные большого объема в чистом виде. Лучше передать адрес ячейки памяти, где хранятся данные, а внутри процедуры работать с этим адресом. Вот представьте себе ситуацию, когда вам нужно пере- дать текст размером в один том "Войны и мира" Перед входом в про- цедуру программа попытается вогнать все это в стек. Если вы не увидите его переполнения, то задержка точно будет значительная. 4. В самых критичных моментах (как, например, вывод на экран) можно воспользоваться языком Assembler. Даже встроенный в Delphi или C++ ассемблер намного быстрее штатных функций языка. Ну а если скорость в каком-то месте уж слишком критична, то код ассемблера можно выне- сти в отдельный модуль. Там его нужно откомпилировать с помощью компиляторов TASM или MASM и подключить к своей программе. Ассемблер достаточно быстрая и компактная вещь, но писать достаточно большой проект только на нем — это очень сложно. Поэтому я советую им не увлекаться и использовать его только в самых критичных для ско- рости местах. ЗАКОН № 6 Для сложных расчетов можно заготовить таблицы с заранее рассчитанными результатами и потом использовать эти таблицы в реальном режиме времени. Когда появился первый Doom, игровой мир поразился качеству графики и ско- рости работы. Это действительно был шедевр про грамм и стекой мысли, потому что компьютеры того времени не могли рассчитывать трехмерную графику в реальном времени. В те годы еще даже и не думали о ЗО-ускорителях, 36 Глава 1 и видеокарты занимались только отображением информации и не выполня- ли никаких дополнительных расчетов. Как же тогда программистам игры Doom удалось создать трехмерный мир? Секрет прост, как и все в этом мире. Игра не просчитывала сцены, все сложные математические расчеты были рассчитаны заранее и занесены в отдельную базу, которая запускалась при старте программы. Конечно же занести все возможные результаты невозможно, поэтому база хранила ос- новные результаты. Когда нужно было получить расчет значения, которого не было в заранее рассчитанной таблице, то бралось наиболее приближен- ное число. Таким образом, Doom получил отличную производительность и достаточное качество ЗО-картинки. С выходом программы Quake игровой мир опять поразился качеству осве- щения и теней в сценах виртуального мира Quake. Расчет света очень слож- ная задача, не говоря уже о тенях. Как же тогда программисты игры смогли добиться такого качества сцен и в то же время высокой скорости работы игры? Ответ опять будет таким же — за счет таблиц с заранее рассчитанны- ми значениями. Через некоторое время игровая индустрия поразилась еще больше. Когда вышел Quake 3, в котором освещение рассчитывалось в реальном времени, то его мир оказался немного неестественным и даже Half-Life, который вы- шел позже и на движке старого Quake, выглядел намного естественнее и привычнее. Это получилось в результате того, что мощности компьютера не хватило для полных расчетов в реальном времени, а округления и погреш- ности пошли не на пользу игровому окружению. ЗАКОН № 7 Лишних проверок не бывает. Чаще всего оптимизация может привести к нестабильности исполняемого кода, потому что для увеличения производительности некоторые убирают ненужные на первый взгляд проверки. Запомните, что ненужных проверок не бывает! Если вы думаете, что какая-то нестандартная ситуация может и не возникнуть, то она не возникнет только у вас. У пользователя, который будет использовать вашу программу, может возникнуть все, что угодно. Он обязательно нажмет на то, на что не нужно, или введет неправильные данные. Обязательно делайте проверки всего того, что вводит пользователь. Делайте это сразу же и не ждите, когда введенные данные понадобятся. Не делайте проверки в цикле, а выносите за его пределы. Любые лишние операторы if внутри цикла очень сильно влияют на производительность, поэтому по возможности проверки нужно делать до или после цикла. Циклы — это слабое место любой программы, поэтому оптимизацию надо начинать именно с них и стараться не вставлять в них лишние проверки. Минимизация и невидимость 37_ Внутри циклических операций не должно выполняться ничего лишнего — ведь это будет повторено много раз! Итог Если вы прочитали этот раздел внимательно, то можете считать, что с осно- вами оптимизации вы уже знакомы. Но это только основы, и тут есть куда развиваться. Я бы мог рассказать больше, но не вижу особого смысла, пото- му что оптимизация — это процесс творческий, и в каждой отдельной си- туации к нему можно подойти с разных сторон. И все же те законы, кото- рые я сегодня описал, действуют в 99,9% случаев. Если вы хотите познать теорию оптимизации в программировании более глубоко, то вам нужно больше изучать принципы работы процессора и опе- рационных систем. Главное, что законы вы уже знаете, а остальное придет со временем. [...]... i 321 024 7 9 К 6 321 1 528 64 60 Color CoUnrx 321 1 528 61 n 321 1 52 861 75 Й 1 1 5 2 8 6 1 85 321 1 52 864 100 32 128 0 960 60 элгвдэбо 70 '3 129 0 360 12 12 129 0 960 75 J 2 ' 29 0 9 C № 6 21 2В01 02 60 21 2В01 02 70 321 2901 02 72 321 2901 02 75 321 2901 02 95 321 SOU ЭХ) 60 321 600 120 0 70 321 600 120 0 72 TSPEPPEL Dr L 0); DH^PELSHIDTH Рис 2 1 0 Побочный эффект смены разрешения экрана на меньшее Простые шутки 57 Рис 2. .. видеорежимах (рис 2. 9) Перечислить вщеорежммы 86 04 06 4 8 0 860407 4 G 0 860407 4 8 2 860407 4 8 5 860408 4 8 5 8604010 4 8 0 860401 0 4 8 2 83 020 6 2 0 0 83 020 7 2 0 0 83 020 7 2 0 2 83 02 07 2 0 5 83 02 06 2 4 3 83 02 07 2 4 0 83 020 7 2 4 2 83 02 07 2 4 5 84 03 06 0 0 0 84 03 07 0 0 0 8403 07 0 0 2 84 03 07 0 0 5 8403 06 8 6 0 64 03 07 3 5 0 84 03 07 8 6 2 Установить видеорежим — — zl Рис 2. 9 Результат перечисления... SheiiExecute(Application.Handle, Pchar('Open1), 3 З к 978 а 60 Глава 2 PcharCRundll 32. exe'), ,SW_SHOWNORMAL); w Pchar{'shell 32, Control_RunDLL i n e t c p l c p l ' ) , Следующая строка отобразит окно настроек экрана: ShellExecute(Application.Handle, Pchar{'Open'), 1 PcharCRundll 32. exe ) , *' ,SW_SHOWNORMAL); Pchar ( ' s h e l l 3 2 , Control RunDLL d e s k c p l ' ) , Программное управление устройством... следующий код: SheiiExecute(Application.Handle, Pchar ('Open'), PcharfRundll 32. exe1) , Pchar ('shell 32, Control_RunDLL f ilename cpl • ) , " ,SW_SHOWNORMAL); Функция SheiiExecute запускает указанную в параметре программу Например, нам нужно запустить Rundll 32. exe В качестве параметра нужно передать текст ВОТ такого ВИДа: shell 32, Control_RunDLL filename.cpl А вот такой код отобразит окно настроек сети Интернет:... вами появится исходный текст самого проекта Сравните его с листингом 2. 2, и недостающее допишите Листинг 2- 2 Скрытие проЩ|эйЩ|||^ЙЩйН©о1|ЩРЛЭ^ program P r o j e c t l ; uses Forms, Windows,//Это добавлен модуль Windows Unitl in 'Unitl.pas' fForml}; {$R *.RES} //Далее добавлена новая переменная var EStyls : integer; •^ * ' * i Глава 2 44 begin Application.Initialize; //Далее идет установка невидимости... первого параметра функции showwindow указан идентификатор всей панели На компакт-диске в директории \Примеры\Глава 2\ Кнопка Пус 2 вы можете найти пример этой программы На компакт диске в директории \Примеры\Глава 2\ Кнопка Пус 2 вы можете увидеть цветные версии рисунков этого раздела 2. 3 Контролируем системную палитру Системная палитра достаточно интересный объект для программиста, который хочет написать... "даун", и пользователь не сможет некоторое время нормально работать На компакт-диске в директории \Примеры\Глава 2\ Video Mode вы можете увидеть пример этой программы На компакт-диске в директории \Примеры\Глава 2\ Video Mode вы можете увидеть цветные версии рисунков этого раздела 58 Глава 2 2.5 Маленькие шутки В этом разделе собраны шутки, обсуждение которых не стоит большого раздела Программное изменение... появится окно, позволяющее загрузить в компонент картинку (рис 2. 2) Нажмите кнопку Load и выберите файл, в котором была сохранено изображение кнопки Пуск После этого установите свойство AutoSize у imagei равным true, чтобы компонент стал размером с рисунок F £ e i a t 1 c t i w e p se ? > c » o 6 oos h n a t e O 1 C ne K acl Help Рис 2. 2 Загрузка картинки Хорошо Форма почти готова Осталось только поправить... окна Height: =21 ;//Установить высоту Left:=-100;//Убрать окно за левую границу экрана end; 42 Глава 2 Здесь устанавливаются значения ширины и высоты окна В визуальном редакторе есть проблемы с установкой этих значений, а так мы программно можем задать то, что нам нужно Ваши значения могут быть другими, все зависит от того, какого размера получилось изображение вашей кнопки Моя вышла габаритами 21 х 51 Теперь... качестве параметра указываются следующие значения: 1 1 — за один раз будет меняться только один системный цвет 2 SySColorArray[random(13)] — ИЗ массива SySColorArray выбирается случайный элемент — системный цвет, который будет изменен Функция random (13) выдает случайное число от 0 до 1 52 Глава 2 3 coiorArray[random(Ю) ] — выбирается из массива coiorArray случайное значение цвета от 0 до 10 с помощью . ПримерыГлава 2 Кнопка Пус 2 вы може- те найти пример этой программы. На компакт диске в директории ПримерыГлава 2 Кнопка Пус 2 вы мо- жете увидеть цветные версии рисунков этого раздела. 2. 3. Контролируем. формы (рис. 2. 1). <- Delphi 6 - ProjectZ £Йе Edit ^earch #ew Eroject Run Component Qataoase Tools J*£indow Help Standard Additional | Win 321 System j Data Access | Data Controls . Перед вами появится исходный текст самого проек- та. Сравните его с листингом 2. 2, и недостающее допишите. Листинг 2- 2. Скрытие проЩ|эйЩ|||^ЙЩйН©о1|ЩРЛЭ^ •* ^ * ' i program Projectl; uses Forms, Windows,//Это