Threads on Z80
Привет, друзья.
Хочу поделиться с вами своими наработками. Сперва хочу извиниться за то, что постоянно буду использовать свой ассемблер ASAM. Увы, он работает только под 32-бита, у этого есть ряд причин, но почему это так, — не сейчас.
Теперь о сегодняшней теме, о параллельных потоках. Начиная с 2003 года я постоянно использую два типа потоков на Speccy: первый, мой любимый — это сквозные функции; и второй, о котором пойдёт речь — это классические потоки. Я приведу пример самых простых потоков, и если вам это нужно — вы легко сможете доработать его до своих нужд.
Thread'ы очень просты. Всё сводиться к двум основным процедурам — это создание и переключение потоков. Текущий вариант потоков эволюционировал из простых переключений между блоками памяти в переключение между страницами, что очень удобно для decrunch'ингов чего-нибудь, или для распаковок данных. Хочу отметить, что впервые оправданное использование потоков я ощутил при coding'е под ts-conf'у.
Как работают потоки в моём варианте. Переключатель потоков происходит на прерываниях. Поэтому предполагается, что мы в режиме IM2, и каждая задача переключается сразу после окончания прерывания, при том условии, что он в режиме idle — потоку нечего делать, и он исполняет циклически процедуру SwitchThread.
Это очень удобно, поскольку прерывание начинается с PUSHA, и всегда заканчивается POPA.
Вот, в аккурат перед POPA и происходит вызов процедуры CALL_SwitchNextThread, которая подставляет стек и страницу нужного потока. Все переключения страниц — вне прерывания, а значит внутри потока, и они происходят вызовом процедуры OutBC. Согласен, что вызов процедуры OutBC занимает такты, но при этом отсутствуют коллизии, и нам не нужно задумываться — где и какая страница включена.
В процедуре переключение страниц учитывается текущий отображаемый экран.
Теперь о самой процедуре переключения потоков, вызываемой из прерываний:
Вот собственно это основа, которая будет вам переключать потоки. Немного разберём код. Я большой любитель макросов, они дают возможность делать переносимый код на разные платформы. Макрос IORQ.MAC.PAGE_SAVE и IORQ.MAC.PAGE_RESTORE — эти макросы определяются персонально для нужной модели.
Например для режима ZX это будет выглядеть так:
… а для TS-CONF так:
Таким образом я компилирую два вида кода, каждый из которых под нужную платформу, и в обоих вариантах на стек сохраняется, и восстанавливается активная в в текущий момент страница. В режиме ZX на стек сохранится два байта, из которых один — это страница, а в режиме TS будет два байта — для page2 и page3.
Теперь про IORQ.MAC.THREAD_BEFORE и IORQ.MAC.THREAD_AFTER.
Это очень важные макросы, позволяющие на стек сохранить нужные вам параметры. В режиме ZX у меня там пустышки, как бы регистров и страницы для каждого потока хватит за красивые глаза. Но вот в режиме TS у меня там хранятся регистры DMA, что даёт возможность в двух потоках параллельно использовать DMA.
TS слишком сложен, что бы к работе с ним подходить по классике ZX'а. Поэтому в режиме работы TS код этих макросов выглядит вот так:
Однако, в примере под TS я так же сделал пустышки, поскольку эти макросы — часть основного движка. Если я его сюда прикручу, то будет очень сложно понять что к чему.
Теперь давайте рассмотрим как выглядит обработчик прерывания:
На прерывании мы сохраняем страницу (или страницы) — в зависимости от конфигурации (ZX или TS), исполняем EFFECT, сразу после чего переключаем поток. Если поток у нас один, то он автоматом будет сохраняться и тут же восстанавливаться.
Ещё хочу обратить внимание на StackIntSP, тут у меня буфер для прерывания, размер которого отдельно конфигурируется в зависимости от модели платформы. Например для ZX мне всегда хватает 64 байта, а вот в TS уже надо 128 байт.
Точно такой же буфер для стека используется в процедуре переключения потоков — для того, что бы не произошло прерывание в момент переключения нужной страницы для текущего потока. Это конечно-же траты памяти, но тут никак, приходиться жертвовать ради технологии, иначе — путь сложного кодинга сквозных процедур.
Теперь про SwitchThread:
Эту процедуру нужно вызывать тогда, когда вы чётко знаете, что в текущем потоке вы всё сделали и готовы отдать оставшееся время процессора на соседний поток.
Обратите внимание, что постоянно защёлкивается переключатель потоков на прерывании с помощью отключения вызова процедуры по адресу SWTHRx8. На прерывании я его всегда возвращаю в режим вызова процедуры, в вот в процедуре SwitchThread — можно убрать восстановление вызова переключателя потоков.
Это даст возможность регулировать приоритеты. Ведь, по сути, если от прерывания для соседнего потока вы оставили времени на 200 тактов, то ровно столько будет исполняться соседний поток, и тут же на прерывании переключится назад. Это будет не очень честно, но зато в текущем режиме, в не честном, можно делать потоки ONE-FRAME, что благополучно используется в gift'е дорогому Вовке. Вот там в одном потоке — генерируется очередной эффект на заднем плане, а на втором потоке — летают буквы по экрану. И это самые, что не на есть честные потоки, которые крутятся ONE FRAME.
А вот так будет выглядеть основной код, с использованием этих процедур:
Этот пример создаст второй поток по адресу 0C080H, в странице 17, а основной исполняемый код автоматом становится первым потоком. Причём при создании потока он данные скопирует из адреса ThreadInThePAGE, в 0C080H, ну а текущие данные можно использовать как угодно.
Ещё особенность: перед данными кода по адресу 0C080H будет располагаться стек этого потока, и глубина его будет, как вы уже догадались, 128 байт. Один поток будет стремиться переключить бордюр в красный цвет, а второй поток в синий, ну и прерывания что-то там будут делать, например играть музыку, эффекты всякие показывать.
В примере есть два макроса MAC.ThreadBegin и MAC.ThreadEnd, они создадут перед и после потока заголовок для переброса данных в страницы. Так же код потока будет между командами компилятора Phase 0C080H и UnPhase. В общем — я любитель автоматов, что бы всё выглядело красиво.
Вот тут можно скачать пример.
Распишу более детально что там внутри.
Два главных файла:
THZX.AZ8 путь для режима ZX
THTS.AZ8 путь для режима TS
В этих файлах и запускается пример кода потоков.
SYS.AZ8 и к нему под нужную платформу SYSZX.AZ8, SYSTS.AZ8 — конфигураторы и процедуры для переключения памяти. А так же прерывания и процедуры работающие с потоками.
Так-же в архиве находится сам компилятор ASAM.EXE и THREAD.BAT, который всё скомпилирует и поместит итоговый результат в THREAD.TRD.
Далее — дело техники.
Хочу поделиться с вами своими наработками. Сперва хочу извиниться за то, что постоянно буду использовать свой ассемблер ASAM. Увы, он работает только под 32-бита, у этого есть ряд причин, но почему это так, — не сейчас.
Теперь о сегодняшней теме, о параллельных потоках. Начиная с 2003 года я постоянно использую два типа потоков на Speccy: первый, мой любимый — это сквозные функции; и второй, о котором пойдёт речь — это классические потоки. Я приведу пример самых простых потоков, и если вам это нужно — вы легко сможете доработать его до своих нужд.
Thread'ы очень просты. Всё сводиться к двум основным процедурам — это создание и переключение потоков. Текущий вариант потоков эволюционировал из простых переключений между блоками памяти в переключение между страницами, что очень удобно для decrunch'ингов чего-нибудь, или для распаковок данных. Хочу отметить, что впервые оправданное использование потоков я ощутил при coding'е под ts-conf'у.
Как работают потоки в моём варианте. Переключатель потоков происходит на прерываниях. Поэтому предполагается, что мы в режиме IM2, и каждая задача переключается сразу после окончания прерывания, при том условии, что он в режиме idle — потоку нечего делать, и он исполняет циклически процедуру SwitchThread.
Это очень удобно, поскольку прерывание начинается с PUSHA, и всегда заканчивается POPA.
Вот, в аккурат перед POPA и происходит вызов процедуры CALL_SwitchNextThread, которая подставляет стек и страницу нужного потока. Все переключения страниц — вне прерывания, а значит внутри потока, и они происходят вызовом процедуры OutBC. Согласен, что вызов процедуры OutBC занимает такты, но при этом отсутствуют коллизии, и нам не нужно задумываться — где и какая страница включена.
В процедуре переключение страниц учитывается текущий отображаемый экран.
OutBC: PUSH BC AF
AND 11110111B
LD B,A
LD A,(OutSCR)
OR B
LD (OutPort),A
LD BC,32765
OUT (C),A
POP AF BC
RET
Теперь о самой процедуре переключения потоков, вызываемой из прерываний:
CALL_SwitchNextThread:
POP HL
LD (SWTHRx7+1),HL
POP DE ;GET PAGE INFORMATION
IORQ.MAC.THREAD_BEFORE
LD HL,(THREAD_REC_PTR)
LD (SWTHRx1+2),HL
SWTHRx1 LD (0),SP
INC HL
INC HL
LD (HL),E
INC HL
LD (HL),D
INC HL
LD SP,StackThreadSwitchSP
SWTHRx3 LD (THREAD_REC_PTR),HL
LD E,(HL)
INC HL
LD D,(HL)
INC HL
INC HL
INC HL
LD A,E
OR D
JP Z,SWTHRx3
LD A,E
DEC A
OR D
JR NZ,SWTHRx2
LD HL,THREAD_REC
JP SWTHRx3
SWTHRx2 LD (SWTHRx4+1),DE
DEC HL
LD D,(HL)
DEC HL
LD E,(HL)
IORQ.MAC.PAGE_RESTORE_DE
SWTHRx4 LD SP,0
IORQ.MAC.THREAD_AFTER
PUSH DE
SWTHRx7 JP 0
Вот собственно это основа, которая будет вам переключать потоки. Немного разберём код. Я большой любитель макросов, они дают возможность делать переносимый код на разные платформы. Макрос IORQ.MAC.PAGE_SAVE и IORQ.MAC.PAGE_RESTORE — эти макросы определяются персонально для нужной модели.
Например для режима ZX это будет выглядеть так:
.IORQ.MAC.PAGE_SAVE
LD A,(OutPort)
PUSH AF
..
.IORQ.MAC.PAGE_RESTORE
POP AF
CALL OutBC
..
… а для TS-CONF так:
.IORQ.MAC.PAGE_SAVE
PUSH HL
PUSH BC
LD BC,TSL.RAMPage2
IN L,(C)
INC B
IN H,(C)
POP BC
EX (SP),HL
..
.IORQ.MAC.PAGE_RESTORE
EX (SP),HL
PUSH BC
LD BC,TSL.RAMPage2
OUT (C),L
INC B
OUT (C),H
POP BC HL
..
Таким образом я компилирую два вида кода, каждый из которых под нужную платформу, и в обоих вариантах на стек сохраняется, и восстанавливается активная в в текущий момент страница. В режиме ZX на стек сохранится два байта, из которых один — это страница, а в режиме TS будет два байта — для page2 и page3.
Теперь про IORQ.MAC.THREAD_BEFORE и IORQ.MAC.THREAD_AFTER.
Это очень важные макросы, позволяющие на стек сохранить нужные вам параметры. В режиме ZX у меня там пустышки, как бы регистров и страницы для каждого потока хватит за красивые глаза. Но вот в режиме TS у меня там хранятся регистры DMA, что даёт возможность в двух потоках параллельно использовать DMA.
TS слишком сложен, что бы к работе с ним подходить по классике ZX'а. Поэтому в режиме работы TS код этих макросов выглядит вот так:
.IORQ.MAC.THREAD_BEFORE
PUSH BC AF
IORQTS.MAC.DMA_WAIT
POP AF BC
IORQTS.MAC.DMA_ALL_REG_PUSH ;PUSH DMA REG x8 Byte
..
.IORQ.MAC.THREAD_AFTER
IORQTS.MAC.DMA_ALL_REG_POP ;POP DMA REG x8 Byte
..
Однако, в примере под TS я так же сделал пустышки, поскольку эти макросы — часть основного движка. Если я его сюда прикручу, то будет очень сложно понять что к чему.
Теперь давайте рассмотрим как выглядит обработчик прерывания:
INT: PUSHA
IORQ.MAC.PAGE_SAVE
LD (INx5+1),SP
POP HL
LD SP,StackIntSP ;STACK FOR INTERRUPT
PUSH HL
CALL EFFECT ;INTERRUPT PROCEDURES
IORQ.MAC.PAGE_RESTORE
INx5 LD SP,0
SWTHRx8 CALL CALL_SwitchNextThread
LD A,205
LD (SWTHRx8),A ;EI FOR INTERRUPT THREAD SWITHCER
IORQ.MAC.PAGE_RESTORE
POPA
EI
RET
На прерывании мы сохраняем страницу (или страницы) — в зависимости от конфигурации (ZX или TS), исполняем EFFECT, сразу после чего переключаем поток. Если поток у нас один, то он автоматом будет сохраняться и тут же восстанавливаться.
Ещё хочу обратить внимание на StackIntSP, тут у меня буфер для прерывания, размер которого отдельно конфигурируется в зависимости от модели платформы. Например для ZX мне всегда хватает 64 байта, а вот в TS уже надо 128 байт.
Точно такой же буфер для стека используется в процедуре переключения потоков — для того, что бы не произошло прерывание в момент переключения нужной страницы для текущего потока. Это конечно-же траты памяти, но тут никак, приходиться жертвовать ради технологии, иначе — путь сложного кодинга сквозных процедур.
Теперь про SwitchThread:
Эту процедуру нужно вызывать тогда, когда вы чётко знаете, что в текущем потоке вы всё сделали и готовы отдать оставшееся время процессора на соседний поток.
SwitchThread:
PUSHA
IORQ.MAC.PAGE_SAVE
LD A,33
LD (SWTHRx8),A ;DI FOR INTERRUPT THREAD SWITHCER
CALL CALL_SwitchNextThread
LD A,205
LD (SWTHRx8),A ;EI FOR INTERRUPT THREAD SWITHCER
IORQ.MAC.PAGE_RESTORE
POPA
RET
Обратите внимание, что постоянно защёлкивается переключатель потоков на прерывании с помощью отключения вызова процедуры по адресу SWTHRx8. На прерывании я его всегда возвращаю в режим вызова процедуры, в вот в процедуре SwitchThread — можно убрать восстановление вызова переключателя потоков.
Это даст возможность регулировать приоритеты. Ведь, по сути, если от прерывания для соседнего потока вы оставили времени на 200 тактов, то ровно столько будет исполняться соседний поток, и тут же на прерывании переключится назад. Это будет не очень честно, но зато в текущем режиме, в не честном, можно делать потоки ONE-FRAME, что благополучно используется в gift'е дорогому Вовке. Вот там в одном потоке — генерируется очередной эффект на заднем плане, а на втором потоке — летают буквы по экрану. И это самые, что не на есть честные потоки, которые крутятся ONE FRAME.
А вот так будет выглядеть основной код, с использованием этих процедур:
ORG 32768
CALL SysInit
LD HL,ThreadInThePAGE
CALL CreateThreadCopyCodeBlockPTR
MLOOP LD A,2
OUT (254),A
CALL SwitchThread
JR MLOOP
ThreadInThePAGE
MAC.ThreadBegin 0C080H, 17
THPAx1 LD A,1
OUT (254),A
CALL SwitchThread
JR THPAx1
MAC.ThreadEnd
Этот пример создаст второй поток по адресу 0C080H, в странице 17, а основной исполняемый код автоматом становится первым потоком. Причём при создании потока он данные скопирует из адреса ThreadInThePAGE, в 0C080H, ну а текущие данные можно использовать как угодно.
Ещё особенность: перед данными кода по адресу 0C080H будет располагаться стек этого потока, и глубина его будет, как вы уже догадались, 128 байт. Один поток будет стремиться переключить бордюр в красный цвет, а второй поток в синий, ну и прерывания что-то там будут делать, например играть музыку, эффекты всякие показывать.
В примере есть два макроса MAC.ThreadBegin и MAC.ThreadEnd, они создадут перед и после потока заголовок для переброса данных в страницы. Так же код потока будет между командами компилятора Phase 0C080H и UnPhase. В общем — я любитель автоматов, что бы всё выглядело красиво.
Вот тут можно скачать пример.
Распишу более детально что там внутри.
Два главных файла:
THZX.AZ8 путь для режима ZX
THTS.AZ8 путь для режима TS
В этих файлах и запускается пример кода потоков.
SYS.AZ8 и к нему под нужную платформу SYSZX.AZ8, SYSTS.AZ8 — конфигураторы и процедуры для переключения памяти. А так же прерывания и процедуры работающие с потоками.
Так-же в архиве находится сам компилятор ASAM.EXE и THREAD.BAT, который всё скомпилирует и поместит итоговый результат в THREAD.TRD.
Далее — дело техники.
106 комментариев
Наверное, это какой-то вышел мутный вопрос. Вот чёткий вопрос: приведи, пожалуйста, 2-3 (можно больше!) примера, желательно из своих дем, где ты реально совмещаешь несколько потоков (т.е. больше двух), чтобы нам лучше понять, какого рода удобства это предоставляет в работе. Пойми правильно, я тебя не критикую; процессы переключения контекстов у тебя выглядят очень знакомо, довольно похоже на то, что пришлось закодить в ядре мне. Но хочется чётко понять возможную мотивацию дальнейшего усложнения ядра.
Примеры привести очень сложно, по скольку у меня львиная доля работ это демка. А тут за всю историю я не нуждался в более чем двух потоков, просто это не нужно. Что для демки надо? На заднем плане что-то считать и контролировать логику на переднем плане, ну а на прерываниях показывать эффекты. Вообще я давно не использовал потоки, по скольку мне приятнее сквозные процедуры, и если бы не SYNCHRONIZATION и gift WAYHACK, я бы не вспомнил о них.
Вот в WAYHACK на первом потоке происходит расчёт эффекта, а на втором потоке печатается текст, рассчитываются координаты букв, создаётся предварительная анимация.
В SYNCHR'е потоки пускаются в части с иконками и летающем кубике. Пока все смотрят на выпадающего Бина, в соседнем потоке подготавливается куб.
Ещё эти потоки, и это было впервые со страницами, были сделаны в игре WonderLust, там пока кто-то смотрит интро, выбирает какими клавишами играть, — на втором потоке генерируется лабиринт.
Прости, но в тематике Speccy, сложно применять более серьёзно потоки, ведь мы все привыкли писать код так, что бы он по сути не требовал потоков. Лёша, просто ты это прошёл, поэтому тебе проще использовать то, что у тебя наработано. И ты прав, я начиная с игры WonderLust не усложнял потоки, просто не нужно больше. Вообще когда я впервые встретился с потоками, увидел, что в большинстве случаев люди(PC) их пихают в местах где это ну совсем не надо.
Поэтому, да более чем два потока я не вижу применений.
У меня только одно ограничение, которое я как-нибудь обязательно разрешу: сейчас у меня в скрипте блокирующие команды перемешаны с неблокирующими. Это не очень существенное ограничение, но оно означает, что во время распаковки (блокирующая команда) я не могу фиксить эффекты (там команды короткие, по сути, не блокирующие). Скорее всего, я решу эту проблему выносом неблокирующих команд, или даже целиком интерпретатора скрипта, в обработчик прерываний, что можно интерпретировать, с небольшой натяжкой, как третий поток. Но пока что не было ещё ситуации, чтобы мне РЕАЛЬНО захотелось это сделать.
А что ты называешь сквозными процедурами? Я не слышал раньше такой термин.
Вот процедура PARS_MODEM будет всегда однопроходная. В ней будет хранится текущая позиция исполнимого кода, и там никогда не будет циклов. Да же не текущая позиция кода, а текущее полодение в наборе мелких участков кода. Пришёл байт, проверили закончил ли он строку, нет, вышли, если да, проверили строку, вышли. Вошли снова, проверили что надо сделать со строкой, если ответить, ответили, вышли, переключили снова в ожидание окончания строки. Таким образом если у меня будет 10 модемов, то процедура PARS_MODEM, будет вызвана 10 раз, но со ссылкой на описание нужного UARTа, или другого интерфейса. то есть в таких процедурах допускается выполнение чего-то ёмкого, но крайне редко, или эти ёмкие части кода будут исполняться поэтапно, что бы вылетать из процедуры и давать работать соседней процедуре.
Здорово что ты поднял эти вопросы на самом деле, прямо захотелось почитать немного теории по этому поводу. Чтобы поменьше велосипедов было.
Всё ООП не более чем описание правил в зависимости от данных, будет правило, что пришедшие данные это код, будет исполняться, просто ссылки в памяти на ссылки в другую память и обвязка тонной «ифов», а не ноль-ли там. Ну правда можно менеджером памяти пользоваться, пытаясь поймать exception'ы, ну тогда это ещё хуже «ифов», ибо скорость ПО резко снижается в разы.
В примере EXE_MAP_ADD_ALL_GROUP и занимается генерацией кодовых блоков в зависимости от данных в карте. Кодовые блоки лежат в таблице PROC_GROUP_TBL, которая генерируется автором уровня, и подгружается с носителя.
Удаление потока не используешь?
а так — реально даже часть операционки, чем кусок демо.
И, кстати, любой шедуллер простейший, он состоит из простых логических действий. Можно трижды написать «он простой и наколенный», но от этого ничего не изменится. Так же можно сказать, — «О, да, Vitamin, дышит простым 23% кислородом», и после сделать циничное выражение лица. Только вот вопрос, зачем эти слова? Цель какова?
Ну попробуй на АРМе из пользовательского пространства ОС сделать DI или выключить на контроллере таймер или испортить код.
По этой логике сложных программ не существует, ибо все они состоят из простых логических действий.
Прикол в том, что ты действительно реализовал самый простой циклической шедулер типа Round Robin. А есть более сложные, учитывающие приориеты и группы приоритетов, с разной вычислительной сложностью и т.п.
На АРМе я вообще не буду использовать модель MMU, не вижу в ней смысла. Меня вообще не интересует путь, где нужно защищаться от того, что автор ПО будет лезть в левые адреса, и простого переключения задач хватит на все случаи жизни. От АРМа мне нужна «молотилка данных», ну или ещё более мне важна обвязка ядра, для практического применения в разработках.
Спасибо, что делишься своими наработками.
А вот если рассматривать «защиту системы от дурака» как право на ошибку (а человек, как известно, к ним весьма склонен), то акцент несколько смещается. И точно такой же сторонник сугубо практического применения может тебе сказать «я не хочу забивать себе голову дурацкими правилами работы с системой, я хочу просто решать задачу».
Ты лично пишешь весь софт, которым пользуешься? Что ты предпочитаешь увидеть- окошко о недопустимой операции в каком-то приложении или зависший/сбросившийся компьютер? А дашь зуб, что твой софт не является «дурным» и не портит память ни при каких условиях?
И да, я практически всегда пишу себе софт, практически на все случаи жизни, потому что я постоянно сталкиваюсь с вопросами которые не решаются сторонним софтом. Именно поэтому у меня есть всякие ASAM'ы, MASON'ны, PINTELIN'ы, пакеры, линковщики, конвертеры и куча-куча всякой шняги. Кто делал со мной проекты всё это видел. Я маньяк на тему того, что график должен рисовать свои работы в том, что ему нравится, а моя задача написать конвертер из его удобства в свои потребности. Единственное, что я заставлял Олега писать музыку в MASON'е, по скольку реально нужна высокая скорость и малая память, а рт3 не даёт мне этого. Так что да, я на все случаи жизни делаю себе софт, если бы люди работающие на том же компьютере что и я не пользовались софтом требующим минимум ХР, я бы так и не слазил с 98-ой, мне от винды требуется только попасть в свой софт, дальше плевать что там на заднем плане.
Конечно я мой софт может глючить, и глючит, но не так что бы случилась катастрофа, а глюк будет лишь поводом исправить ошибку. Но при написании софта я потрачу максимальное количество времени, что бы ошибки свести к нулю.
Ты прекрасный, даже больше, пример принципа «eat your own dog's food», это просто супер! Но ты же все равно не пишешь абсолютно весь софт, включая компиляторы ЯВУ для того чтобы писать всякие ASAM'ы и т.п. Не пишешь музыкальный плеер, в котором слушаешь музыку пока набираешь этот текст (кстати, в браузере, который ты тоже не пишешь). И это правильно, ибо жизни не хватит. Но весь этот софт содержит ошибки, равно как и твой. Се ля ви. С этим можно только смириться и попытаться уменьшить возможный урон.
А вот чтоб не случилась катастрофа от ошибки в твоем софте- это задача ОСи, обеспечивающей это с помощью разных механизмов, в том числе и пресловутого MMU.
MMU на мой взгляд, не более чем порождение лени, просто кто-то ленится отладить свой софт, оправдывая тем, что ММУ спасёт ситуацию. Так же как подход современно кодинга, где деструкция данных предполагается при закрытии приложения, это лень. А кодер должен любить то, что делает, иначе он превращается в программиста.
В рамочку и на стенку :)
Хотя ты и перебираешь с этим, на мой личный вкус!
Знаешь, как-то очень давно был какой-то сайт где был написан эмулятор SPECCY, то ли на джаве то ли на флеше. Этот эмулятор был через браузер, и набранные очки в играх вписывались в топы. Ну там всякие соревнования EXELONов можно было проводить. И начал с авторами переписываться, простите, уже не помню кто это был, что вот эмулятор полностью защищён от взлома, потому что там, что-то применяется, что в памяти ксорится код исполняемой программы, вообщем не взломать одной бутылкой пива. Ну я сразу начал пробовать что к чему, и всё оказалось банально. Вся память 49152 байт в памяти хранилась DWORD'ами, где и вправду первый байт DWORD'а был заксоренный и видно, что он менялся, все 49152 байта в каждом DWORD'е молотились, однако третий байт этого DWORD'а не менялся. то есть видать где-то был код типа MEM[n]=MEM[n] XOR SUPER_BYTE_FOR_XORING, но автор да же не догадывался что в памяти остаётся оригинал. Там я был зарегистрирован под именем HACKER, и договорился с авторами, что я пытаюсь накрутить очки, а они придумывают метод защититься от этого. Мало того весь этот джава или флеш был такой супер ООП и так следил за памятью, что,! вникаем! — я находил в памяти, допустим, очки в Earth Shaker'е, менял на нужные байты НЕ ЗАКСОРЕННЫЕ ДАННЫЕ, то есть это биты с 16 по 23. И универсальный язык программирования выдавал мне в окошке аккуратную надпись «CRC memrory region error, correct?». Эврика!!! Отвечаешь ДА, и новые данные, которые я подставил автоматически вписывались под ксор!
Поэтому, да, я не хочу писать свой код, под вот такие вот виртуальные машины. Я уж лучше по старинке, на асме, под эмулятором Z80 быстрее будет исполняться, чем на таком вот «нативном» языке.
OMG Ты действительно не чуешь разницу между языком программирования, средой выполнения и конкретной программой, написанной на этом самом языке?
Настало время для объяснения факту
? :-D
В таком случае, ассемблера под ARM или x86 тоже нет, только машинный код. Но ты его (ассемблер) почему-то используешь, причем с макросами, повышающими сопровождаемость кода, иногда в ущерб размеру памяти или скорости работы. Я вижу тут банальное лукавство и самообман.
Код на асме, к сожалению, далеко не всегда оптимален. Хотя бы по причине человеческого фактора — слишком много деталей чтобы они уместились в голове у программиста. А в «голове» у компилятора- запросто. Не говоря уже о том, что ассемблерный код моментально протухает сразу после написания, теряя возможность работать на другой платформе. А мог бы совершенно бесплатно получить ускорение за счет более новых технологий.
Короче, на самом деле — сильно общая тема, с неопределённым кругом.
Машинный код практически равен мнемоникам асма, самая нижайшая надстройка для того что-бы хоть как-то уйти от голых непривычных чисел, сам знаешь.
А оптимальность асма напрямую связана с уровнем КОДЕРА :) просто когда пишешь для мелкой системы — ты сжат её рамками, и это постоянно чувствуешь. История одного байта, думаю ты читал.
Да и возможность работать на другой платформе — ещё тот вопрос. от конкретной задачи всё зависит.
А когда пишешь на ЯВУ, контроль использования ресурсов имеет далеко не такое решающее значение, важнее — скорость разработки, ну и, возможно, постоянная поддержка.
текущий подход:
"[16:32:41] Абхидхармакоша Васубандху:
нужен мобильный ноут для текста и серфинга…
какой проц брать и где производительность а где просто маркетинг?
а разница между i3 и i5? или это тонкие нюансы уже???
к тому что для ворда и 20 вкладок в баузере хватит i3 и 4 гигов оперативы?"
охренеть вопрос. СОВРЕМЕННЫЙ.
я брюзга?
А еще ты не учитываешь, что мелкая система не сколько сжимает ТВОИ рамки, сколько сжимает рамки того что тебе НАДО знать. Отсюда и иллюзия прежней власти над системой после смены оной на более сложную.
Да! Поддержка! На нее обычно кладут такой длинный и толстый, что аж страшно становится.
Тебе кто-то мешает поставить старый софт с гораздо меньшими аппетитами?
Но нет, мы хотим получать новый софт, максимально функциональный, как можно быстрее, и чтоб баги правились моментально. Но платить за это (требованиями к железу)- пф-ф-ф-ф, вы о чем?!
Я не говорю, что на контроль за расходом ресурсов надо забить. Совсем нет. Но это как рынок — каждая вещь стоит столько, за сколько ее готовы купить. Наглые требования и фуфловая функциональность при наличии альтернатив — в топку. Очень наглые требования, но альтернативы не подходят или еще наглее- придется раскошелиться.
Так что не существует единственно верного способа писать оптимально.
Что тебе пришлось восстанавливать после падения плеера сида? Пропавший плейлист? Или упала вся система, не умеющая 10 лет назад толком обеспечить защиту приложений друг от друга и тебе пришлось восстанавливать сеанс?
По твоей логике получается, что любая защита- это порождение лени. Давай уберем перила на лестницах и мостах (кто-то ленится ходить правильно чтоб не падать с края), подушки и ремни безопасности из автомобилей (кто-то ленится научиться хорошо водить). Да, наличие средств безопасности, совершенно не повод эту безопасность соблюдать, у них другая задача- минимизировать ущерб, если что-то пошло не так.
И еще одна нестыковка. Себе ты оставляешь право на ошибки в софте, хотя и стараешься минимизировать их. А вот другим почему-то не хочешь давать. Глючит — никогда не буду использовать. И плевать что это первая версия. И плевать, что если все так будут делать, то следующей версии не будет. И плевать, что «деструкция данных при закрытии программы» — это рукожопие определенного программиста, а не подход современного кодинга. Не надо так.
А глюки своего софта я всегда могу поправить, если бы автором плеера был бы ты, я бы тебе написал письмо и возможно плеер больше бы такое не делал, но автор где-то там в Копенгагене.
«деструкция данных» это попытка заткнуть дырки людей которые любят у системы попросить попросить байтики, а отдавать не хотят. были бы программисты педантичными как кодеры, занимались бы деструкцией сами, тем более, что это одна-две строчки кода. Лень это всё. И в чём тут рукопожатие, экономия на двух строчках кода? Тогда можно выпилить из Z80 команду DI, будет рукопожатие, что бы HALTы не мешали работать ядру.
Т.е. ты не писал разработчику с сообщением о его проблеме только потому что он в Копенгагене? Серьезно?
Давай ты откроешь исходники своих проектов для аудита на предмет педантичности, неленивости и «деструкции данных»? Заодно отделим рукожопость от невнимательности.
В случае с Робусом — это программирование железа и разработка схем.
На заре рабочей карьеры занимался программированием железа. Бесценный опыт в плане понимания, что ассемблеру не место в промышленной разработке, только в специализированных библиотеках/SDK и в минимально возможных дозах. Похожий опыт был пару лет назад, когда пришлось писать драйвер для гипервизора.
и в данном треде вы с робусом находитесь в противопожных позициях — скорость изготовления против максимального использования ресурсов, примерно так.
Максимальное использование ресурсов? Вылизанная числодробилка на ассемблере под одноядерный x86 против многопоточного варианта под x86_64 — кто оптимальнее?
Не утрируй, ни ты ни я не используем макросы в ущерб чего-то там. Только ради красоты и удобства записи. И я не утверждаю что Си бесполезен, просто это не программирование. Это упрощенная запись для математического склада ума. Поэтому на языках высокого уровня благое дело формулы всякие считать, но как только приходит вопрос в энергопотреблении, или распаковке, тут асм — и получаешь качество. Насчёт оптимизации, давай не трогать этот вопрос, лишь технология кэша сводит на нет оптимизацию, пишешь код с размахом не более 64к, и любая математика начинает крутиться на частоте ядра. Всё таки есть разница между 3 гигагерца ядра и 300 мегагерц выборки памяти. Ты же, надеюсь, не будешь доказывать, что ДДР на 1 гигагерц будет тебе давать доступ к памяти на это же частоте?
Пока ты будешь супероптимально писать код под частоту ядра, это уже нахрен никому не нужно будет.
Программист в первую очередь должен решить задачу максимально эффективным способом. В критерий эффективности в том числе входит и затраченное время, причем с очень большим весом.
Достаточно, чтобы можно было себе позволить выбирать между интересной и очень высокооплачиваемой работой в пользу первой.
Я за тебя не волнуюсь в плане зарплаты, а в плане деформации. Я вон Роба тоже пробовал лечить, пока с ним не познакомился. Ну и на работе у меня хватает таких умников, что считают первым делом конструктивно обложить хуями чей-угодно код/архитектуру, правда после ближайшего ознакомления с сабжем становятся кроткими, как гопота в военкомате.
Здесь же эта деформация к месту, ибо совпадает с предметом разговора.
NIH синдром я уже почти пережил, но код и архитектура должны решать возложенные на них задачи. Не справляются — переделать, возможно обкладывая хуями код и его авторов, внятно не умеющих объяснить причины того или иного решения.
И это — крайне важная составляющая, несущая уже дух флоу, управляющая всем богатством написанного кода, меняющая уже даже впечатление человека, а не картинку на экране.
ЯВУ! :) Хоть и простой
И кто из нас после этого программист, а кто ремесленник?
Тип 1: делать код, «чтобы было». Вот прочитывается где-то описание многозадачности или просто интересно поиграть в большие машины — и пишется вот такая программа как у тебя на скриншоте. Это всё замечательно, но конечно у больших машин огромный запас вычислительной мощности + архитектура, которая вообще говоря была продумана для такого использования. И выясняется, что DI делать нельзя, что память не защищена, что машина не приспособлена. И получается какое-то разочарование, вроде прозвучавшего в твоём первом комментарии.
Тип 2: делать код для решения конкретных задач. В старых демах было обычным делом сделать декранч или распаковку с бегущей строкой или просто на чёрном экране. Реализовав двухпоточность, оказывается можно делать дему почти без швов или даже совсем без швов. Конечно, все ограничения выше остаются в силе. Ты забыл ещё упомянуть координацию потоков, чтобы данные одного потока поступали в другой без разнообразных конфликтов. Тем не менее, на выходе мы получаем то, что раньше делать не выходило.
По сути выходит, что в первом случае у тебя стакан наполовину пост, а во втором — наполовину полон.
Robus сделал свой вариант потому что у него возникла такая задача и он ее прекрасно решил. У меня ее (задачи) не было (ну не пишу я демы, извините), а встраивать многопоточность в прикладное ПО по принципу «чтоб было» — это довольно глупо.
Забей короче, спорить не о чем, скриншот у тебя клёвый. Я тоже болел в 1990е окнами, но у меня всё же не до того было всё запущено :)
Сарказм- это как приправа. Уберешь совсем — все станет пресным до ужаса (или сладким до слипания). Добавишь слишком много — может стошнить. Значит надо соблюдать дозировку:) Правда, она у каждого своя…
Это средство обеспечения надежности системы, в частности многозадачной.
Ваш адмирал Ясен Хуй.
«Не надо искать в моем комментарии цинизм, сарказм или еще какой негатив»
:))
мышедрочерстваграфического интерфейса:)На скриншоте честно-многозадачно работающие системный монитор, консоль, карта памяти и мегатормозной просмотрщик текста.
Ваш Лейтенант Вежливость.
Равно как и идея использования многопоточности именно в демах не отменяет озвученных спектрумовских проблем оной, возникающих во время разработки на пути к ровно одному сценарию использования.
Без подписи
потоки полезны будут как вариант выполнения кода под конфой.
под спеком эта обвязка относительно много ест.
Адекватность восприятия концепций обуславливается корректностью морфологии семантических единиц.