Если вы покопаетесь внутри
user32
, вы увидите там некоторые, казалось бы, тривиальные функции - такие как CopyRect
и EqualRect
. Зачем нам вообще нужны целые функции для того, что можно сделать с помощью операторов :=
и =
?Краткий ответ: потому что эти операторы генерируют кучу кода.
Длинный ответ: посмотрите на код, который генерируется при использовании оператора присвоения (копирование прямоугольника):
c4 5e f0 les bx, [bp-10] ; es:bx -> исходный прямоугольник (source) 26 8b 07 mov ax, es:[bx] ; ax = source.left c4 5e ec les bx, [bp-14] ; es:bx -> целевой прямоугольник (dest) 26 89 07 mov es:[bx], ax ; dest.left = ax c4 5e f0 les bx, [bp-10] ; es:bx -> исходный прямоугольник (source) 26 8b 47 02 mov ax, es:[bx+2] ; ax = source.top c4 5e ec les bx, [bp-14] ; es:bx -> целевой прямоугольник (dest) 26 89 47 02 mov es:[bx+2], ax ; dest.top = ax c4 5e f0 les bx, [bp-10] ; es:bx -> исходный прямоугольник (source) 26 8b 47 04 mov ax, es:[bx+4] ; ax = source.right c4 5e ec les bx, [bp-14] ; es:bx -> целевой прямоугольник (dest) 26 89 47 04 mov es:[bx+4], ax ; dest.right = ax c4 5e f0 les bx, [bp-10] ; es:bx -> исходный прямоугольник (source) 26 8b 47 06 mov ax, es:[bx+6] ; ax = source.bottom c4 5e ec les bx, [bp-14] ; es:bx -> целевой прямоугольник (dest) 26 89 47 06 mov es:[bx+6], ax ; dest.bottom = axЭтот код занимает 54 байт! Он получился ужасно неэффективным, потому что процессор 8086 может использовать непрямую адресацию только через регистры
bx
, bp
, si
и di
. Регистр bp
зарезервирован для использования в качестве указателя фрейма, так что его можно сразу вычеркнуть. Регистры si
и di
используются как регистры-переменные, поэтому в них почти всегда лежит что-то нужное. Что оставляет нам единственный регистр, который можно использовать для разыменования указателей: bx
.Поскольку мы работаем с указателем 16:16 - нам также нужен регистр сегмента, а у 8086 есть только четыре сегментных регистра:
cs
(сегмент кода), ds
(сегмент данных), ss
(сегмент стека), es
(дополнительный сегмент). У трёх из них уж есть предназначенные цели, поэтому остаётся только один: es
. Так что даже если бы мы могли временно использовать si
или di
, мы всё равно оказались бы в узком месте.Но если мы переместим код в функцию
CopyRect
, то мы можем сэкономить кучу байт:
c4 5e f0 les bx, [bp-10] ; es:bx -> исходный прямоугольник (source) 53 push bx 06 push es c4 5e ec les bx, [bp-14] ; es:bx -> целевой прямоугольник (dest) 53 push bx 06 push es 9a xx xx xx xx call CopyRectВсего 15 байт! Экономия почти в ЧЕТЫРЕ раза.
Это была эпоха экономии байт, поэтому любой приём, позволяющий сэкономить несколько байтов, заслуживал рассмотрения - особенно если учесть, что у вас было "всего" 256 Кб памяти¹.
А поскольку копирование и сравнение прямоугольников были весьма частыми операциями, сворачивание кода в функцию позволяло сэкономить много байтов.
Конечно, сегодня требуется уже не так много кода, чтобы скопировать прямоугольник вручную: весь прямоугольник помещается в один 128-битный регистр:
mov eax, [sourcerect] movups xmm0, [eax] mov eax, [destrect] movups [eax], xmm0
¹ Слово "всего" взято в кавычки, потому что 256 Кб кажется ужасно малым размером памяти сегодня, но вспомните, что в те времена это было максимумом, который вы могли поставить в IBM PC XT! По крайней мере, не прибегая к использованию карт расширений.
Бонусное обсуждение: мы могли бы убрать несколько инструкций, перемещая по два целых числа за раз. Это требует, чтобы два прямоугольника не перекрывались бы в памяти (чтобы избежать наложения данных) - но это, вероятно, безопасное предположение, потому что исходный код в этом случае так же не работал.
var R1, R2: PRect; V: array[0..5] of Byte; begin R1 := @V[0]; R2 := @V[1]; // плохая идеяХорошо, раз переключение на перемещение двух целых чисел за раз не нарушает ничего, что ещё не было сломано, давайте сделаем это:
c4 5e f0 les bx, [bp-10] ; es:bx -> исходный прямоугольник (source) 26 8b 07 mov ax, es:[bx] ; ax = source.left 26 8b 57 02 mov dx, es:[bx+2] ; dx = source.top c4 5e ec les bx, [bp-14] ; es:bx -> целевой прямоугольник (dest) 26 89 07 mov es:[bx], ax ; dest.left = ax 26 89 57 02 mov es:[bx+2], dx ; dest.top = dx c4 5e f0 les bx, [bp-10] ; es:bx -> исходный прямоугольник (source) 26 8b 47 04 mov ax, es:[bx+4] ; ax = source.right 26 8b 57 06 mov dx, es:[bx+6] ; dx = source.bottom c4 5e ec les bx, [bp-14] ; es:bx -> целевой прямоугольник (dest) 26 89 47 04 mov es:[bx+4], ax ; dest.right = ax 26 89 57 06 mov es:[bx+6], dx ; dest.bottom = dxПолучаем 42 байта (против бывших 54). Уже лучше, но это всё равно почти в три раза больше вызова функции.
Если у нас есть одна дополнительная свободная переменная (скажем,
si
), то мы можем сократить этот код ещё больше:
c4 5e f0 les bx, [bp-10] ; es:bx -> исходный прямоугольник (source) 26 8b 07 mov ax, es:[bx] ; ax = source.left 26 8b 57 02 mov dx, es:[bx+2] ; dx = source.top 26 8b 4f 04 mov cx, es:[bx+4] ; cx = source.right 26 8b 77 06 mov si, es:[bx+6] ; si = source.bottom c4 5e ec les bx, [bp-14] ; es:bx -> целевой прямоугольник (dest) 26 89 07 mov es:[bx], ax ; dest.left = ax 26 89 57 02 mov es:[bx+2], dx ; dest.top = dx 26 89 4f 04 mov es:[bx+4], cx ; dest.right = cx 26 89 77 06 mov es:[bx+6], si ; dest.bottom = siВсего 36 байт. Уже лучше, но всё ещё более чем в два раза больше, чем вызов функции. И это также стоило нам одного регистра.
Вот ещё один трюк: копирование прямоугольника через стек.
c4 5e f0 les bx, [bp-10] ; es:bx -> исходный прямоугольник (source) 26 ff 37 push es:[bx] ; push source.left 26 ff 77 02 push es:[bx+2] ; push source.top 26 ff 77 04 push es:[bx+4] ; push source.right 26 8b 77 06 push es:[bx+6] ; push source.bottom c4 5e ec les bx, [bp-14] ; es:bx -> целевой прямоугольник (dest) 26 8f 47 06 pop es:[bx+6] ; pop dest.bottom 26 8f 47 04 pop es:[bx+4] ; pop dest.right 26 8f 47 02 pop es:[bx+2] ; pop dest.top 26 8f 47 pop es:[bx] ; pop dest.leftХм, получается столько же, сколько и с регистрами. Но хотя бы в этот раз нам не потребовался свободный регистр.
Хорошо, а если мы позаимствуем регистр
ds
, а также и si
и di
?
1e push ds c5 7e ec lds di, [bp-14] c4 76 f0 les si, [bp-10] fc cld a5 movsw a5 movsw a5 movsw a5 movsw 1f pop dsУх ты! Тринадцать байт!
Комментариев нет:
Отправить комментарий
Можно использовать некоторые HTML-теги, например:
<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>
Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку.
Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.
Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.
Примечание. Отправлять комментарии могут только участники этого блога.