Главная - Литература

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 [207] 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294

( 1.0 - Math.pow( 1.0 + monthlylnterest, - months ) ) / monthlylnterest

Результаты не впечатляют:

Время выполнения

Время выполнения

Экономия

Язык

кода до оптимизации

оптимизированного кода

время он и

Java

2,94

2,83

Python

3,91

3,94

По-виднмому, метод MathpowQ настолько дорог, что он перекрывает выгоду устранения подвыражения. Возможно также, что подвыражение уже было устранено компилятором. Если бы подвыражение составляло не такую малую часть стоимости всего выражения или если бы оптимизатор компилятора был менее эффективен, этот вид оптимизации, наверное, оказал бы большее влияние на производительность.

26.5. Методы

Одним из самых эффективных способов оптимизации кода птиитниит ш11Ш1 т» является грамотная декомпозиция программы на методы, заании ттУ* Небольшие, хорошо определенные методы делают програм- , . -

му компактнее, устраняя повторяющиеся фрагменты кода. Они упрощают оптимизацию, потому что рефакторинг одного метода улучшает все методы, которые его вызывают. Небольшие методы относительно легко переписать на низкоуровневом языке. Объемные хитроумные методы понять сложно, а после переписывания их на низкоуровневом языке вроде ассемблера это вообще невыполнимо.

Встраивание методов

На заре программирования вызывать методы на некоторых компьютерах было крайне дорого. Вызов метода означал, что ОС должна выгрузить программу, загрузить каталог методов, загрузить конкретный метод, выполнить метод, выгрузить метод и снова загрузить вызвавший метод. Все это потребляло много ресурсов и замедляло программу.

Современные компьютеры облагают вызовы методов гораздо меньшей пошлиной. Например, встраивание метода копирования строк приводит к таким результатам:

Время выполнения

Время выполнения

Экономия

Язык

метода

встроенного кода

времени

0,471

0,431

Java

13,1

14,4

-10%

В некоторых случаях вы можете сэкономить несколько наносекунд, встроив код метода в программу, используя ключевое слово inline языка С++ или аналогичную возможность. Если ваш язык не поддерживает inline непосредственно, но имеет препроцессор макросов, вы можете встраивать код при помощи макроса, включая и выключая встраивание по требованию. Однако современные компьютеры



(под «современными» я понимаю любые машины, с которыми вы можете столкнуться в своей работе), при вызове метода почти не тратят дополнительных ресурсов. Как показывает пример, встроив код, вы можете как улучшить производительность, так и ухудшить ее.

26.6. Переписывание кода

на низкоуровневом языке

Давняя мудрость, которую не стоит оставлять без внимания, гласит, что при низком быстродействии код следует переписать на языке низкого уровня. Если вы пишете на С++, языком низкого уровня может быть ассемблер, если на Python - С. Переписывание кода на низкоуровневом языке обычно положительно влияет и на быстродействие кода, и на его объем. Типичный подход к оптимизации при помощи низкоуровневого языка таков.

1. Напишите все приложение на высокоуровневом языке.

2. Выполните полное тестирование приложения и проверьте его корректность. ftepiKfiiHii (Бшш Пйдроб- 5- Если производительность недостаточна, выполните пронес & том, ЧТ0 оеновная тш филирование приложения с целью выявления горячих то-щшт штшш пты- чек. Так как около 50% времени выполнения программы ш ftpti)c&jpr?я на «аный що обычно приходится примерно на 5% кода, горячими точ-т Парвто* рщеяаТо. обычно будут небольшие фрагменты программы.

4. Перепишите несколько небольших фрагментов на низкоуровневом языке для повышения общей производительности программы.

Последуете ли вы по этому проторенному пути, зависит от того, насколько хорошо вы программируете на низкоуровневых языках, насколько хорошо проблема подходит для решения на низкоуровневом языке, а также от степени вашего отчаяния. Сам я впервые применил эту методику при реализации алгоритма Data Encryption Standard, о чем я упоминал в главе 25. Я перепробовал все виды оптимизации, о которых когда-либо слышал, но программа все равно работала вдвое медленнее, чем нужно. Мне ничего не оставалось делать, кроме как переписать часть программы на ассемблере. Не имея особого опыта программирования на ассемблере, я по сути ограничился простым переводом кода с высокоуровневого языка на ассемблер, но даже этот примитивный подход ускорил выполнение программы на 50%.

Рассмотрим переписывание на ассемблере метода, преобразующего двоичные данные в символы ASCII верхнего регистра. В следующем примере показан соответствующий код Delphi:

Пример кода на Delphi, который лучше переписать на ассемблере

procedure HexExpanci( var source: ByteArray; var target: WordArray; byteCount: word



index: integer; lowerByte: byte; upperByte: byte; targetlndex: integer; begin

targetlndex := 1;

for index := 1 to byteCount do begin

target[ targetlndex ] := ( (source[ index target[ targetIndex+1 ] := (source[ index targetlndex := targetlndex + 2;

end; end;

] and $F0) shr 4 ) ] and $0f) + $41;

+ $41;

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

Пример метода, переписанного на ассемблере

procedure HexExpand( var source; var target; byteCount : Integer

label EXPAND;

EXPAND:

ECX,byteCount

Загрузка числа расширяемых байт.

ESI,source

Смещение источника.

EDI,target

Смещение приемника.

EAX,EAX

Обнуление индекса смещения в массиве.

): MOV

EBX,EAX

Смещение в массиве.

DL,[ESI+EBX]

Получение байта источника.

DH.DL

Копирование байта источника.

DH,$F

Получение старших разрядов.

DH,$41

Преобразование символа в верхний регистр

DL,4

Сдвиг разрядов на нужную позицию.

DL,$F

Получение младших разрядов.

DL,$41

Преобразование символа в верхний регистр

BX,1

Удвоение смещения в массиве-приемнике.

[EDI+EBX],DX

Копирование слова в приемник.



0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 [207] 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294



0.0037