К. Касперски - Техника оптимизации программ, Эффективное использование памяти (1127752), страница 81
Текст из файла (страница 81)
А когда же выясняется, что можно обойтись и меньшим количеством переменных, то удалить "излишки" как всегда забывают. Оптимизирующие компиляторы, стремясь сэкономить память, автоматически удаляют такие переменные, зачастую сопровождая эту процедуру "ворчанием" — выдачей предупреждающих сообщений на экран. Действительно, неиспользование переменной может являться следствием ошибки или описки. Никакой опасности эти предупреждения не представляют, но все же лучше от них избавиться, соответствующим образом скорректировав код. Причем, инициализация переменной или присвоение ей некоторого значения (результата арифметических вычислений или результата работы функции) не "трудоустраивают" ее! Присвоенное переменной значение хотя бы раз должно использоваться в программе! Рассмотрим следуюший пример: гпп а=О; апа Ь) Ь=а; Несмотря на то, что значение, присвоенное переменной а, передается переменной ь, переменная а все равно считается неиспользуемой, т.
к. результат ее присвоения ь никак не используется в программе! Компиляторы М)сгоаой Ч!вца! С++ и %АТСОМ вЂ” прекрасно справляются с удалением неиспользуемых переменных, выбрасывая в данном примере и переменную а, и переменную ь. Компилятор Вог1апд С++ не умеет отслеживать генетические связи между переменными, поэтому неявно неиспользуемые переменные удалять не способен.
В данном примере он удалит переменную ь, т. к. присваиваемое ей значение никак не используется, но сохранит переменную а, т. к, ее значение копируется в ь. Удаление копий переменных Если две и более переменных имеют одно и то же значение, то можно оставить лишь одну из них, а остальные удалить, заодно избавляясь от лишних присвоений. Рассмотрим следующий пример: Гпа а=Ь; рпгпатп'ах Ъп Хп",а, Ь); 4!5 Машинная оптимизация Логично, что переменная а совершенно не нужна и программа может рабо- тать и без нее, достаточно переписать ее так: р стц' зп 'л",Ь, Ю; Компиляторы М)сгоаой Ч)зца! С++ и аЧАТСОМ успешно справляются с улалением копий переменных, а вот Вог)апг! С++ этого не умеет. Удаление неиспользуемых присвоений Если значение, присвоенное переменной, никак не используется в программе, то выполнять операцию присвоения бессмысленно.
Рассмотрим следующий пример: аккуратный программист, следуя советам популярных руководств, явно инициализировал все переменные в момент их объявления, и получилось примерно вот что: гпо *р=с; апп а=ыаеогмпс)1 р=па11ас)а*1024) Никто, конечно, не спорит, что инициализация переменных (особенно указателей!) при объявлении — хороший тон программирования. Если разработчик случайно забудет о вызове функции ы, нулевой указатель при первом же обращении выбросит исключение, демаскируя ошибку. Напротив, неинициализированные указатели порождают трудноуловимую "блужлающую" ошибку — программа может работать, но нестабильно, искажая "чужие" данные, причем возможно, при кажлом запуске разные.
Попробуйка, разберись — тле зарыт жучок! Платой за надежность является "разбухание" и снижение производительности программы, поэтому оптимизирующие компиляторы, обнаружив, что значение 0, пРисвоенное пеРеменной р, никак не использУетсЯ в пРогРамме, удаляют его: апо *Р 91 апо а=а1аеотмпс)1 р=па11ос)а*1024) С этой задачей вполне справляются все три рассматриваемых компилято- ра — М)сгозой Ч)зца! С++, Вот)апс! Сч-+ и аЧАТСОМ. Глава 4 4]с Удаление лишних присвоений Операция присвоения одной переменной лругой переменной (т. е. что-то вроде а=ь) бессмысленна — от нее всегда можно избавиться, заменив копию переменной ее оригиналом.
Например, пусть неоптимизированный код выглядел так: ппс а=ь1 рсьпьт]"Ъх Ъх ~п",а, Ы ' а=а+1: рстпСЫ"Ъх Ъх 1п",а, Ь]; Очевидно, что переменная а не является копией переменной ь и не может быть удалена, но вот присвоение а=ь удалить можно, смотрите: 4пъ-а-Ьх рс пст]"Ъх Ъх ~п-,Ь, Ь]1 а=Ьъ1; рх]пст("Ъх Ъх ~п",а, Ь] 1 Компиляторы М!сгозой Ч!ьца! С++ и ъЧАТСОМ всегда избавляются от лиш- них присвоений, а Вот!апъ! С++ делать этого не умеет. Удаление лишних выражений Если результат вычисления некоторого выражения никак не используется в программе, то, очевидно, выполнять вычисление выражения нет никакой нужды.
Как же возникает такой бессмысленный код? Чаще всего — из-за небрежности программиста, привыкшего сначала программировать, а потом думать, что он запрограммировал. Рассмотрим следующий пример: с-алн с = а*Ь1 рстпьй]"Ъх~п",с]; Поскольку результат вычислений выражения мь никак не используется в про]рамме, его можно удалить, избавляясь тем самым от одной операции деления и присвоения, смотрите: а- — аяах с = а*Ь; раппы]"Ъх'~п",с]; 4)/ Машинная оптимизация Все три рассматриваемых компилятора очень даже просто справляются с удалением лишних выражений.
Удаление лишних вызовов функций удаление лишних выражений ни в коем случае нельзя распространять на удаление функций! (Во всяком случае, без дополнительных ухищрений, разговор о которь)х выходит за рамки этой книги.) Рассмотрим следуюший пример: сбвппс Ысхббб, 05777)у с/ Еппс 2 ) О) ) рггпгг)"Зх~п", с); На первый взгляд, от вызова функции г, с т можно безболезненно избавиться — возрашенное ею значение никак не используется и переменной с тут же присваивается результат работы 2 с 2, который и выводится на экран. Но задумайтесь: что произойдет, если функция тп т помимо возрашения значения делает и другую работу? Ну, скажем, записывает переданные ей аргументы в дисковый файл.
Правильно — удаление этой функции нарушит нормальную работу программы! Таким образом, оптимизируюший компилятор не должен сокращать вызовы функций, однако он может (и должен) не присваивать возрашенное значение переменной, если оно действительно нигде не используются (см. раэд. "Моление лишних присвоений" этой главы). Ни один из трех рассматриваемых компиляторов не удаляет "лишние" вызовы функций. Выполнение алгебраических упрощений Вычисление многих выражений можно сушественно ускорить, если на этапе компиляции выполнить все возможные алгебраические упрошения. Очень показателен следуюший пример, кстати, заимствованный из фирменного "хелпа" компилятора М)сгобо)г Ч)бца) С++ (см, описание функции РГЕСГЕаГЕИГПВС55)) сп.у = Ысх.су * 3) — сп.су) / 2; СХ.Х = ))СХ.СХ * 3) — С5.СХ) / 21 Если раскрыть скобки (помните школьный курс математики?), то получится буквально следующее: СХ.У = СХ.СУ) С5.Х = С5.СХ) Глава 4 л)в Поскольку оба присвоения бессмысленны !см.
разд, ".Удп<гение лишних присвоений" згпой главы), то их можно сократить. В результате мы избавляемся от двух операций умножения, двух операций деления, двух операций вычитания и двух операций присвоения — совсем не плохой результат, правда? К сожалению, даже современные оптимизаторы очень плохо справляются с задачей алгебраических упрощений. Так, приведенный пример ие сумеет сократить ии один из них. Ни М)сгобо)г Ч)бца! С++, ни Вот)апд С++, ии >ЧАТСОМ не избавятся от операций деления и умножения в выражении а=з-ьгз, хотя его избыточность очевидна.
Самый "продвинутый" в этом плане — М)сгобой Ч)бг)а! С~+ — сокращает лишь простейшие выражения наподобие а=з*ь-ь или а=ь-ь. А компиляторы Вог)а)зд С++ и %АТСОМ могут похвастаться только тем, что автоматически вычисляют результат умножения (деления) на ноль или единицу. Следовательно, код а=ь.ог =вгз после оптимизации будет выглядеть так: =сг =а. Поэтому всегда выполняйгпе все возможные алгебраические упрощения — компилятор не собирается делать это за вас! Заметим, что сказанное ие имеет никакого отношения к константным выражениям вроде з*з<ггг — их "сворачивают" все три рассматриваемых компилятора. Оптимизация подвыражений Если выражение содержит два или более идентичных подвыражений, то вполне достаточно вычислять значение лишь одного из иих.
Рассмотрим следующий пример: >б Ыа*Ь)>схббб аа )а*Ы <Охсьс) Присвоив результат вычисления га*ы промежуточной переменной, напри- мер, бар, мы сможем избавиться от одной операции умножения, смотрите: вар=а*ьг ЗГ )Ьа~р>схббб аа гви<бхсов) Оптимизировать выражения умеют все три рассматриваемых компилятора, ио не каждый из них способен распознавать идентичность выражений при их перегруппировке.
Вот, например: >г ))а ы>схббб аа )ь*а)<схьсы Очевидно, что от перестановки множителей произведение ие меняется и )а*ы равно )ь*а). Компиляторы М)сгобой Ч)бг)а! С++ и >ЧАТСОМ вычислят Машинная оптимизация значение ~з-бн лишь однажды, а Вот!апг! С+-> примет га*ю и ш*з~ за разиые выражения со всеми вытекающими отсюда послелствиями.