Ускоряем на 70% игру на процессоре в 1 МГц

Автор оригинала: Tom Moertel
  • Перевод

Давным-давно, во времена студенчества в колледже я немного занимался разработкой компьютерных видеоигр. Это была эпоха 8-битных PC, когда игровое оборудование по современным стандартам было почти невозможно медленным.

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

Это история об одном из таких трюков.

Я постараюсь припомнить все важные подробности, однако в чём-то могу ошибиться. Если так случится, простите меня, это было очень давно.

Исходные данные


Мой друг, одарённый программист, почти закончил свою новую игру. Каким-то образом ему удалось почти без изменений уместить в компьютер эпохи 1980-х довольно впечатляющую графически на то время игру, популярную на аркадных автоматах.

Единственная проблема заключалась в том, что его версия игры оказалась неиграбельной. Она работала слишком медленно, а дёрганые движения мешали вовлечённости игрока, ведь игра была сайд-скроллером.

Мой друг, работавший над игрой параллельно с учёбой в колледже, начал уже ощущать себя немного вымотанным. Опасаясь, что мог упустить какую-нибудь простую оптимизацию, он попросил посмотреть код меня.

Я посмотрел. Но там нельзя было найти никакой простой оптимизации.

Код был очень оптимальным. Куда бы я не посмотрел, он уже реализовал всё то, что бы я только мог придумать. Циклы были развёрнуты. Необязательная отрисовка была устранена. Все признаки пустой траты ресурсов были уничтожены.

А вместе с ними и наши надежды на простоту исправления.

Но как насчёт сложного исправления? Может быть, даже безумного?

Ну, такая возможность существует всегда.

Однако прежде чем приступать к безумной оптимизации, мне нужно объяснить, как в те времена работало графическое оборудование.

Как работало графическое оборудование


Если вкратце, то графическое оборудование не делало ничегошеньки.

На PC, с которым мы работали, при необходимости отрисовки чего-то на экране это нужно было делать самостоятельно, байт за байтом. Никаких функций наложения текстур, никаких блиттеров. Только байты. Байты, которые нужно перемещать ручками.

Очень захватывающе.

Бо́льшую часть времени игра моего друга тратила на перерисовку фона. (Повторюсь: это сайд-скроллер.) В каждом кадре ей приходилось отрисовывать почти целый экран тайлов фона, сдвинутых на позицию игрока.

Если память меня не подводит, каждый тайл имел размер 28 на 28 пикселей. Каждый пиксель имел один из 16 цветов, то есть на его описание требовалась половина байта. Следовательно, в памяти тайлы были представлены в виде 28 соседних строк по 14 байт каждая. Первые 14 байт представляли первую строку пикселей, вторые 14 байт — вторую, и так далее.

Однако экран имел размер 320 пикселей в ширину и 240 пикселей в высоту. То есть в памяти буфер экрана размещался как 240 соседних строк по 160 байт каждый.

Следовательно, при копировании тайла по адресу X в позицию экранного буфера, начинающуюся с адреса Y, нужно скопировать 28 строк. Чтобы скопировать каждую строку, нужно скопировать 14 байт. К счастью, эта игра работала на процессоре 6809, имевшем несколько 16-битных индексных регистров и замечательные режимы адресации с «автоинкрементом» (наподобие добавления постфиксного оператора ++ к указателям на C). Это означало, что можно скопировать 4 пикселя за раз, одновременно в процессе изменяя регистры X и Y:

    LDU ,X++     ; read a 2-byte word (= 4 pixels) from source tile
    STU ,Y++     ; write it to screen buffer

Чтобы скопировать всю строку, это нужно проделать семь раз, поэтому можно поместить эти строки в цикл со счётчиком 7 итераций:

    LDB #7       ; remaining words <- tile width in words
@LOOP
    LDU ,X++     ; read a 2-byte word (= 4 pixels) from source tile
    STU ,Y++     ; write it to screen buffer
    DECB         ; reduce remaining-word count
    BNE @LOOP    ; loop while words remain

Закончив копирование строки, нужно переместить указатель точки назначения Y, чтобы он указывал на начальный адрес следующей строки, которую мы будем отрисовывать в экранный буфер. Так как экранный буфер имеет ширину 160 байт, а тайл имел размер всего 14 байт, необходимо было прибавить их разность к Y:

    LEAY 160-14,Y

Вот и всё, мы скопировали строку на экран.

Но это только одна строка. Чтобы скопировать тайл целиком, нужно проделать то же самое 28 раз. Поэтому в свою очередь этот код мы засовываем в цикл со счётчиком 28 итераций.

Соединив всё вместе и дав имена всем важным числам, мы можем получить примерно такую подпроцедуру:

;;; important constants

SCRW = 160          ; screen-buffer width in bytes (= 320 4-bit pixels)
TILW =  14          ; background-tile width in bytes (= 28 4-bit pixels)
TILH =  28          ; background-tile height in rows
WOFF = SCRW - TILW  ; s-b offset from end of one tile row to start of next


COPYTILE
;;;
;;; Copy a 28x28 background tile into a screen buffer.
;;; Arguments:
;;;   X = starting address of background tile
;;;   Y = starting address of destination in screen buffer
;;;
    LDA #TILH               ; remaining rows <- tile height
@COPYROW
    LDB #TILW/2             ; remaining words <- tile width in words
@LOOP
    LDU ,X++                ; read a word (= 4 pixels) from source tile
    STU ,Y++                ; write it to screen buffer
    DECB                    ; reduce remaining-word count
    BNE @LOOP               ; loop while words remain
    ;;
    LEAY WOFF,Y             ; advance dst ptr to start of next dst row
    DECA                    ; reduce remaining-row count
    BNE @COPYROW            ; loop while rows remain
    ;;
    RTS                     ; done! return to caller

И этот код будет хорошо работать.

Конечно, если вас не заботит скорость.

Заботимся о скорости


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

    LDB #TILW/2             ; 2 cycles (set-up)
@LOOP
    LDU ,X++                ; 8
    STU ,Y++                ; 8
    DECB                    ; 2
    BNE @LOOP               ; 3
    ;;
    LEAY WOFF,Y             ; 8 (finishing)

Изучая эти величины, вы вряд ли упустите аж целых 21 такта на копирование всего 4 пикселей. То есть для копирования полной строки требуется 2 такта + (7 итераций) * (21 такт/итерация) + 8 тактов = 157 тактов. Ой-ёй.

Но и мы не в первый раз за клавиатурой. Мы знаем, что нужно сделать. Развернём этот цикл!

    LDU ,X++                ; 8 cycles
    STU ,Y++                ; 8
    LDU ,X++                ; 8
    STU ,Y++                ; 8
    LDU ,X++                ; 8
    STU ,Y++                ; 8
    LDU ,X++                ; 8
    STU ,Y++                ; 8
    LDU ,X++                ; 8
    STU ,Y++                ; 8
    LDU ,X++                ; 8
    STU ,Y++                ; 8
    LDU ,X++                ; 8
    STU ,Y++                ; 8
    LEAY WOFF,Y             ; 8 (finishing)

Теперь количество впустую тратящихся в цикле тактов снижено до нуля — мы избавились от подготовки, на каждую строку требуется всего 7 * (8 + 8) + 8 = 120 тактов. Ускорение на 30 процентов, довольно неплохо.

И на этом большинство программистов закончили бы.

Но не мой друг.

Он знал, что эти операторы ++ затратны, по 3 такта на каждый. А после разворачивания цикла он точно знал, где расположено каждое слово для чтения или записи относительно X или Y. Поэтому он остроумно заменил эти трёхтактовых постинкременты точными смещениями. Каждое из них стоит всего 1 такт, а смещение на 0 по сути бесплатное:

    LDU  ,X                 ; 5 cycles
    STU  ,Y                 ; 5
    LDU 2,X                 ; 6
    STU 2,Y                 ; 6
    LDU 4,X                 ; 6
    STU 4,Y                 ; 6
    LDU 6,X                 ; 6
    STU 6,Y                 ; 6
    LDU 8,X                 ; 6
    STU 8,Y                 ; 6
    LDU 10,X                ; 6
    STU 10,Y                ; 6
    LDU 12,X                ; 6
    STU 12,Y                ; 6
    LEAX TILW,X             ; 8 (finishing)
    LEAY SCRW,Y             ; 8 (finishing)

После таких оптимизаций количество тактов на строку снизилось до (5 + 5) + 6 * (6 + 6) + (8 + 8) = 98 тактов. По сравнению с первоначальным кодом ускорение составило 60 процентов:

original_speed  = (1*row) / (157*cycle)
optimized_speed = (1*row) /  (98*cycle)

speed_up  =  optimized_speed / original_speed - 1  =  157 / 98 - 1  =  0.60

Соединим всё вместе (я снова делаю это по памяти, поэтому код мог быть и немного другим), и подпроцедура копирования тайла будет выглядеть примерно так, копируя полный тайл (все 28 строк) всего за 2893 такта:

COPYTILE2
;;;
;;; Copy a 28x28 screen tile into a screen buffer.
;;; Arguments:
;;;   X = starting address of background tile
;;;   Y = starting address of destination in screen buffer
;;; Execution time:
;;;   4 + 28 * (82 + 8 + 8 + 2 + 3) + 5 = 2893 cycles
;;;
    LDA #TILH      ; initialize row count (4 cycles)
    ;;
@COPY1
    ;; unroll inner loop (copies one row of 28 pixels in 82 cycles)
    LDU ,X         ; (1) read 4 pixels (5 cycles)
    STU ,Y         ;     write 4 pixels (5 cycles)
    LDU 2,X        ; (2) (6 cycles)
    STU 2,Y        ;     (6 cycles)
    LDU 4,X        ; (3) ...
    STU 4,Y        ;     ...
    LDU 6,X        ; (4)
    STU 6,Y        ;
    LDU 8,X        ; (5)
    STU 8,Y        ;
    LDU 10,X       ; (6)
    STU 10,Y       ;
    LDU 12,X       ; (7)
    STU 12,Y       ;
    ;;
    LEAX TILW,X    ; advance src to start of next row (8 cycles)
    LEAY SCRW,Y    ; advance dst to start of next row (8 cycles)
    DECA           ; reduce remaining count by one (2 cycles)
    BNE @COPY1     ; loop while rows remain (3 cycles)
    ;;
    RTS            ; done!  return to caller (5 cycles)

Этот код в сумме оказался на 60% быстрее, чем наивный код COPYTILE, с которого мы начали.

Но он не был достаточно быстрым, даже близко.

Поэтому когда друг показал мне свой код и спросил, смогу ли я его ускорить, я на самом деле хотел ему помочь. Я действительно хотел ответить «да».

Но мне пришлось ответить «нет». Мне ужасно не хотелось давать такой ответ. Однако, изучив код, я не нашёл никаких способов его ускорить.

Тем не менее, крючок с наживкой был заброшен.

Я не мог выкинуть эту задачку из головы. Вырос я на компьютерах Apple II и их процессорах 6502. Однако код моего друга исполнялся на 6809. Возможно, он позволяет создавать оптимизации, о которых я не знаю.

Возродив свой оптимизм, я набрал номер университетской библиотеки для доступа к каталогу карточек. (В те времена ещё не было World Wide Web.) Через эмулятор терминала VT220 я поискал книги о 6809.

Нашлась только одна: руководство по микропроцессору 6809. Оно находилось в инженерной библиотеке. К счастью, я учился на инженера и имел возможность брать книги.

Поэтому я отправился в библиотеку.

Безумная идея


Добравшись до инженерной библиотеки, я нашёл книгу именно там, где она и должна была находиться, ожидая небольшой встряски:

The MC6809-MC6809E Microprocessor Programming Manual.

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

Затем я наткнулся на PSHS. «Push registers on the hardware stack» («Поместить регистры в аппаратный стек»).

Если на моём любимом 6502 вам нужно было сохранить регистры в стек, то это надо было делать по одному за раз, да и ещё передавая их через накопитель. Это было медленно и затратно. Поэтому когда была важна скорость, я учился избегать использования стека.

Однако на 6809 можно сохранять все регистры (или любое их подмножество) одной командой. Удивительно, но для этого требовалось всего 5 тактов, плюс по одному такту за каждый записываемый в стек байт.

Так как процессор имел три 16-битных регистра общего назначения — D, X и Y — я мог загрузить их, а затем использовать команду PSHS, чтобы записать 6 байт всего за 11 тактов. Соответствующая pull-команда, PULS, имела столь же низкие затраты.

Более того, 6809 имел два регистра стека, S и U. Я мог использовать один как указатель на источник, а другой — как указатель на точку назначения. Теоретически, при помощи одной пары PULS/PSHU я мог копировать 6 байт за 22 такта.

Это безумно, безумно быстро.

Охваченный восторгом, я подошёл к столу библиотекаря и вытащил свой студенческий билет. Я собирался взять эту книгу для дальнейшего изучения.

Безумный план


По дороге назад в общежитие я сформировал свой план.

Я буду сохранять куда-нибудь регистры S и U, а затем указывать при помощи S на тайл фона, а при помощи U на экранный буфер. Затем я буду извлекать данные из S и записывать в U, копируя по 6 байт за раз при помощи D, X и Y в качестве промежуточных носителей. Для копирования 14 байтов, составляющих строку, потребуется три такие итерации, которые в развёрнутом виде составят примерно 60 тактов.

Добравшись до своей комнаты, я нашёл лист бумаги и набросал черновик:

    PULS D,X,Y    ; first 6 bytes    11 cycles
    PSHU D,X,Y    ;                  11
    PULS D,X,Y    ; second 6 bytes   11
    PSHU D,X,Y    ;                  11
    PULS D        ; final 2 bytes     7
    PSHU D        ;                   7
    LEAU -WOFF,U  ; advance dst ptr   8

Всего 66 тактов, в том числе и корректировка U после строки, подготавливающая его к следующей строке. (Обратите внимание, что корректировка теперь отрицательна.) Для сравнения: наивный цикл копирования строк выполнял ту же задачу за 157 тактов. А оптимизированный код моего друга за 98. Эта безумная идея уже казалась серьёзным выигрышем.

Однако у нас была очень неуклюжая последняя пара PULS/PSHU! Ей нужно было обрабатывать последние два байта строки, потому что строки имели ширину 28 пикселей = 14 байт, а 14 не делится нацело на 6.

Этот чёртов остаток в 2 байта!

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

И, к своему удивлению, я наткнулся на золотую жилу! Это была ещё одна особенность процессора 6809 — регистр DP.

В 6502 и большинстве 8-битных процессоров той эпохи нижние 256 байт памяти назывались нулевой страницей. Эта нулевая страница была особой, потому что её ячейки памяти имели однобайтные адреса и к ним можно было получить доступ с помощью более коротких и обычно более быстрых команд.

Проектировщики 6809 развили эту идею ещё глубже. Они позволили программистам использовать регистр DP, чтобы назначать любую страницу в качестве нулевой, которую они называли «direct page».

Но ни одна из моих передающих байты команд не требовала применения direct page. Это означало, что можно использовать регистр DP как ещё одно дополнительное промежуточное хранилище. Теперь я мог копировать 7 байт каждой парой pull-push!

И 14 точно делится на 7 нацело.

После внесения этого изменения я мог копировать целую 28-пиксельную строку и переходить к следующей всего за 5 команд:

    PULS D,X,Y,DP  ; first 7 bytes    12 cycles
    PSHU D,X,Y,DP  ;                  12
    PULS D,X,Y,DP  ; second 7 bytes   12
    PSHU D,X,Y,DP  ;                  12
    LEAU -WOFF,U   ; advance dst ptr   8

56 тактов!

Благодаря этому фрагменту кода я почувствовал себя потрясающе. Мне удалось задействовать каждый доступный в машине регистр для передачи байтов! D, X, Y, U, S и даже нетипичный DP — все они задействовались в полную силу.

Мне очень нравилось это решение.

За одним исключением…

Мне нужно сделать ещё одну безумную вещь


Если вы знакомы со стеками, то могли заметить небольшую погрешность в моём блестящем плане:

Push и pull работают в противоположных направлениях.

Видите, ли я обманул вас, показывая фрагмент кода. Я не копировал одну строку из тайла фона на экран. На самом деле он разбивал строку на два блока по 7 байт (я назову их септетами), а затем отрисовывал их на экран в обратном порядке.

Помните те строки тайлов, которые удобно укладывались в память как 14 соседних байт?

+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0   1   2   3   4   5   6   7   8   9   A   B   C   D |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

Когда мы выполняли pull и push строки на экран в 7-байтных септетах, они разделялись горизонтально таким образом:

+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 7   8   9   A   B   C   D | 0   1   2   3   4   5   6 |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

Первый септет становился вторым, а второй — первым. Однако обратите внимание на то, что байты в рамках каждого септета оставались неизменными, сохраняя исходный порядок.

Более того, если выполнить код копирования строк несколько раз, то вертикальный столбец копируемых срок окажется перевёрнутым. Так происходит потому, что код использует push для записи строк на экран. Команды push перемещают указатель стека в сторону нижних адресов, а нижние адреса соответствуют строкам экрана, расположенным выше на дисплее.

Чтобы продемонстрировать хаос, которое может создавать этот переворот строк и обмен местами септетов, давайте представим, что у нас есть такой тайл ключа размером 28 на 28:


Если бы вы использовали 28 раз мой код копирования строк для отрисовки его на экране, то получили бы вот такой не-ключ:


То есть… в этом была моя проблема.

Однако у этой проблемы тоже имелось изящное решение!

Опишем проблему вкратце:

  • строки перевёрнуты
  • септеты в каждой из строк поменялись местами
  • но байты в пределах каждого септета остались неизменными

Моё открытие заключалось в том, чтобы рассматривать «переворот» и «обмен местами» как два примера одного явления. Обращения.

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

Проработав всё это на бумаге, я также обнаружил, что этап предварительной обработки может игнорировать строки и просто считать тайлы одной длинной линейной последовательностью септетов1. Чтобы понять, почему это так, давайте рассмотрим небольшой тайл из 2 строк по 2 септета каждая:

+---+---+
| a   b |
+---+---+
| c   d |
+---+---+

В памяти они будут размещены в построчном порядке, то есть как 4 последовательных септета:

+---+---+---+---+
| a   b | c   d |
+---+---+---+---+

То есть переворот строк с последующим обменом местами септетов в каждой строке будут аналогичны перевороту порядка септетов в памяти:

+---+---+                  +---+---+                  +---+---+
| a   b |                  | c   d |                  | d   c |
+---+---+        =======>  +---+---+        =======>  +---+---+
| c   d |        reverse   | a   b |        swap      | b   a |
+---+---+        rows      +---+---+        septets   +---+---+

+---+---+---+---+          +---+---+---+---+          +---+---+---+---+
| a   b | c   d |          | c   d | a   b |          | d   c | b   a |
+---+---+---+---+          +---+---+---+---+          +---+---+---+---+

То есть у проблемы с искажениями решением оказался простой единовременный этап предварительной обработки: достаточно просто разбить каждый тайл на септеты и обратить их порядок.

Зная, что проблему с искажениями можно решить, мне начала нравиться вся идея в целом. я не видел никаких других проблем, поэтому накидал черновик новой подпроцедуры копирования тайлов, чтобы показать её другу. Так как логика копирования строк занимала теперь всего 5 команд, я использовал её 4 раза, немного развернув последний оставшийся цикл. Теперь вместо копирования 28 одиночных строк я копировал 7 четверных строки.

Код выглядел примерно так:

COPYTILE3
;;;
;;; Copy a 28x28 screen tile into a screen buffer.
;;; Arguments:
;;;   X = starting address of *septet-reversed* background tile
;;;   Y = starting address of destination in screen buffer
;;; Execution time:
;;;   34 + 7 * (224 + 7 + 3) + 7 + 10 = 1689 cycles
;;;
    ;; setup:  34 cycles
    PSHS U,DP      ; save U and DP (8 cycles)
    STS >SSAVE     ; save S (7 cycles)
    ;;
    LDA #TILH/4    ; initial quad-row count (2 cycles)
    STA >ROWCT     ; (5 cycles)
    ;;
    LEAS ,X                    ; initialize src ptr (4 cycles)
    LEAU (TILH-1)*SCRW+TILW,Y  ; initialize dst ptr (8 cycles)
    ;;
@COPY1
    ;; copy four rows of 28 pixels in 4 * (48 + 8) = 224 cycles
    PULS X,Y,D,DP
    PSHU X,Y,D,DP
    PULS X,Y,D,DP
    PSHU X,Y,D,DP
    LEAU -WOFF,U
    PULS X,Y,D,DP
    PSHU X,Y,D,DP
    PULS X,Y,D,DP
    PSHU X,Y,D,DP
    LEAU -WOFF,U
    PULS X,Y,D,DP
    PSHU X,Y,D,DP
    PULS X,Y,D,DP
    PSHU X,Y,D,DP
    LEAU -WOFF,U
    PULS X,Y,D,DP
    PSHU X,Y,D,DP
    PULS X,Y,D,DP
    PSHU X,Y,D,DP
    LEAU -WOFF,U
    ;;
    DEC >ROWCT     ; reduce remaining quad-row count by one (7 cycles)
    BNE @COPY1     ; loop while quad-rows remain (3 cycles)
    ;;
    LDS  >SSAVE    ; restore S (7 cycles)
    PULS U,DP,PC   ; restore regs and return to caller (10 cycles)

SSAVE ZMD  1       ; stash for saving S while drawing
ROWCT ZMB  1       ; var: remaining rows to copy

После сложения тактов их сумма составила всего 1689. Я сократил код друга почти на 1200 тактов. Ускорение на 70 процентов!

Сияя от радости, я направился к другу.

Кислотный тест


Когда я встретился с другом и сказал, что разобрался, как сделать процедуру копирования тайла на 70% быстрее, его лицо озарилось. Когда я немного объяснил всю концепцию искажений и септетов, он помрачнел и засомневался. Но когда я показал ему код, он всё понял. В голове «щёлкнуло».

«Давай его проверим», — сказал он.

Спустя примерно полчаса работы он интегрировал код в игру. После пересборки и перезапуска игра начала загружаться.

Появился экран заставки.

Он запустил игру.

И…

Чёрт побери!

Она казалась удивительно быстрой. На самом деле, вероятно, только на треть или на половину, но этого было достаточно. Мы превзошли некий порог восприятия. Игровой процесс теперь казался плавным и естественным. Разницу можно было ощутить.

Мы просто продолжали играть в игру и улыбаться. Это был хороший день.

Но наша радость была недолгой.

Семь бед...


Спустя несколько дней проблема со скоростью была далеко позади (или так нам казалось), оставалось добавить окончательные штрихи. Одним из таких штрихов были сэмплируемые звуковые эффекты. Для этого требовалось несколько раз в секунду передавать фрагменты размером в байт в ЦАП вывода звука. Чтобы запланировать передачу, мой друг включил прерывание по аппаратному таймеру.

Всё работало замечательно. Звуки звучали отлично, и всё остальное было идеально.

За одним исключением.

Поиграв в игру однажды вечером, мы заметили, что некоторые тайлы повреждаются. И чем больше играешь в игру, тем больше появляется повреждений.

О, нет.

Происходило что-то очень плохое.

И потом нас озарило.

Что происходит, если прерывание срабатывает во время выполнения процедуры копирования тайлов?

Чтобы подготовиться к вызову обработчика прерывания, процессор помещает своё текущее состояние в системный стек. Однако во время процедуры копирования тайлов системного стека нет. Процедура «конфисковала» регистр системного стека S. И куда же он указывает? Прямо на буфер памяти, содержащий исходные тайлы!

Упс.

Мы пали духом. После всех этих усилий трудно поверить, что наше совершенное необходимое ускорение вызывает повреждение памяти…

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

Существовало всего два стековых регистра и без использования их обоих процедура копирования тайлов и близко не будет такой быстрой. Таким образом, мы никак не могли вернуть системе регистр S, не потеряв при этом с трудом заработанный выигрыш в скорости. Но в то же время не было никакого способа реализации надёжного звука без использования прерываний.

Значит, тем или иным образом прерывания должны работать. А если они работают, то при выполнении копирования тайлов всё, на что указывает S, будет повреждено.

«Как можно предотвратить это повреждение?», — спросил я.

Мы сидели, забыв о еде, и вопрос повис в воздухе.

Внезапно мой друг хлопнул по столу. Он понял.

«Не нужно его предотвращать!», — сказал он.

«Что?»

«Не будем предотвращать повреждение. Пусть оно происходит. Просто не с исходными тайлами».

Это на самом деле было просто. Он продолжил:

«Просто поменяем местами S и U в процедуре копирования тайлов. U будет указывать на исходные тайлы, а S — на экранный буфер. Если сработает прерывание, то повреждение произойдёт там, куда указывает Sна экране. Повреждение сохранится только до перерисовки следующего кадра».

«Гениально!», — сказал я.

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

… один ответ


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

Позже тем же вечером мы его нашли.

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

Таким образом, при срабатывании прерывания и повреждении ячеек памяти непосредственно перед текущим отрисовываемым тайлом повреждение будет устранено при отрисовке следующего тайла. А поскольку отрисовка тайлов выполняется в буфере, который не отображался до полной отрисовки (и выполнения вертикального обновления), никто никогда не увидит повреждения2.

Это был идеальный хак!

За старые деньки!


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

Приходилось экспериментировать с идеями, перекручивать их и поворачивать, искать что-то, что-нибудь, что позволит запихнуть их в машину. Иногда ты находил это что-то, и становился на шаг ближе к реализации своих идей, иногда нет.

Однако такой поиск всегда был очень поучительным.

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

Мы начали с процедуры копирования тайлов, ядро которой состояло из настроенного вручную развёрнутого цикла машинных команд. Затем для получения ускорения на 70%:

  1. Мы заменили эту подпроцедуру очень специфичным признаком проявления нашего безумия, конфискующим оба стековых указателя и использующим извлечение и запись в стеки, а также все доступные регистры для отрисовки тайлов вниз головой и сломанными по горизонтали.
  2. Затем мы предварительно обработали тайлы, чтобы отрисовка на самом деле их исправляла.
  3. Но потом (чёрт возьми!) оказалось, что прерывания, запускаемые во время отрисовки, способны повредить исходные тайлы.
  4. Поэтому для защиты исходных тайлов мы повреждали вместо них экранный буфер.
  5. Но такое повреждение было бы видимым.
  6. Поэтому мы изменили порядок размещения тайлов, чтобы чинить (на лету) любые повреждения, которые могут возникнуть, прежде чем они успеют отобразиться на экране.
  7. И всё это сработало!

Мы сделали это. Ради 70 процентов.

И это полностью оправдало себя.

Расскажи свою историю


Я хотел поделиться этой историей по двум причинам.

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

Я узнал, что борьба оправдывает себя.

Во-вторых, я хотел вдохновить вас рассказать свои истории. Я знаю, что «в былые времена» создание большинства видеоигр приводило к появлению множества таких историй. Я бы хотел их услышать. Но слишком многие из них утеряны, стёрты из памяти до того, как кто-нибудь догадался их сохранить.

Если у вас есть история, не ждите. Расскажите её, с каждым днём ожидания это становится сложнее.



  1. Задание: доказать для всех конечных последовательностей из конечных последовательностей, что применение (reverseconcat) эквивалентно применению (concatreversemap reverse).
  2. Нам также нужно было проверять, что ничего ценного не хранится в ячейках памяти непосредственно перед экранным буфером. Теоретически они тоже могли бы быть повреждены при срабатывании прерывания в момент размещения верхнего левого септета верхнего левого углового тайла. Адрес этого септета соответствует началу буфера.

Комментарии 96

    +13
    Эх, оптимизация… Как же её не хватает в современном мире интерпритируемых («Управляемых») языков…
      +25
      Если отказаться от «управляемых» языков, то очень быстро окажешься в мире встраиваемых систем и низкоуровневого программирования, где до сих пор главенствуют C (просто С) и ассемблер. И хватает простора для безумных оптимизаций.
      Приятно читать такие статьи. Еще приятнее осознавать, что подобные «хаки» уже использовались в твоих решениях. К счастью сегодня в большинстве случаев можно не считать такты (и не экономит байты XOR'я указатели). Впрочем, это умение всегда оказывается востребованным в самый неожиданный момент. И владеть им очень даже полезно.
        +1
        Благодаря Ардуине и прочим микроконтроллерам — такие специфические знания по-прежнему популярны (более-менее).
          +4
          Боюсь тут мы с Вами по разные стороны баррикад. С моей колокольни именно Arduino первой обесценила умение «мыслить оптимальным кодом на ассемблере». А когда выяснилось что для более-менее серьезных задач это все же необходимо подоспел STM32, который позволил «не думать оптимально» дальше.
          В итоге если раньше меня звали на проект с фразой «Мы хотим того и этого. Что тебе надо для того чтобы сделать?», то теперь всегда одно и то же «У нас STM32, а молодой и перспективный предшественник слился». И уж поверьте на слово, «молодой и перспективный» в подавляющем большинстве случаев совсем не горел желанием «оптимально мыслить на ассемблере». Скорее ему хотелось «нагородить фичь и выполнить все хотелки руководства». И пока получалось — работал. А как перестало… зовут меня.
          Конечно, глупо винить в таком раскладе Arduino или STM32. Но и признавать их полную непричастность я тоже отказываюсь.
            +1
            Ну да… Ардуину я зря упомянул. Тут надо STM32F103 + HAL, хотябы, упомянуть. Или STM8…
              +1
              У меня был один из любимых «хаков» в программе на ассемблере
              проверить один регистр, например на нуль — т.е. сформировать соответствующий флаг в регисте состояния (например флаг Z-нуля, на допустимый тайм-аут выполнения текущего цикла), а другой командой проверить ещё какой то регистр, но командой не изменяющей первый флаг, а устанавливающий/сбрасывающий другой флаг (наприме флаг переноса Carry), И финальной командой или остаться в цикле или выйти из него по результату командой перехода анализирующей оба флага (знаковые, безнаковые… переходы)

              P.S. Вот, такие и ещё «моменты», хотелось бы видеть в учебниках применения ассемблера… :)
                +5
                Ммм… А нужен ли здесь ассемблер? Насколько я знаю любой более-менее уважающий себя компилятор C умеет (и с радостью проделывает подобное) на уровнях оптимизации больше «none».

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

                Ну и спуск на уровень ассемблера… Это тема отдельного разговора. И, конечно, это всегда компромис. Другое дело, что работа со встраиваемыми системами это всегда бесконечный день сурка. Каждая новая железка начинается с написания того, что уже было написано неоднократно ранее. Та сама ситуация, против которой всегда выступает так любимый прикладным сообществом лозунг «не повторяйся» (DRY code). Но… Каждая новая итерация приносит чуть больше понимания и делает код чуть быстрее и чуть более качественным. Очень медленный процесс. Фундаментальные изменения крайне редки. С другой стороны, этот самый лозунг «не повторяйся» больно бьёт прикладников порождаю «технический долг» и прочий «Legacy Code». Так что своя порция «счастья» есть везде.

                Потому мой обычный принцип состоит в том, что если не нравится итоговый ассеблерный код — правь алгоритмы на C. И только если это невозможно спускайся ниже. Другое дело, что понять что именно не нравится и определиться с тем надо ли спускаться можно только понимая ассемблер и зная как именно один код преобразуется в другой (и как на этот процесс можно влиять). Но это наш кактус — кактус embedder'щиков. И мы его потихоньку грызём.
                0
                Ну ладно Ардуино с кучей С++ абстракций над GPIO, а с STM32 что не так?
                  +1
                  Так и с Ардуино проблем нет. AVR классный чип с отличным набором инструкций и превосходной документацией. Он очень на многое способен.

                  Что до STM32, то у меня к нему вопросы исключительно по качеству документации. Кто реально пытался с ним работать на уровне «шуршать по регистрам» тот знает о чем речь. Частенько приходилось заглядывать в библиотеку или HAL и натыкаться на странные вещи типа важной не задокументированной последовательности инициализации периферии.

                  Впрочем, возможно уже поправили. И документацию и чипы. Просто лет 5 назад это настолько взбесило, что зарекся с этим чипом писать на уровне регистров. А современный HAL, наконец позволяющий делать нормальные callback'и, конечно, красив. И, самое главное, под капотом имеет довольно не плохой код. Совсем не чета старой Standard Peripheral Library.

                  Впрочем, низкий порок вхождения генерирует проблемы. Что на Ардуино, что на STM32. Пока эти проблемы не выходят за рамки DIY — проблем нет. Но обнаружить хм… дурно пахнущий код… в промышленном изделии (крайне плохо) работающим тысячами и раскиданным по всей территории России… А самое главное потом его полностью переписать и понять что STM32 не недостаточен (как считалось) а избыточен (как получилось)… И если б это единичная история… Потому с моей колокольни это одного поля ягоды.

                  Впрочем, упаси меня бог запрещать кухонные ножи, топоры или автомобили только по той причине, что они могут убить. Потому отвечаю на Ваш вопрос так — проблема не в конкретных чипах. Проблема в людях, которые эти конкретные чипы используют. И если Atmel (теперь Microchip) несколько дистанцируется от Arduino, то ST Microelectronics всячески способствует увеличению таких «специалистов». Для них это хорошо. Для рынка, пожалуй, тоже неплохо. Но кто сказал что моя оценка обязана совпадать с рыночной?
                    +1
                    И если Atmel (теперь Microchip) несколько дистанцируется от Arduino, то ST Microelectronics всячески способствует увеличению таких «специалистов».

                    а много ли ардуинщиков знает, что «у неё внутре неонка atmel»?
                    Ну а ST просто захватывает рынок. И переводит его из «элитарного» в утилитарное. 30 лет назад слово микроконтроллер знали, условно, единицы. 20 — уметь что-то сделать на МК было крутостью. 10 лет назад это стало обычным в узких кругах, и элитарным в среде школьников благодаря ардуине. сейчас не освоить ардуину — стыд и позор, а STM хочет занять ее место для всех слоев — от школоты, использующих вместо ардуины — нуклео или блупилл (да, ftitzing и вот это всё), включая середнячков, которые знают, что такое принципиальная схема, и которым кубик просто сильно экономит время, и заканчивая такими, как Вы, профессионалами. И у STM это получается.
                      –2
                      Наверное мне стоит благодарить ST Microelectronics за то, что без работы я не сижу. Но, предельно честно, это не та работа которой хочется заниматься. Хочется разрабатывать и рождать не костыльные решения (и это моя основная работа уже много лет), а приходится подтирать сопли за теми, кто переоценил свои силы в плане освоения STM32 (и это тоже многолетняя подработка).

                      И, слушайте, откуда взялось «элитарное», «утилитарное»? Это чья оценка? По мне или ты профессионал и делаешь продукт или… не профессионал и не делаешь. Во всяком случае за деньги. Я же не бросаюсь учиться оперировать на мышах и соседских собаках и не кричу что это «элитарное» умение должно быть продвинуто в массы и стать «утилитарным».

                      Резюмируя скажу так. Я бы предпочел жить без подработки. Но это тема политэкономии, а не техники. Потому простите, но… Я завязываю.
                        0
                        Выдавать глобальные идеи — это удовольствие; искать сволочные маленькие ошибки — вот настоящая работа. (Фредерик Брукс-мл)
                        Между «профессионал» и «не профессионал» — широкий спектр. У некоторых это целая жизнь.
                        «элитарное» — это доступное немногим. «утилитарное» — доступное всем. Жизнь постоянно переносить элитарность в утилитарность, в т.ч. и в профессиях. В начале прошлого века «шофер» был крутой дядька в кожаной куртке и очках, ездящий на настоящей автомашинеЭ с замасленными руками и жутко умный (ибо еще и чинил свою машину). Сейчас шофером может быть даже слабоумная «блондинка». Редкое и особенное стало распространенным и обычным.
                          0
                          Даешь атомный реактор в широкие массы! Раньше электричеством занимался только головастый Фарадей, а теперь любой дошкольник может не просто замкнуть электрическую сеть включая свет в туалете, так ещё и способен обуздать полупроводники банальным нажатием кнопки на планшете. Было б смешно, если б не было так грустно.

                          Ваш пример в шофером — сущая провокация. Даже в извозчики брали не всех. Нужно было пройти обучение и подтвердить квалификацию. В частности и по знанию ПДД. Извозчик почти всегда должен был иметь навыки конюха (и, отчасти, ветеринара). Ранние шоферы — не просто водители, они еще и автомеханики. А нередко еще и автоконструкторы. Куда там любой современной блондинке до старого шофера. Как и до шеф-повара. Механически повторять рецепты на кухне (даже немного разнообразя их количеством специй) совсем не то же самое, что придумать реально новое блюдо. Да и дать мужу или ребенку парацетамольчик, чтоб сбить температуру блондинка может. И то в зависимости от степени «блондинистости» — а то ведь только после подсказки провизора из аптеки (по сути варианта блондинки с медицинским уклоном).

                          Не думаю, что подавляющее большинство населения заинтересовано в том, чтоб блондинки без специального образования (а часто и огромной практики под руководством более опытных соратников) водили такси и автобусы, преподавали в школе, лечили в больницах (и, подавно, вне больниц).

                          При этом все (хорошо, почти все) работаю на эту самую блондинку. Медики придумывают безопасные и действующие лекарства, повара новые блюда, автоконцерны придумываю новые коробки-автоматы и беспилотные автомобили. И даже программисты стараются изо всех сил (хоть и плюются с «тупых» юзеров) ровно для нее. При этом и прикладники (условно — кнопочки красивые и на своих местах) и системщики (система отзывчивая и «не глючит»).

                          А теперь простой вопрос. После всего сказанного Вы будете продолжать настаивать на праве блондинки не просто иметь свою мнение по любому вопросу (включая управление атомным реактором), а именно на праве активно учавствовать в работах и принятии решения? По мне такой мир будет просто безумно страшным. Пусть блондинка решает давать парацетамол или грипферон (или вообще ничего не давать). Пускай она отвечает за выбор блюд на ужин и постинг фоточек в социальные сети. Это те решения, которые ей ясно и недвусмысленно делегированы. Но ни на шаг больше.

                          Так что я не готов согласиться с тем, что «Жизнь постоянно переносить элитарность в утилитарность». Нет, она просто постоянно расширяет границы утилитарности. Вселенная расширяется, энтропия возрастает…

                          А цитата хороша. Впрочем… Вреден я. Ведь и здесь не соглашусь. Выдавать глобальные идеи — это политика, популизм, научная фантастика или что-то им близкое. А вот реализовывать глобальные идеи, получая удовольствие от устранения мелких сволочных ошибок — это уже работа мечты. К счастью у меня такая вот уже 20 лет как есть. И, надеюсь, еще хотя бы 25 продержаться.
                      0

                      Странно, что производители чипов стали виновны в низком уровне разработчиков с Вашей точки зрения. А не образовательная система, например.


                      ST Microelectronics всячески способствует увеличению таких «специалистов».

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

                        +1
                        Давайте так. Научить программиста в ВУЗе невозможно. Сколько б минусов я не словил за столь категоричное высказывание. Любого программиста делает в первую очередь опыт практической работы. Я не говорю что базовые знания бесполезны. Нет. Но я утверждаю — выпускник ВУЗа это полуфабрикат. Его еще готовить и готовить. При чем в любой отрасли. Невозможно подготовить специалиста по базам данных. По веб-технологиям. По ИИ. Ну и токовый специалист по встраиваемым системам это плод длительной работы. Сначала с наставником, потом самостоятельной, потом обучения. Да, умение передавать знание не менее важно, чем умение получать. Рассказывая, объясняя и обосновывая почему так сам глубже понимаешь вопрос и пересматриваешь свои подходы. Возможно, я не прав. Но мне кажется что это касается вообще любой сферы. Учителя, врачи, токари, слесари, плиточники, монтажники…

                        Теперь чем же мне не нравится ST Microelectronics. Да всем нравится. Кроме одного. Она выдает микроскопы и не объясняет как ими пользоваться. В итоге в неподготовленных руках они безоговорочно превращаются в молотки. И ладно б так. Почему б и не забить саморез микроскопом, раз микроскоп доступен и саморезов навалом? Но беда в том, что люди заколотившие саморез становятся абсолютно уверенны в том, что только так и можно. Более того — как все неофиты они искренне и с достойную лучшего применения рвением пытаются доказать всему оставшемуся миру свою правоту. Вот именно за это я и не люблю ST Microelectronics. Atmel (Microchip), Sharp, Maxim, NXP — да фактически все уделяют внимание в первую очередь документации и базовой поддержки средствами разработки. Справедливо полагая что код напишут знающие люди (и напишут так хорошо, как только смогут). STM же выдает странный полуфабрикат. Который превратится в едва съедобное в руках неопытного разработчика, и во что-то более или менее похожее на правильный результат в руках опытного специалиста.

                        Я поправлюсь. Я видел очень классный код работающий на STM. Но беда в том, что таких проектов подавляющее меньшинство. Нужна определенная смелость для того чтобы все же пробраться через огрехи документации и доделать нижний слой. К счастью на хабре такие присутствуют. Чтоб ненароком кого не обидеть не стану называть никого. Но видел я и такие статьи. И скажу честно — слегка завидовал. Меня на такое не хватило. Впрочем, там в основном плюсы были. А это несколько не мой профиль.
                  +3
                  Ардуина — вряд ли. там тупокод цветет и пахнет.
                  а контроллеры вообще- да, хотя сейчас «историю одного байта» повторять никто не будет — проще взять контроллер на несколько центов дороже, с вдвое большей памятью.
                    0
                    В «история одного байта» все же условия были несколько экстримальные: есть МК который утвержден и под него уже все готово и нужно было впихнуть фичу, про которую заказчик забыл.
                    +1
                    Подождите немного. Даже на хабре полно статей о том, как сделать очередную мигалку светодиодом на контроллере, с тегами «python» или «javascript». Эту чуму уже не остановить, персоналки пали под натиском скриптового безумия, контроллеры — на очереди.
                      0
                      И это печально… Теперь найти интересную работу, где надо напрягать мозги, стало крайне проблематично.
                        +4
                        … и опять я со своими уточнениями. Работы такой навалом. Проблема не в работе. Проблема в том, что оплачивать ее адекватно не хотят. Как правило бюджет съеден на этапе обрастания фичами. Но увы, это практически невозможно исправить.
                          0

                          В большинстве случаев конечный пользователь не хочет платить на скорость, он хочет платить за функционал. Так что это не проблема, просто IT даёт то, на что есть спрос.


                          Представьте, что вы хотели бы вкусную еду, а вам в ресторане подавали бы только полезную, не разрешая заказать то, что вам хочется. Можно сколько угодно агитировать ресторан так делать, но он не сделает так никогда.


                          Так же и с софтом. Только тот софт быстрый, пользователи которого требуют и платят за эту скорость (не считая проектов, живущих на энтузиазме).

                            +4
                            Я за то, чтоб был McDonald's. Но я категорически против того, чтоб был ТОЛЬКО McDonald's. Я хочу хоть иногда отдохнуть в Метрополе. И сильно расстроюсь если закроют сильно любимые Питерские кафехи Север. Больше того — суши бары, азиатская кухня, и все остальное тоже вкусно и интересно.

                            Но времена складывается ощущение что я единственный кому интересно хоть что-то кроме Макдака. А вот это уже страшно.
                          +5
                          Можно, например, пойти работать в NASA и попробовать добавить новую фичу в прошивку Вояджеров.
                          0
                          Да оставьте вы эти статьи в покое — они для людей для которых это просто хобби. В то же время есть и статьи для «хардкорщиков». Но первых гораздо больше, вот и статей таких появляется больше.
                          Ну вот я «мигал светодиодом» недавно на esp32. Это был единичный и практически одноразовый девайс. Я взял платку, которая была под рукой, спаял пяток проводов своей криворукой пайкой, набросал пару десятков кода в ardiuno ide. На все про все ушло пол часа времени. Зачем мне лезть в дебри, если это для меня лишь хобби. Я отдыхаю а не работаю (хотя этот девайс мне нужен был для работы, но повторюсь, он был не серийный и одноразовый).
                        +2
                        Помню времена, когда для ускорения работы кода в программу вставлял многочисленные ассемблерные вставки (в т.ч. для отрисовки на экране, вывода графических файлов, обмена с внешними устройствами и пр., чтобы экономить процессорные ресурсы), да еще старался сильно оптимизировать программный код (чтобы уложиться в объем небольшого ОЗУ). А сейчас — все увеличивающиеся мощности компьютеров, ОЗУ и дисковое пространство, ООП, визуальное программирование, фреймворки и пр. попросту убивают искусство программирования; и современные программы реально пожирают ресурсы компьютеров.
                          0
                          Искусство программирования убито кроссплатформенностью. Теперь нужно либо для каждой из платформ придумывать собственный набор трюков и хаков, фактически написав одну и ту же программу столько раз, сколько она поддерживает платформ. Либо же использовать фреймворки и создавать как можно более предсказуемый код, чтоб он работал везде.
                          И ситуация изменится не раньше, чем рынок полностью монополизируется единственной платформой. В этом плане есть некоторая надежда на интенсивное развитие WebAssembly и сдыхание всех альтернативных браузеров в пользу одного-единственного.
                          То есть, конечно, если такое случится, то негативных эффектов будет масса. Но вот возможности оптимизации возрастут колоссально, вплоть до написания байт-кода вручную.
                            0
                            Искусство программирования убито кроссплатформенностью...

                            Это не совсем так. Есть множество хаков мало зависящих от конкретного процессора или системы команд. Хотя, конечно, часть правды в этом есть. Но в коце-концов тот же Unix придумывался в первую очередь ради переносимости. Но это не мешает оптимизировать и ядро (на конкретной железке) и прикладное ПО (как под железку, так и кроссплатформенно).
                              +1
                              То есть, конечно, если такое случится, то негативных эффектов будет масса. Но вот возможности оптимизации возрастут колоссально, вплоть до написания байт-кода вручную.

                              Вот только некому будет писать.
                                0
                                В современном мире все еще есть сферы, где считают байтики и такты. Как в качестве хобби (демосцена) так и во вполне практических целях (ААА-геймдев и прочие рендереры, кодеки, майнинг и т.д.).
                                И людей, что таким занимаются, поди на порядок больше, чем 30 лет назад. Просто доля их в общем числе программистов упала на тот же порядок или два. Но когда будет под это задача, любители хардкорной оптимизации найдутся.
                                Тут скорее проблема именно с задачами, где такая оптимизация будет экономически оправданной.
                                  0

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

                              0
                              Ooopss… Похоже ошибся уровнем. Извиняюсь.
                              … современные программы реально пожирают ресурсы компьютеров ...

                              А Вы современный программист? Вот только честно и для себя.
                              Я когда-то решил, что мне не интересно прикладное программирование. Вот совсем. И решил остаться на самом низком уровне. Пусть и менее востребовано, пусть и денег здесь меньше — за то я четко знаю как каждый оператор моей программы влияет на бег электронов внутри изделия. И на этом уровне все осталось как раньше. Те самые бешенные хотелки в процессе реализации выжимают все даже из таких монстров как STM32, а неуемные аппетиты прикладного ПО не дают расслабиться и на уровне ядер операционной системы. И загрузку быструю все хотят — загрузчик неизбежное зло, а потому должен быть максимально эффективным.
                              Другое дело, что мало кто желает идти этим путем. Было бы интересно добавить в эту (и ей подобные статьи) опрос — кто насколько понял написанное. Думаю при честных ответах едва ли половину осилили. Букв много. Буквы такие, которые требуют знания «внутреннего мира» процессора. Хотя по сути просто описана давно известная оптимизация.
                              А искусство убить невозможно. Оно так или иначе неистребимо. Другое дело, что в живописи тоже были свои эпохи. Импрессионисты не примут Авангард, но и те и другие будут посланы далеко в лес художниками эпохи Возрождения. Так что главное выбрать путь и идти по нему. По возможности не навязывая окружающим своего мнения. Временами даже полезно узнать, что там нового в соседском лагере (и где мы это уже видели).
                                0
                                Помню, как-то переписывал все свои ассемблерные вставки обратно на С++, т.к:
                                1. Код не компилировался на другую архитектуру.
                                2. Когда я писал этот код, он и правда был быстрее того, что генерил компилятор того времени для тех процессоров. Но с тех пор сменилось уже не одно поколение процессоров и версий компиляторов, и современный компилятор уже давно генерил для новых процессоров более быстрый код, в чем я убедился переписав его на С.
                                  0
                                  Ассемблер — это не ядерная бомба. Это скальпель. А у хирургического вмешательства всегда есть единственное показание — без него жизнь по угрозой. Потому если без него МОЖНО обойтись, то без него НУЖНО обойтись.
                                  А вопрос написания кроссплатформенного кода сильно шире ассемблерных вставок. В общем случае наиболее справедливый ответ выглядит как «вы просто не умеете их готовить». Впрочем — еще раз повторюсь — это всегда хирургия. И великое искусство всегда ровно в том, чтоб с одной стороны не впадать в гомеопатию, с другой не скатываться до хирургии. И уж подавно не доводить до необходимости хирургического лечения избыточным увлечением гомеопатией.
                                +1

                                Даже на встраиваемых 1МГц нынче встретишь нечасто. И уж тем более нечасто там бывают задачи о плавной отрисовке тайлов в реалтайме. Поэтому запилить туда python и запульнуть скрипт на сотню строчек, который опрашивает данные очередной "погодной станции" — норм. А читать посты о том, как кто-то запустил эмулятор ZX-Spectrum на двухдолларовом программаторе-"свистке" — уже вызывает "аах!"

                                  0

                                  Именно что полезно уметь и держать в арсенале. А заодно — знать, как оно там под капотом шевелится. От встраиваемых систем недалеко (качественно) до какого-нибудь core-i9; покуда там больше "количества".
                                  Ардуина хороша высокоуровневостью и и предлагаемым другим подходом: чтобы просто помигать светодиодом пару раз в секунду не нужно лезть в ассемблер!
                                  (если задача состоит именно в этом — скрипт-ардуидди парой строчек, скопипащенных с какого-нибудь форума/stackoverflow решит такую задачу гораздо быстрее гуру ассемблера). Но досадно, что дальше, порой, дело не идёт. И причина этому — сама ардуина. Она, во-первых, слишком быстрая, чтобы заметить недостатки высокоуровневых языков. Во-вторых, в её родной экосистеме программируется всё же на Си, который компилируется, и тем самым оставляет место по сути лишь для безумных оптимизаций (покуда все разумные уже сделал компилятор).


                                  В этом плане тот же ZX Spectrum гораздо больше провоцирует к исследованиям: сам как ардуино (запустил — и вот тебе бейсик!), но при этом более-менее серьёзные программы на бейсике с какой-то числодробительной математикой уже ЗАМЕТНО тормозят, и при этом под рукой куча программ, написанных на некоих "машинных кодах", которые те же задачи выполняют плавно и быстро. Оп-па, челлендж! Надо разобраться!
                                  Да и встроенное руководство их упоминает, и даже приводит пример простейшей программы, причём в НУЖНОМ месте — в самом конце, когда бейсик уже освоен


                                  LD BC, 99
                                  RET

                                  хм… непонятные буковки… Интересно!

                                    0
                                    Если отказаться от «управляемых» языков, то очень быстро окажешься в мире встраиваемых систем и низкоуровневого программирования
                                    Это хорошо или плохо, на ваш взгляд?
                                      0
                                      молоток — это хорошо или плохо? :-)
                                        0
                                        Я не уверен, что понял вопрос.

                                        На сегодня это факт. Я склонен считать, что любая сложившая и самоподдерживающая ситуация — это хорошо. Раз получается довольно четкое разделение на «системщиков» и «прикладников» значит так (и только так) и нужно. И пока взаимопроникновения не происходит — значит и не должно происходить. Потому отвечу так — на мой взгляд это факт. И все. Без оценок.

                                        Подход с «подсчетом байт» почти наверняка никогда не придет в прикладное программирование. Прикладное программирование всеми силами от него убегало и нет ни одной причины, по которой ему бы захотелось вернуться к таким сложностям. Да, мы понимаем цену, которую приходится платить за такие удобства. Но ведь платим. Значит всех все устраивает. И конечного потребителя, и разработчика прикладного ПО, и разработчика инструментальных средств. Не вижу причин считать такую ситуацию плохой.

                                        На другом фронте «системное ПО». Как бы много не было байт и тактов, но они конечны. И сильно нужны прикладной части. Потому позволить себе бесконтрольно их использовать просто нельзя. Нравится это кому-то или нет. Потому и приходится опускаться и отказываться от «управляемых» языков. Да, это не столь востребовано. Очевидно — прикладников надо в разы больше. Да, это не так хорошо оплачивается. Да, даже производители чипов не сильно заботятся о «низкоуровневой» части доверяя ее OpenSource Community. Но по факту именно такой подход позволяет поддерживать критически необходимый уровень системных программистов. Не истребляя их как класс, не заморачиваясь их специальной подготовкой, но и не оставаясь без низкоуровневых разработчиков. Не вижу причин считать такую ситуацию плохой.

                                        А вот свою личную оценку всей отрасли целиком, прикладной и системной составляющей в частности я, пожалуй, попридержу. Но она не будет сильно отличаться от озвученной.
                                      0
                                      На том же Z80 (ZX-Spectrum, к примеру) двух стековых регистров не было. Но никто не мешал, скажем, читать через POP из памяти, а писать в фиксированный адрес ячейки, что бы без всяких ++. Да, процедуру нужно было разворачивать «руками» для каждой ячейки экрана. Благо, что их было не так много. Зато максимально быстро, на Z80 быстрее сделать было нельзя.
                                        0

                                        На спектруме в целом всё более "теплично".
                                        Возьмём условно частоту 7Мгц и экран размером 7Кб (так легче прикидывать). Это сразу даёт возможность возиться над каждым байтом по 1000 тактов, чтобы обновлять картинку раз в секунду. Делим на "жёсткий реалтайм" того времени (25 кадров/сек) — 40 тактов. Берём реальную частоту (половина от 7МГц) — 20 тактов. И если учесть, что экран всё же 6,75К, и цветовые байты на каждый чих обновлять не надо — даже чуть больше. Так что даже в этих условиях вроде всё получается без всяких безумных оптимизаций (тот же LDIR насколько помню кушал 17 тактов на байт). Плюс, никто не думал о простое процессора (а сколько он кушает в HALT до следующего прерывания, по сравнению с тем, если мы вместо этого будем что-то считать). Ну и 25fps — тоже жёстко, да и цвета всё равно будут прыгать по 8 строчек и портить визуальное впечатление. А раз идеала всё равно не получится — можно и тут понизить и освободить ресурсы для логики.

                                          +4
                                          Насчёт оптимизаций — LDIR был медленнее кучи LDI. А обновить все 6.75кб экранной памяти между прерываниями от кадровой развёртки (50к/с на телевизоре и, соответственно, спектруме) не хватало времени даже при помощи LDI, так что приходилось идти на ухищрения типа обновления только части экрана, занятой активными спрайтами и, соответственно, полностью статичным фоном в игре (Dizzy) или использования не всего экрана, а к примеру, верхних двух третей (Elite) или даже средней трети (демоверсия чего-то похожего на Doom). Ну и особенности адресации видеопамяти тоже доставляли, но к этому уже можно было привыкнуть и использовать для относительно быстрого вывода спрайтов с точностью до знакоместа.
                                            0

                                            50fps там никто особо не ждал (и реально заметных глазу поводов стремиться к этому не было). Да и прерывания на два-три тика можно просто запретить. Или заменить обработчик своим собственным, легковесным (чтобы пошустрее всё проверил и вернулся). Но в целом да, сделать половину/две трети экрана чёрными и там не рисовать вообще — тоже вариант. Но мне кажется, это уже шаги в сторону Agile. Сделать хоть как-то быстрее, но на пол-экрана, и уже завоёвывать рынок. Против "сделать плавно и гладко на весь экран, но + пол-года разработки безумных оптимизаций"

                                            +2
                                            LDIR — это 21 такт на байт, а через стек можно почти вдвое быстрее.
                                              +1
                                              На ZX не 7 МГц, а вдвое меньше, и LDIR 21 такт на байт. И в игровом рендере нельзя обойтись просто линейным копированием байт, одна только обвязка кадра может занять сравнимое с копированием врем, не говоря про наложение спрайтов по маске.
                                                0

                                                Про вдвое меньше я прямо в комментарии и написал, просто вы не дочитали.
                                                И цель была, в общем-то, просто прикинуть рамки выше которых точно не прыгнуть.

                                                  +1
                                                  Трудно построить реалистичные прикидки на изначально очень неточных данных. Это довольно странная идея, сначала завысить-занизить параметры, потом как-то поделить. Поэтому у вас получилось, что легко получить хороший фреймрейт без креативных оптимизаций. Но в реальности это было далеко не так. В реальности была медленная память, снег, порча стека, пропуск прерываний, ради 25 FPS был код типа pop de:ld (hl),e:inc l:ld(hl),d:dec l, т.е. чтение стеком и вывод змейкой с оптимизацией перехода 256-байтных границ (~16 тактов на байт), и многое другое.

                                                  Про подобное определение рамок вспоминаются статьи из 90-х о невозможности полноэкранной прокрутки текста в 50 FPS, там тоже считали примерно так же. Но фактически она таки возможна, что и было многократно реализовано чуть позже.
                                                    +1
                                                    Особенно весело было в Ghosts'n'Goblins под ZX Spectrum 48K:
                                                    loop:
                                                    ld bc,7
                                                    add hl,bc
                                                    ld sp,hl
                                                    add hl,bc
                                                    pop de
                                                    pop bc; вот этими словами будем рисовать одну строку
                                                    ld sp,hl
                                                    exx
                                                    pop de
                                                    pop bc; а вот этими соседнюю
                                                    ld sp,hl; вот сюда будем рисовать строку
                                                    push bc/de *14; некая комбинация слов (например, небо и потом текстура земли)
                                                    inc h
                                                    ld sp,hl; а вот сюда соседнюю
                                                    inc h
                                                    exx
                                                    push bc/de *14; аналогично
                                                    dec a
                                                    jr nz,loop

                                                    С вариациями (там несколько таких циклов).
                                                    И дизайн игры подчиняется этому техническому решению, чтобы было быстро.
                                              +1
                                              На спектруме часто заполняли все регистровые пары из стека, потом меняли указатель стека на буфер экрана и pushили их туда. Для этого даже использовали второй набор регистров, чтобы реже менять указатель стека.
                                                +1
                                                На моей памяти одна демка на спектруме устанавливала адрес стека на начало экранной памяти 16384 и пушила туда картинку. Это позволяло получить чуть-ли не полноценную анимацию… Вспомнил название — «Lyra 128»
                                                www.youtube.com/watch?v=EpQ94LmXaSQ&lc=Ugi2Y8idS0jnhngCoAEC

                                                Меня тогда удивила как быстро она работает и я полез поковырять ассемблерный код.
                                                  0
                                                  На моей памяти одна демка на спектруме устанавливала адрес стека на начало экранной памяти 16384

                                                  Ошибочка. При добавлении чего-либо в стек изменение регистра SP (указателя на вершину стека) происходит в сторону уменьшения.

                                                  В случае ZX Spectrum, если требовался вывод только пикселов без изменения атрибутов, то указывалось на #5800 (начало области атрибутов) и далее PUSH'илось на экран до #4000.
                                                    0
                                                    Точно, вы правы. Уже забыл детали архитектуры. Но простительно, уже 25 лет не трогал спектрум :)
                                                +1
                                                Вы, очевидно, просто не представляете, сколько оптимизаций в движке JS V8, например. Все эти ваши разворачивания циклов или вынос переменной в регистр прост покурят рядышком, не отсвечивая.
                                                  +1
                                                  А что толку с этих оптимизаций, если куча веб-программистов на форумах спрашивают, нужны ли им знания алгоритмов?
                                                  –1
                                                  Это не оптимизация, это бит-жонглирование…
                                                  +4
                                                  Главное не делать оптимизацию ради оптимизации. Хотя и парадигма «у нас дофига процессорной мощности и памяти» иногда излишне развращает. Была такая игра Арк — выживалка с динозаврами, она удивительным образом тормозила даже на мощных системах. А затем выяснилось, что она целиком построена на одних блюпринтах анриал энжина.
                                                    0
                                                    «Была»? А сейчас что с ней? Перестала следить за новостями. Неужели наконец оптимизировали?
                                                      0
                                                      Нет, конечно. Но ушли кудато в сторону от динозавров, полностью сосредоточившись на Sci-Fi составляющей.
                                                      Требования прежние: 1080Ti + i7-7700 + 16GB Ram = 40-50FPS с просадками до 5-10 в городах…
                                                      0

                                                      ну, что значит "иногда"… Она всегда развращает, если поддаваться!

                                                      +8
                                                      Рассказываю свою историю. Я писал лабиринтную стрелялку с возможностью прыжка/полета типа Марио. То есть перемещение персонажа не попадало под сетку спрайтов, что должно было дать уступ с которого можно упасть, стены в-пол кирпича итд.
                                                      Вопрос в том — а как проверить недетерменированную позицию на столкновение с препятствием и как считать повреждения от монстров?

                                                      Я использовал битмаску XOR, окружая все спрайты рамкой с пикселем нулевого цвета (прозрачность). Если при наложении маски реверсом для перемещения спрайта образ спрайта на экране отличался от образа в исходной памяти — то это свидетельствовало о столкновении.

                                                      Аналогично действовали и монстры — они перемещались во внутрь спрайта персонажа и если их спрайт получал искажения внутри этой рамки (два вектора скоростей давали 8 пикселей по горизонтали, минус 2 на рамки — 6 пикселей для маневров), то наносилось повреждение.

                                                      К сожалению такая гибкая логика была недетерменированной по тактовой частоте и подобрать плавующую паузу было нереально.

                                                      Кроме того надо было добавить и звуковые эффекты. Но там я нашел недокументированный мапхак — если быстро менять палитру при отрисовки спрайтов, то это вызывает колебание динамика но визуально совершенно незаметно, если только не забыть вернуть палитру после отрисовки в исходное состояние.

                                                      Вот что из этого вышло: www.youtube.com/watch?v=zs2HA6eQY88
                                                        +2
                                                        Счастливое время, когда можно было потратить пару дней времени на оптимизацию. Сейчас так не получается — время жизни стало куда дороже, а железо — дешевле. Хотя удовольствие от того, что удалось ускорить расчет или запихнуть алгоритм в тесную память контроллера — ничего уже не заменит :(
                                                          +5
                                                          Как только программист сам начинает пользоваться результатами своей работы, а не получает премию за досрочно закрытые таски — взгляд на вещи меняется ;)
                                                            +4
                                                            При этом хорошие игры начали создаваться намного медленнее. Если на разработку игры уровня Battletoads под NES в те времена уходило около года, а Battle of Olympus вообще был создан тремя людьми за полгода, то сейчас по-настоящему хорошую игру за год уже не сделать.
                                                            Мало того, после выхода ещё года два будут выходить патчи, потому что системы усложнились настолько, что добиться стабильной работы кода на всех пользовательских устройствах сразу попросту невозможно.
                                                              0
                                                              Вы не правы, раньше просто это необходимо было делать практически везде, чтобы хоть как-то шелелилось. А сейчас те задачи, где это необходимо, просто не доезжают до СНГ.

                                                              В каком-нибудь Сони вполне себе люди и сейчас упарываются тактами и байтиками чтобы, например, их новый GPS-чип работал быстрее и еще меньше выедал заряд потому, что теперь целевая платформа это даже не смартфон, а наручные часы.

                                                              Но, понятно, что никто больше не разрешит тратить неделю, чтобы оптимизировать работу какой-то формы, чтобы она могла нормально работать на 386 процессоре (условно).

                                                              Благодаря этому, такие формы могут заменять бумажные даже в условном кофейном ларьке, куда раньше было слишком дорого внедрять что либо т.к. сильно много времени требовалось на разработку софта с заданным набором требований (либо слишком редкие и дорогие разработчики, которые могли бы сделать такую форму на С++/asm с той же скоростью, с которой сделает современный джун на JS с фреймворками).
                                                              0
                                                              Рассказываю свою историю. Я писал лабиринтную стрелялку с возможностью прыжка/полета типа Марио. То есть перемещение персонажа не попадало под сетку спрайтов, что должно было дать уступ с которого можно упасть, стены в-пол кирпича итд.
                                                              Вопрос в том — а как проверить недетерменированную позицию на столкновение с препятствием и как считать повреждения от монстров?

                                                              Я использовал битмаску XOR, окружая все спрайты рамкой с пикселем нулевого цвета (прозрачность). Если при наложении маски реверсом для перемещения спрайта образ спрайта на экране отличался от образа в исходной памяти — то это свидетельствовало о столкновении.

                                                              Аналогично действовали и монстры — они перемещались во внутрь спрайта персонажа и если их спрайт получал искажения внутри этой рамки (два вектора скоростей давали 8 пикселей по горизонтали, минус 2 на рамки — 6 пикселей для маневров), то наносилось повреждение.

                                                              К сожалению такая гибкая логика была недетерменированной по тактовой частоте и подобрать плавующую паузу было нереально.

                                                              Кроме того надо было добавить и звуковые эффекты. Но там я нашел недокументированный мапхак — если быстро менять палитру при отрисовки спрайтов, то это вызывает колебание динамика но визуально совершенно незаметно, если только не забыть вернуть палитру после отрисовки в исходное состояние.

                                                              Вот что из этого вышло: www.youtube.com/watch?v=zs2HA6eQY88
                                                                +2
                                                                Могу рассказать свою недавнюю историю. Считал спектры в Октаве беря экспортированные в .csv текстовые двоичные данные. В какой-то момент понял, что считаются раздражающе медленно и, главное, сильно медленее чем в предыдущем проекте. Натравив профайлер (великая вещь!) обнаружил, что большую часть времени скрипт проводит внутри октавовской функции bin2dec() (которая реализована как any2dec(param,2) и служебных функций копирования строк. Переписал функцию руками — ускорилось на порядок. Заинлайнил функцию преобразования в основной скрипт — еще процентов на 30%.
                                                                Это к слову об эффективности стандартных библиотек и своих велосипедов.
                                                                  +4
                                                                  А как вам такое, перепаять в EC-1843 (советский аналог IMB совместимых) «тактовый герератор» с 4Гц до 8 или 9, точно уже не помню.
                                                                  Вывести отдельно на платке оба эти генератоы с переключателем, плату сам травил, брал соляную кислоту и у мамы таблетки «гидроперита» (использовали для окраски волос).
                                                                  Звал друзей в гости и показывал разницу между дефолтом и моим хаком.
                                                                  93 год (никаких интернетов и гайдов, только советы «борадатых дядек инженеров»), 15 лет :) когда все паял, руки ОЧЕНЬ дрожали, это была дорогая техника, очень боялся поломать.
                                                                    +2

                                                                    Я в те годы только сумел пропатчить boot-сектор для EC-1841 на дискетке так, чтобы дисковод переходил в 80-дорожечный режим из 40-дорожечного, так что у меня даже загрузочная дискета имела размер 720 кб.

                                                                      +1
                                                                      Кнопка «турбо» в ЕС варианте :))
                                                                      0

                                                                      Отличная история! Браво!
                                                                      Сколько сил тратилось на поиски "правильных" ассемблерных команд, использования арифметики с int вместо float.

                                                                        +2
                                                                        Подобные игры со стеком применялись при выводе спрайтов в стратегии «Черный ворон» 1997года на ZX-SPECTRUM. Я этот алгоритм когда то оттуда вытащил, когда стало интересно как эта игра успевает так быстро отрисовать спрайты.
                                                                          0
                                                                          Копирование через стек применялось любым писателем демо для Спекки задолго до Чёрного Ворона.
                                                                          +1
                                                                          Точно такая же идея с копированием байтовых пар стеком, в том числе с точно такими же решениями проблем порчи стека прерываниями и разного порядка байт, широко применялась в зарубежном и особенно в отечественном софте для ZX Spectrum. Также были и варианты, типа пар ld hl,NNNN:push hl (храним данные прямо в команде).
                                                                            +2

                                                                            Прекрасная история, люблю такие. Превозмогания всегда захватывающи почему-то. Вспомнилась классическая «История одного байта».

                                                                              0
                                                                              Это был идеальный хак!
                                                                              За исключением варианта прерывания в момент копирования последнего тайла, когда запись данный может вылететь за экранное пространство… Но ведь этого никто не увидить, а старые компы… Что ж, они порой висли по загадочным причинам… :)
                                                                                0

                                                                                за экраном можно для этого буферок предусмотреть ) Упадёт в него — и фиг с ним, главное чтобы ничего не упало.
                                                                                (вот если бы кроме этого и память была впритык и считалась вплоть до отдельных байтов — там было бы интереснее)

                                                                                  0
                                                                                  Можно конечно. Но это очень старый комп. Фиг его знает какая там карта памяти, могло впритык к области видеопамяти что-то быть. Могло и не быть.
                                                                                  Правильным решением тут было бы отключать прерывания на момент копирования, но. Это могло бы искажать генерируемый звук. Или в принципе невозможно в этом компьютере.
                                                                                0

                                                                                Моим последним шагом в исследовании деталей Z-80 на спектруме было измерение времени исполнения недокументированных команд.
                                                                                У меня был Profi с 1024К памяти. Верхняя страница переключаемая, нижняя — ПЗУ (обычно), вторая — экран. Самая стабильная — третья (с 16К до 32К).
                                                                                Размножал туда нужную команду, ждал прерывания и после него запускал. По следующему прерыванию (20ms) смотрел, докуда дошагал регистр PC. Взяв за эталон NOP, который должен занимать минимум (4 такта), 16Кб ему хватало, чтобы следующее прерывание возникло в пределах одной страницы, ну разве что с хвостиком (3,5Мгц/50 = 70К тактов, а в 16К NOP-ов 65,5К тактов, т.е. за одно прерывание, если забить память до верху одной командой мы никак не дошагаем).
                                                                                Реальность идеи проверял на известных (документированных) командах и табличке с тактами. И вот там возникли вопросы… Потому что полученные метрики внезапно не соответствовали табличке. Вкратце, наблюдаемое количество команд в пределах одного прерывания для 8-тактовых оказалось не в точности вдвое меньше 4-тактовых. И даже не плюс/минус команда. Расхождение было в целом в пределах 20%, что очень удивило (вроде всё кажется очень детерминистично, а тут внезапно такой непорядок!

                                                                                  +1

                                                                                  refresh памяти. Есть быстрые страницы и медленные (которые адресуются аппаратно вместе с видео контроллером.). Если читать ищ медденной страницы хотят оба — процессор приостанааливается. Если все страницы быстрые (пентагон, атм) то различия начинают проявляться в турбе. А в желтом скорпионе, емнип, даже бещ турбы все команды выравнивались до четного числа тактов. Тоже из-за его видноконтроллера

                                                                                    0

                                                                                    Медленная — до 32К. Выше быстрая. Там и проверял.

                                                                                      0

                                                                                      не знаю особенностей профи, но даже в фирменных 128 страницы делились не так и по-разному. Ниже 32 это только zx48 и его клоны, даже пнтагон48 имел всю памятт быстрой.

                                                                                        0

                                                                                        Ну вот я тоже слышал (а с интернетом в те времена было ещё туго), что первые 16К оперативки — небыстрые, поскольку доступ разделяется с выводом видео. Чтобы не гадать, так это или нет в профи — просто взял начиная с 32К, чтобы не было сомнений.
                                                                                        (а самая верхняя страница — переключаемая. Не знаю, влияет ли это хоть как-то на скорость, но чтобы исключить сомнения остаётся только третья, с 32 до 48К адресов)

                                                                                          0
                                                                                          Тормозят не окна памяти, а именно страницы. Т.е. если на компьютере с раздельными полями включить тормозящую экранную страницу в #c000, она будет тормозить и наверху тоже. При этом набор медленных и быстрых страниц на разных оригинальных моделях по нумерации разный. В Profi же, насколько я знаю, не было медленной памяти, по крайней мере в версии 3 и позже, и при желании развёртку можно было переделать на Pentagon'овскую.
                                                                                            0

                                                                                            С Пентагоном не встречался (только слышал про него). В те годы не было алиэкспресса и даже озона, приходилось везде ходить ногами и проверять, что есть в наличии. В наших краях был ATM-Turbo, продавался от готовых вариантов до голого куска текстолита. А потом всё понемногу пошло на убыль, и Profi я взял, кажется, в 1995-м, когда уже даже у нас в далёком замкадье массовым компьютером был 286, а в компьютерных фирмах можно было свободно купить 486 и позже пентиум.


                                                                                            Тем не менее, возвращаясь к началу ветки: если там действительно везде одинаково быстрая память, гэп в измеренных таймингах тем более любопытен.

                                                                                  +1

                                                                                  Недавно читал статью о старом железе, и с удивлением узнал, что оказывается в первых IBM PC обновление DRAM было реализовано при помощи программируемого таймера и контроллера DMA. При этом можно было перепрограммировать таймер, чтобы обновлять DRAM чуть пореже (до определённого предела, конечно), и выжать чуть больше производительности:
                                                                                  https://www.reenigne.org/blog/how-to-get-away-with-disabling-dram-refresh/


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

                                                                                    0
                                                                                    Декранч и перенос данных стеком и сейчас пригождается в дебрях STM32.
                                                                                    Тот же FIR декранчем и стеком отбивает по 60% быстродействия. Да и авторы официальных библиотек считают по 8 сэмплов за цикл, а не по 1. Жива еще старая школа.
                                                                                      +2
                                                                                      Моя история увеличения… только не производительности, а плотности печати картинки на матричном принтере. У папы на работе был дос, кто-то принес програмку печатающую, скажем, девушек в определенном виде. Я дизасемблировал эту программку чтобы понять как она распаковует графические файлы. Помню возился долго ибо было видно, что прога была скомпилирована с языка высокого уровня. Я написал свой вариант на асме + ноухау: печатал одной иголкой вместо девяти. Скорость упала в 9 раз, зато плотность печати возросла и напечатанные картинки были просто супер
                                                                                        0
                                                                                        Как говаривал мой друг — Прочитал, как стакан водки хлопнул! Отлично!
                                                                                          0
                                                                                          Если сработает прерывание, то повреждение произойдёт там, куда указывает S — на экране. Повреждение сохранится только до перерисовки следующего кадра

                                                                                          Таким образом, при срабатывании прерывания и повреждении ячеек памяти непосредственно перед текущим отрисовываемым тайлом повреждение будет устранено при отрисовке следующего тайла. А поскольку отрисовка тайлов выполняется в буфере, который не отображался до полной отрисовки (и выполнения вертикального обновления), никто никогда не увидит повреждения.

                                                                                          Это прекрасно!

                                                                                            0

                                                                                            Огромное спасибо за статью и перевод!!! Очень интересно написано. И результат отличный!

                                                                                              0
                                                                                              Данная статья — лишнее подтверждение, что надо читать документацию, господа. И чем внимательнее, тем продуктивнее вы будете. Если кто подумал, что тут про оптимизацию, то это не совсем так. Ибо улучшение результата состоялось благодаря архитектуре конкретного железа, а не оптимизации самого кода в универсальном его понятии.
                                                                                                0
                                                                                                Лет 10 назад делали контроллер управления аквариумом на платформе ATMEGA32.
                                                                                                Софт автор писал на BASIC — получилось весьма неплохо, я сам потом пару поделок на этом басике сделал — очень приятная штука и ASMа совсем не хотелось, при всей моей любви к нему и ностальгии.
                                                                                                  0

                                                                                                  Был у меня в лихие 90е PC 486dx2-66. И какой-то тетрис под DOS. У ПК была кнопка “turbo”, которая переключала частоту ЦП 33/66MHz. Что позволяло снизить скорость падения фигурок тетриса вдвое. Таким же образом можно было замедлить еще несколько игр.

                                                                                                    0
                                                                                                    Есть русскоязычный журнал Downgrade, в нём попадаются похожие статьи; в частности, в Downgrade №28 (страница 34) и Downgrade №29 мои статьи про историю ассемблерных оптимизаций на БК 0010.
                                                                                                      0

                                                                                                      А зачем было рисовать все спрайты заново? Это же скроллер, можно скопировать весь экран одним махом в другое положение, а дорисовать только спрайты с краю. Имхо это было бы быстрее.

                                                                                                        0
                                                                                                        Как минимум, чтобы анимация у этих спрайтов могла работать. Но еще может быть, что все эти оптимизации были нужны только для тех спрайтов, которые перерисовывать приходилось полностью.
                                                                                                        0
                                                                                                        Мы в своё время (~91-92 год), на очень прогрессивной в то время IBM PC XT двигали для таких фокусов указатель на видеопамять. Получался очень хороший и плавный скроллинг.

                                                                                                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                                                                        Самое читаемое