Комментарии 155
К примеру, автоматическую векторизацию кода (приведение обычных скалярных вычислений к параллельным вычислениям с использованием SIMD: SSE, AVX и т.п.), компиляторы делают весьма посредственно.
Уже давно для этого дела придуманы интринсики.
Когда у меня только появился Core i7-2600K (первая линейка с AVX), я ради интереса переписал несколько операций обработки изображений, например, свёртку, на ассемблере с использованием новых инструкций. Впечатлился. Но когда возникла необходимость переписать код с x86 на x64, я ощутил боль, потому что писать нужно, фактически, заново.
Так я открыл для себя инстринсики — низкоуровневые абстракции для работы с регистрами на низком уровне, не опускаясь до ассемблера. И оказалось, что это круто:
- один и тот же код прекрасно компилируется и под x86, и под x64;
- вместо пары дней реализации на ассемблере достаточно потратить пару часов;
- код, сгенерированный компилятором, оказался быстрее написанного вручную на ассемблере, потому что сложность кода такая, что у человека уже не хватает ресурсов на учёт всех нюансов архитектуры.
Интринсик – это аналог инструкции процессора. Поэтому изучая интринсики вы изучаете и ассемблер (в несколько ином синтаксисе) :)
Нет, с интринзиками нам не только не нужно заботиться об аллокации регистров, но и далеко не факт что интринзик вообще будет во что-то скомпилен. Если компилятор видит, что интринзик не делает "видимой" работы, то он вправе убрать его. Так же компилятор вправе вместо тупой трансляции кода на интринзиках в соответствующие им инструкции, применять более эффективные эквивалентные инструкции. Т.е. в отличие от ассемблерных вставок, в данном случае компилятор вправе считать себя "умнее" программиста.
Ну да, пытался интринсиками сделать код для определения быстродействия в тактах, с высокой точностью. На асме всё просто, интристиками ещё проще, но дебильный компилятор удалил часть инструкций, потому что они ничего не делают, хотя они делают, очищают конвейер. Попытался использовать ассемблерные вставки, опять косяк, компилятор не знал что CPUID меняет 4 регистра, ebx в том числе, хотя использовал этот регистр в качестве счётчика.
Компилятор GCC 8.2 вроде хороший.
Делал в прошлом году измерялку скорости кода (в тактах).
Если интересно: https://gist.github.com/jin-x/88358e9bb1d58d01b7a318d0873208e3
Если есть понимание как улучшить, буду рад ;)
А что именно вас интересует? Я писала про них давно и много разного :) например, https://habr.com/en/post/94789/
Я погуглил подобные статьи на Хабре. Обычно их просто вал (как, например, ежемесячное обсасывание SOLID), а именно про интринсики и нет ничего. Только вот это:
https://habr.com/ru/post/326900/
Видимо, не особо людям это интересно, да и на английском документации достаточно:
https://software.intel.com/content/www/us/en/develop/documentation/cpp-compiler-developer-guide-and-reference/top/compiler-reference/intrinsics/details-about-intrinsics.html
https://software.intel.com/sites/landingpage/IntrinsicsGuide/#
а именно про интринсики и нет ничего. Только вот это:
Как раз интринсики изрядно заезженная тема, возьмите любую статью по SIMD, и там будут интринсики. Переносимость у них по-моему посредственная, вы не сможете даже скомпилировать AVX-интринсики под SSE, не то что под другой процессор.
Менее известны векторные расширения (gcc, Clang, также ICC поддерживает gcc-нотацию). Здесь я выкладывал пример, полная версия.
Вот ещё пример успешной автовекторизации с незаурядной перетасовкой данных в исполнении gcc.
В своё время начинал писать статью про "высокоуровневую" векторизацию, но забросил. Может, как-нибудь допишу.
"код, сгенерированный компилятором, оказался быстрее написанного вручную на ассемблере."
Тут дело не в сложности, а что процессоры давно уже не хавают инструкции как есть и просто обрабатывают их группами. От того написание циклов через dec-jn будет не такое эффективное как написать mov-sub-jn. (пример от балды)
И те кто пишут компиляторы под х86 вкурсе про это всё. Тот же компилятор С от intel выдаёт просто какие-то неестественные оптимизации для кода, что даже банальные циклы a[i]+=b[i] начинают работать раза в 2 быстрее чем на асм. (преувеличил) (По сути генерируя код именно под свою микроархитектуру транслятора х86 в МОП-ы у процессорах)
Хотя если мы пишем под процессоры\микроконтроллеры без МОП-ов и реордеров, то асм станет эффективней по производительности многих ЯВУ. (если ты кнш помнишь сколько тактов каждая команда исполняется и какие там условия)
И те кто пишут компиляторы под х86 вкурсе про это всё
а почему вы думаете, что те, кто пишут руками, не в курсе?
я вот точно помню, что учитывал (это было ещё во времена первого пентиума, что писал уже даже не вспомню, но помню, что замеренные такты более-менее сходились с написанными в справочнике).
"а почему вы думаете, что те, кто пишут руками, не в курсе?" - Те кто занимаются этим профессионально то в курсе, но учитывать ВСЕ нюансы архитектуры достаточно трудно. Это надо под пол года чисто под одну микроархитектуру кодить чтоб начать в ней спокойно разбираться. И те трудозатраты не очень стоят того результата, ибо это же можно решить директивами компилятора и вскими инстринсиками.
ЗЫ хотя я знаю что есть некоторый класс задач который мало на каком языке можно реализовать эффективно. (в голову лезут только битовые операции, типо цикличн сдвигов и команды для работы с отдельными битами)
Есть такой небезызвестный товарищ, Агнер Фог, который пишет маны по оптимизации (и не только). Очень полезно к изучению: https://www.agner.org/optimize/#manuals
К примеру, мне очень нравилось писать резидентные программыНа Turbo C писались в два счёта.
Зубков хорош!
Всем, кто хочет изучить/понять ассемблер рекомендую начать именно с книги Зубкова.
мне больше нравится такой формат:
https://firststeps.ru/asm/tasm1.html
с https://firststeps.ru я начинал свой тур по с/с++
Пысы. Что с меня взять, я кинестетик :)
Дело не в этом, а в востребованности, конкуренции (кол-во разработчиков на вакансию), сложности языка, ИМХО. Я бы не сказал, что зарплаты программистов Python выше, чем на C++, хотя последний явно куда ближе к низкому уровню.
В области информационной безопасности, кстати, бывают вакансии с весьма достойными зарплатами (250 т.р. и выше), где в качестве требований указывают знание C/C++, ассемблера, опыт написания драйверов, знание внутреннего устройства ОС.
Не поленился сейчас и посмотрел на HH з/п ассемблерщиков. На первой странице нашёл ARM-разработчика с з/п до 350 т.р и инженера-программиста BIOS с з/п до 200 т.р. Есть ещё 160 и от 110. Так что, всё не так уж и плохо.
Со своей колокольни добавлю. В вебе сейчас есть WASM и WebGL. Достаточно низкоуровневые технологии. И сейчас довольно много задач, где они активно используются. Я например, не так давно делал конфигуратор кастомной футболки, где прямо на 3D модели футболки можно вставлять рисунки, изменять их размеры, писать и редактировать текст, поворачивать. И это все прямо на веб-странице. Половина работы здесь - это написание шейдеров на WebGL. Это конечно не ассемблер, но без понимания, как все работает на низком уровне здесь не обойтись. Вроде бы пишешь на C-подобном языке, но не покидаешь ощущение, что делаешь все на Ассемблере :)
Привет! А можно где-то увидеть результат? Очень интересно.
Пока @raamidне ответил, много всего интересного на шейдерах можете посмотреть здесь: https://www.shadertoy.com.
Я сейчас уже не помню где конкретно это использовано, вот анимация того, как работает в Google Chrome. Цифры - это обычные картинки. На анимации это не показано, но я потом еще добавил возможность выгибания текста вдоль дуги окружности.
![](https://habrastorage.org/getpro/habr/upload_files/d66/d24/cfd/d66d24cfde7f02e0ac6c9815bf59f0c4.gif)
К своему стыду не знал, что WASM это не только про графику.
Я бы сказал оно совсем не про графику. WebAssembly на самом деле самое путающее название, т.к. оно и не Web и не assembly: оно изначально задумывалась как ускорение JS в браузере, но примерно в то же время стрельнул Node.js и JS рантаймы вылезли за пределы обычного Web. Плюс на текущий момент существует довольно немало standalone runtime для запуска wasm. За час даже можно написать свой собственный. Ну а не-ассемблер он потому что ассемблер транслируется напрямую в машинный код, а wasm — в код виртуальной машины, который транслирует инструкции в машинный код, считай IR, с некоторым ассемблероподобным синтаксисом.
standalone runtime для запуска wasm
То есть запуск не самого быстрого кода на виртуальной машине вне браузера? А в чем смысл если есть куча других языков для локальный программ?
В первую очередь безопасность и простота. Как говорил создатель докер — если бы wasm был во время того как создавался докер, то докер мог и не стрельнуть.
Во-вторых, как и в случае с node js — можно шарить один рантайм между фронтом и бэком.
В-третьих, кроссплатформа и возможность run on 4 billion devices тут же под боком. Есть мнение, что edge-вычисления могут иметь немалый профит с этого.
А так есть FAQ по теме
На мой сугубо субъективный взгляд из реально обоснованных применений для десктопа сейчас остались только #3 и #9, а оттуда пол-шага и до взлома, чего уж отрицать. Точечные правки в двоичном файле похожи на генную инженерию, когда в бинарнике на 10 мегабайт меняется 5-20 байт, но это правильные 20 байт. И вот такой патч без понимания ассемблера не сочинить.
И вот такой патч без понимания ассемблера не сочинить.
а когда исполняемый файл подписан каким-нибудь сертификатом, то и сочинять такой патч становится бессмысленно.
Почему? Сертификат отрезается от файла в три минуты и вот файл уже и не был подписан. Контрольная сумма в PE-заголовке? Пересчитывается в HIEW чуть ли не автоматически...
Да, но проаерку подписи обрезанный файл не проходит. Привет неподписаным драйверам винды.
Вы так говорите будто все трои подписаны украденными у крупных разработчиков сертификатами. Но нет, они в массе не подписаны вовсе ни чем и ничего, работают
Проблема только в 64-битных Windows. И эту проверку можно отключить.
Обходной способ уже найден, но он требует некоторых манипуляций с заменой системных файлов.
(сгодится любой отозванный сертификат, который можно нагуглить в интернете)
В системах, где проверка подписи не обязательна, подпись убирается и тогда вообще проблемы нет, как верно заметили. Точнее так, даже в Андроиде такое требование очень легко обходится для несистемных приложений и просто легко для системных.
Требования к драйверам — тоже не остановят, при необходимости. А т.к. драйвера "лечатся" сильно реже usermode приложений, то и проблема совсем небольшая.
На ассемблере можно написать ВСЁ, он всемогущ!
не знаю, байка или правда, но слышал историю, как очередная версия lotus 1-2-3 писалась на асме, писалась-писалась, релиз откладывался-откладывался, в то время, как excel зарелизился в разумные сроки и начал подминать под себя рынок.
- Создание загрузчиков
- Вы не поверите, но ассемблер можно использовать и для написания игр
можно объединить эти пункты и запихать игру в загрузчик :)
Для FAT12 вообще не проблема, писал интереса ради https://pastebin.com/a7TLUNht
Но благо что уже отошли от этого в сторону UEFI. Т.к. 16-битный режим устарел капитально, а переключение в защищенный режим заставляет реализовывать поддержку оборудования самостоятельно
О, я тоже недавно писал PONG на ассемблере. Правда, для БК 0010. И в загрузчик он не умещается, потому что поддерживает звуки через Covox. Зато Станислав Масловский написал полноценный Тетрис для БК в 512 байт
Что, ассемблер уже компилятором стал? Он же всегда был транслятором!
Хочу сразу оговориться, что правильно говорить не «ассемблер» (assembler), а «язык ассемблера»
Пусть меня заминусуют, но не правильно говорить «язык ассемблера», у ассемблера нет языка, поскольку без контекста конкретной марки процессора не понятно, какие мнемоники писать. Ассемблер это транслятор (вернее завершающий этап трансляции) с мнемоник процессора в коды процессора (упрощённо), поэтому на Арм «язык» будет один, на x86 другой. Соответственно, правильно говорить: Транслятор, и конкретно сборщик (Ассемблер) размещает уже транслированные данные в адресном пространстве (именно для него пишется в начале программы с какого адреса размещается программа), но поскольку от этого зависят коды обращений к памяти, то в трансляции он принимает непосредственное участие.
При этом, опять же, всё это относится только к процессу, а не к написанию, то есть, фактически это не Pascal, а FreePascalCompiler и т.п. Я думаю, что термин язык, тут вводить вообще не уместно и что-то вроде «Программа для транслятора» (ближе к понятию язык было бы монструозное: «система комманд процессора» или «представление комманд процессора», «язык комманд процессора») тоже совсем не внятно. Предлагаю термин «Низкоуровневая программа», можно уточнить для какого процессора, если нужно. Хотя, конечно, я понимаю, что «Ассемблер», это столп, уважуха и +10 в карму, а низкоуровневая программа это «ГраммарНаци».
"Низкоуровневая программа" – не очень понятный термин. Программа на C, работающая с оборудованием – это низкоуровневая программа? Да и к чему изобретать замену уже устоявшимся терминам? Понятие "язык" в данном случае вполне уместен, хоть он и имеет разные диалекты и разные правила написания под разные процессоры. "Язык ассемблера" можно сравнить с "языком людей", который в то же время может быть "русскими", "английским", "французский" или "японским" (x86, ARM, Z80...). При этом каждый из этих языков может иметь несколько диалектов (MASM, NASM, GAS).
Тот же Python тоже бывает версии 2 и 3, которые довольно серьёзные различия. Тот же C++ тоже имеет некоторые различия в стандартах C++03 и C++20. И даже C/C++ одной и той же редакции будет иметь отличия в интринсиках и библиотеках для разных ОС/процессоров (более того, даже компиляторы привносят свои нюансы: VC++ vs GCC). Может, тогда и эти языки не стоит называть языками?
Ассемблеры состоят из нескольких модулей: транслятор в объектный модуль и сборщик (линкер; хотя есть и 2 в 1: тот же fasm), поэтому называть линкер ассемблером как-то не очень корректно (хоть он и переводится как "сборщик").
В английском данная тема проста, элементарна, понятна, последовательна.
Assembly code is converted into executable machine code by a utility program referred to as an assembler.
In communications and information processing, code is a system of rules to convert information—such as a letter, word, sound, image, or gesture—into another form, sometimes shortened or secret, for communication through a communication channel or storage in a storage medium.
Любой "язык", будь то русский, формальная логика или С++ - это алгоритм (т.е. набор правил) совместно с алфавитом. Языки ассемблеров - не исключение.
Как же я вас понимаю! Сам увлекался ассемблером. Однажды, ради челленджа написал консольную программу, которая решала задачу расстановки ферзей. Размер области принимался в качестве параметра командной строки. В откомпилированном варианте программа занимала ... 182 байта. Еще написал программу обхода конем всех клеток шахматного поля, 516 байт.
Это действительно кайф, писать такие программы :)
Комп — это же не просто универсальный девайс для работы с информацией, а в первую очередь свобода. Важная часть которой — я могу любую программу нажать правой кнопочкой, выбрать «открыть в дебаггере» и поправить всё что мне не нравится.
Я изучаю ARM и могу сказать, что он не менее великолепен, очень интересные штуки в нём есть :)
Меня в своё время просто убила FJCVTZS
, я сначала подумал это прикол какой-то )
В x86 тоже не мало всяких gf2p8affineinvqb, ldmxcsr, mpsadbw, pclmulhqlqdq, v4fnmaddps, vfmsubadd132pd, vp4dpwssds, xgetbv... :)
upd: Ах, вон о чём речь. Вообще, некоторые процы ARM и Java-байткод понимают.
Не знаю, почему вы именно их в пример привели, на х86_64 ближе всего к fjcvtzs будет видимо cvttss2si c проверками – но и там Javascript не упоминается, может потому что во времена PIII JS ещё из пелёнок не вылез )
После ассемблеров DEC (PDP-11) и Motorola (6800, 68000) c ассемблера x86 меня всегда тошнило и выворачивало мехом внутрь :)
За “говнориски” ты зело неправ. Видимо, ты просто в них не смог :) Но спешу тебя порадовать — их создавали не для “торчков по ассемблерам”. “говнориск” показывает ту же условную производительность, что и “шоколадциск”, только содержит при этом на два порядка меньше транзисторов, работает на порядок ниже частоте, при на два порядка меньших энергозатратах. Сама Intel не раз порывалась (432, 93x), но пипл хотел и дальше хавать говно (и это было х86), и требовал только ложку по-больше (мегагерц).
И за “разумных инженеров” замечу — ни одному действительно разумному инженеру не придёт в голову мысль сделать “что-то одно и для всего вообще”. Любое универсальное решение практически всегда и во всём проиграет частному. Почему-то в IBM в своё время не нашлось (к счастью) “разумного” инженера, который такой “А давайте видеокарту тоже на 8086 забульбеним, йопта, чё мудрить-то?!”
Почему-то в IBM в своё время не нашлось (к счастью) “разумного” инженера, который такой “А давайте видеокарту тоже на 8086 забульбеним, йопта, чё мудрить-то?!”
Ну как сказать, не нашлось
Между 8086 и 386 разница большая, на 386 кодить гораздо легче, а вот с х86-64 и х86-32 уже нет такой разницы. Так что я бы не сказал что 386 или х86-32 хуже PDP11, 68000 хотя некоторые ограничении в х86-32 мне тоже не нравятся.
Поскольку ассемблер – самый низкоуровневый язык программированияСАМЫМ низкоуровневым является машинный код, не?
Обычно ассемблер является отображением машинных кодов практически 1 в 1. Мы, например, на 1 курсе университета писали программы в машинных кодах в debug.exe, чтобы потом вздохнуть с облегчением, когда начался ассемблер. Да транслятор вычисляет размеры операндов и адреса меток за вас, но в остальном не сказал бы, что есть какая-то существенная разница. Зная ассемблер, например, вы можете в отладчике остановиться на вызове функции отображения месседжбокса ("This program is not licensed"), промотать код немножко вверх и заменить jne на je (например, обычно конечно всё сложнее) - и вот уже программа принимает любой ключ, кроме собственно верного. Затем, зная, где находится это место в коде - открываете какой-нибудь HIEW и патчите опкод перехода руками. Просматривая голые hex-коды сделать то же самое будет гораздо сложнее.
Не понял, про какие команды вы говорите в начале.
Никто не мешает вам, используя ассемблер, писать машинные коды через db :)
Машинные коды вряд ли можно назвать языком программирования. Если брать определение из Википедии, то "Язы́к программи́рования — формальный язык, предназначенный для записи компьютерных программ. Язык программирования определяет набор лексических, синтаксических и семантических правил, определяющих внешний вид программы и действия, которые выполнит исполнитель (обычно — ЭВМ) под её управлением."
Если так докапываться, то можно сказать, что и машинные коды не всегда являются самыми низкоуровневыми, ведь бывает ещё и микрокод :)
То, что можно писать через db ни о чём не гвоорит, в Pascal тоже можно писать прямо мнемониками ассемблера или использовать Goto. Речь идёт о слое абстракции: если вы умеете писать на высокоуровневом языке, то вам не нужны для этого знания более низкого уровня, если же они у вас есть, то, как правило, даже высокоуровневые инструменты позволяют копнуть глубже. Если же утверждать, что разницы нет, тогда, да, можно бубенить прямо db на любом языке и это будет высокоуровневая программа.
Это что за словоблудие? Ассемблерная мнемоника “xor eax, eax” — это всего лишь человекоудобная запись “машинного кода” 0x5d. И никак иначе ты эту операцию не запишешь. Такое “программирование в машинных кодах”, о котором ты говоришь — это тупо ручное исполнение работы ассемблеров. “Программирование в hex-редакторе”
Ассемблерная мнемоника “xor eax, eax” — это всего лишь человекоудобная запись “машинного кода” 0x5d.
Во-первых 0x31 0xC0; а 0x5D — это
pop ebp
.И никак иначе ты эту операцию не запишешь.
Во-вторых, у весьма многих инструкций есть по несколько вариантов кодирования. Например, 0x33 0xC0 — это тот же самый
xor eax, eax
.а бывает и наоборот, когда код один, а мнемоник несколько. например, nop
и xchg eax, eax
. :)
Ну да, про 0x59 не туда глянул, только сути это не меняет. А суть, повторяю, в том, что “программирование в машинных кодах” — это не какое-то ещё более низкоуровневое программирование, а всего лишь “ручное” исполнение того, с чем на 100500 порядков быстрее справляется ассемблер.
И не надо откровенной чуши про “тот же самый xor eax, eax”, поскольку “xor al, al” это совсем не то же самое.
Введите «31 c0 33 c0» на onlinedisassembler.com/odaweb и убедитесь сами.
$ echo "0x33 0xc0 0x31 0xc0" | llvm-mc -mcpu=i686 -disassemble -show-inst
.text
xorl %eax, %eax # <MCInst #15279 XOR32rr_REV
# <MCOperand Reg:22>
# <MCOperand Reg:22>
# <MCOperand Reg:22>>
xorl %eax, %eax # <MCInst #15278 XOR32rr
# <MCOperand Reg:22>
# <MCOperand Reg:22>
# <MCOperand Reg:22>>
Нет никакой разницы, да? Может, ты тогда откроешь всё же второй том «Intel 64 and IA-32 Architectures Software Developer’s Manual»? Там очень много интересного найти можно.
Один результат можно получить кучей способов, только это никак не означает, что все эти способы — одинаковые.
Нет никакой разницы, да.
Протрите экран, и вы увидите, что аргумент - не al.
Речь давно не про 0x30 (это был мой косяк). Речь про КОПы 0x31 и 0x33.
Таки почитай рекомендованную литературу. Внимательно.
В fasmg, к примеру, вы можете создать такой синтаксис, который позволит кодировать инструкцию нужным образом (31 C0 или 33 C0).
Например, для z80 мнемоника Set 0, reg, где 0 и регистр жто часть 16 битной комманды, они не берутся из данных или регистров, вот как выглядят 16 бит в машинном коде: 1100 1011 11bi tSSS, зная структуру машинного кода можно перезаписать часть комманды превратив её в переменную и всего пара инструкций позволит из регистра записать номер бита, сделав что-то вроде «set reg, reg», надеюсь я не очень сумбурно изъясняюсь. Система комманд z80 Я не хочу заявить о каких-то серьёзных преимуществах машинных кодов, я всего лишь говорю о том. что мнемоники и машинные коды это разные вещи и машинные коды это не просто числа, а система кодов.
Нет у машинных кодов никаких преимуществ, кроме тех, которые имеет, например, сваренное яйцо перед сырым.
А про “систему” знает любой программист, который имеет опыт программирования на ассемблерах. Система называется “Архитектура”, частью которой является и система команд (instruction set). Назначение всех битов в кодах команд расписано подробно в любом reference guide к конкретному процессору. И от того, как эти команды записаны — битами или в мнемонической форме, не зависит ровным счётом ничего, кроме удобства чтения.
Справедливости ради про микрокод речь не шла, конечно процессор под капотом транслирует шестнадцатеричные коды в микрокод, и если рассматривать его (ассемблер) в этом аспекте, то он уже давно не является самым низкоуровневым кодом. Но разве у нас есть возможность писать напрямую в микрокоде?
Помимо демосцены существует ещё ретрокодинг - писать не выкрутасы, как на демосцене, а самые обычные программы (почти всегда игры) для старого железа. И на этом поприще асм это альфа и омега.
Если ассемблер так хорош, почему же мы не видим десктопных программ на нем? Компиляторов? СУБД?
Потому что это сложные программы, работающие со сложными данными (массивы, структуры, хеш-таблицы). В Си мы пишем index.fields[FIELD_NAME].offset, а в ассемблере вы будете сидеть вручную высчитывать смещение от указателя. Вы потратите в разы больше времени. И какой смысл этим заниматься?
Или Питон. В Питоне мы пишем [some_dict[x] for x in values if x > 0], а в ассемблере вам надо написать кучу строчек, чтобы сделать тот же объем работы.
Вдобавок, ваша программа будет работать только на одном процессоре и не будет переносима на другие платформы. Она просто умрет, когда процессор выйдет из употребления.
Да, современные программы тяжелые и тормозят, несмотря на то, что процессор способен выполнять миллиард операций в секунду. Но, как правило, не из-за языка (даже если это Питон). А из-за неэффективных алгоритмов, большого количества дисковых или сетевых обращений, блокирующих операций в GUI треде. И просто переход на ассемблер эти проблемы бы не исправил.
Даже если нужна высокая производительность, то глупо руками писать код на ассемблере. Умно — написать эффективный транслятор, который будет без потерь транслировать высокоуровневый код в ассемблер. И примеры есть: регулярки транслируются в ассемблер, SQL-запросы, JS-код. Вот где искусство — преобразовать сложный запрос в набор низкоуровневых команд. А не сидеть часами переставлять mov и inc местами.
Вы пишете, что ассемблер дает "полный контроль над кодом". Не дает. Современные процессоры используют out-of-order выполнение команд, то есть произвольно их переставляют местами. Вы над этим не имеете контроля и скорее всего даже не знаете, как это работает. И при написании программы не можете учесть это и писать программу оптимальным образом. А вот, кстати, процессор Эльбрус действительно дает вам полный контроль над выполнением команды.
Вы пишете, что ассемблер позволяет обходиться "без стандартных библиотек". Опять же, это глупо — переходить на ассемблер ради сокращения программы. Умно — переделать компилятор, чтобы он умел выкидывать неиспользуемые функции. gcc это более-менее умеет. Ну и без библиотек тоже плохо. Что вы будете делать, когда вам понадобится множество, словарь и другие структуры данных? Что вы будете делать, когда понадобится применить к данным фильтр низких частот? Я, например, просто возьму scipy и решу проблему за пару десятков минут.
Получаемый код даже самого умного и навороченного компилятора, как правило, можно оптимизировать (как по скорости, так и по размеру).
И компьютер способен сделать это лучше человека. Почитайте про "супероптимизацию". Кратко: человек написал программу, которая генерирует все возможные комбинации ассемблерных команд, чтобы получить эквивалент данной на вход программы. Например, компьютер смог найти эквивалент из 4 команд для программы из 9 команд. Супероптимизация — это умно. Сидеть руками переставлять байты — глупо.
В продолжение, вот еще работа, где супероптимизатор использовался для оптимизации реальных программ. Выигрыш в размере составил до 4%. В том числе супероптимизатор смог применить SSE инструкции.
Ещё одна интересная область применения ассемблера – создание файлов данных с помощью макросов и директив генерации данных.
Питон это делает лучше.
Для внедрения кода в другие процессы
создания различных антиотладочных приёмов
Блоки распаковки, защиты кода
Эта дрянь не нужна. Это верный способ ввести труднообнаружимые баги или нарушить переносимость программы на другие ОС или другие версии ОС. Пример: вы используете какую-то недокументируемую функцию или смещение, а в новой версии ОС оно поменялось. Также, из-за таких любителей "защищать" код часто игры не работают под Wine. То есть, это значит что в будущем, когда Windows перейдет на другую архитектуру, их ни на чем нельзя будет запустить.
Отдельно хочется сказать про систему команд Интел. Она безнадежно плоха из-за необходимости совместимости с 8086. 8086 вышел по моему в 70-х годах, в нем 16-битные команды исполнялись строго по очереди, и с тех пор все поменялось радикально: процессоры стали 64-битные, в них используется out-of-order параллельное исполнение команд, а Интел должен тянуть это дремучее легаси. Ну представьте, например, как сложно процессору определять зависимости команд (то, что команду add dl, 1 нельзя выполнить параллельно с mov rdx, [rdi]). Команда занимает от 1 до 15 байт, с кучей префиксов. Регистры могут быть указаны в куче разных мест кучей разных способов. Данные могут читаться 1, 2, 4, 8 байтами без выравнивания. Или флаги: они тоже здорово мешают параллельности.
Лучшее, что можно сделать с архитектурой x86 — это выкинуть ее на помойку. Опять же, если брать Эльбрус, то система команд там намного современнее и логичнее, жаль только, что команды очень жирные.
Комп — это же не просто универсальный девайс для работы с информацией, а в первую очередь свобода. Важная часть которой — я могу любую программу нажать правой кнопочкой, выбрать «открыть в дебаггере» и поправить всё что мне не нравится.
Это не свобода, а насмешка. Свобода — это Линукс, где вы можете открыть нормальный читабельный исходник. Единственное, что огорчает в Линуксе: для Си нет нормального менеджера зависимостей, там на полном серьезе предлагают скачать исходники в tar.gz и до сих пор используют страшные нечитаемые Makefile.
Насчет демосцены — согласен, что это интересное искусство. Но вот сомневаюсь, что быстрокод там пишут руками или руками считают тайминги, думаю, что это у них как-то тоже автоматизировано.
Если ассемблер так хорош, почему же мы не видим десктопных программ на нем? Компиляторов? СУБД?
Читайте внимательно, я писал, что "писать всегда на ассемблере – занятие не очень разумное с точки зрения времени, усилий, вероятности допустить ошибку и кроссплатформенности (я сам реже пишу на ассемблере, нежели на других языках)".
Умно — написать эффективный транслятор, который будет без потерь транслировать высокоуровневый код в ассемблер
Значит, ассемблер вам всё-таки пригодится? Иначе что вы будете делать потом с этим кодом, не зная ассемблера и не имея программ трансляции/компиляции?
Вы пишете, что ассемблер дает "полный контроль над кодом". Не дает. Современные процессоры используют out-of-order выполнение команд, то есть произвольно их переставляют местами. Вы над этим не имеете контроля и скорее всего даже не знаете, как это работает.
Над исполнением кода вы не имеете 100% контроля (хотя на out-of-order тоже можно влиять, существуют барьерные и сериализирующие инструкции; к тому же, для отдельных процессоров это всё возможно изучить, да сложно). А над кодом имеете.
Опять же, это глупо — переходить на ассемблер ради сокращения программы. Умно — переделать компилятор, чтобы он умел выкидывать неиспользуемые функции. gcc это более-менее умеет.
Мы не живём в идеальном мире, никогда не будет компилятора, который оставит лишь минимум того, что необходимо. А глупо или умно – это уже зависит от целей и задач, не надо уж так резать и смотреть только со своего угла.
Ну и без библиотек тоже плохо. Что вы будете делать, когда вам понадобится множество, словарь и другие структуры данных?
Я не призываю писать всё на асме (см. выше). Что я буду делать? Если очень надо, буду реализовывать его на асме (либо найду модуль/либу, делающую то, что мне нужно), либо буду использовать другой язык – это же очевидно :)
Супероптимизация — это умно. Сидеть руками переставлять байты — глупо.
Идея прикольная, но где же эти ваши супероптимизаторы в реальности? Или это просто очередная недореализованная идея? Опять же, всё зависит от ситуации. Текущие компиляторы далеко не всегда умеют генерить самый быстрый код. А дальше всё зависит от опыта и компетенций. Кстати, справится ли этот супероптимизатор с программой, которая использует свой же код как данные (сейчас вы, наверное, скажете, что это бредовая затея)?
Эта дрянь не нужна.
Как скажете. Кстати, почему вы решили, что использование недокументированных функций – неотъемлемая часть защиты и антиотладки?
8086 вышел по моему в 70-х годах, в нем 16-битные команды исполнялись строго по очереди, и с тех пор все поменялось радикально: процессоры стали 64-битные, в них используется out-of-order параллельное исполнение команд, а Интел должен тянуть это дремучее легаси.
Легко и удобно рассуждать, сидя в кресле. А что сделали бы вы на месте архитектора? Они уже попытались всё сломать, создав Itanium, и где он теперь? Кстати говоря, в x64 часть legacy вполне успешно вырезали.
Опять же, если брать Эльбрус, то система команд там намного современнее и логичнее, жаль только, что команды очень жирные.
Хорошо, когда процессор разрабатывается тогда, когда было создано уже много всего и можно с оглядкой на конкурентов сразу придумать нормальную архитектуру, правда? Посмотрим, насколько эффективен и чист будет Эльбрус, когда мы перейдём к какому-нибудь 256-битному коду :)
Комп — это же не просто универсальный девайс для работы с информацией, а в первую очередь свобода. Важная часть которой — я могу любую программу нажать правой кнопочкой, выбрать «открыть в дебаггере» и поправить всё что мне не нравится.
Это не моя цитата... А Линукс – это уже холивар.
Но вот сомневаюсь, что быстрокод там пишут руками или руками считают тайминги, думаю, что это у них как-то тоже автоматизировано.
Тайминги не считают (по крайней мере, покомандно), но код небольших интр, поверьте мне, пишут руками на асме (за крайне редким исключением).
Если ассемблер так хорош, почему же мы не видим десктопных программ на нем? Компиляторов? СУБД?
Потому что это сложные программы, работающие со сложными данными (массивы, структуры, хеш-таблицы). В Си мы пишем index.fields[FIELD_NAME].offset, а в ассемблере вы будете сидеть вручную высчитывать смещение от указателя. Вы потратите в разы больше времени.
Не факт. Я бы сказал, что на ассемблере с массивами работать не сложнее, чем на си, а в ряде случаев и гораздо проще. Во-первых, у нас есть возможность задействовать пару регистров, в один из которых (базовый) мы помещаем адрес массива, а в другой (индексный) — смещение нужного элемента от его начала. Процессор сам выполняет все адресные операции, а нам остается только модифицировать содержимое индексного регистра. А если размер элемента 1, 2, 4 или 8 байт, то и следить не нужно — процессор это делает сам, а мы только индекс элемента в этот индексный регистр кладем.
А во-вторых, массивы в си — это не массивы, как например, в фортране, а всего лишь алиасы для операций с указателями, о чем именно так и написано в стандарте
6.5.2.1(2)...The definition of the subscript operator [] is that E1[E2] is identical to (*((E1)+(E2))).
Так что работа с массивами в си (и в сикросскросс) — это операции с указателями и если не думать постоянно о том, что именно представляет собой оператор «квадратные скобки», то результатом обязательно будет какая-нибудь проблема. В лучшем случае это будет уязвимость, в худшем — мусор в результатах и крушение чего-нибудь с пассажирами.
Я давно как-то с BBS скачивал исходники файла turbo.exe для Turbo Pascal 6.0, так там было полно вставок на асм 286. Наверно, потому он так быстро транслировал.
А вот про красоту ассемблера x86 это спорно. Я пробовал писать на асме под БК-0010, ктотрый как известно слизан с архитектуры PDP, там система команд и сам ассемблер гораздо логичнее и приятнее. Не знаю как объяснить, просто удобнее и комфортнее, чем под x86 - просто надо попробовать, удобство практически сразу чувствуется.
Почему именно об x86? Чем не угодил ARM? В Z80 или 6502 мало инструкций? Ну, так и на brainfuck люди выделывают ого-го что. Взгляните на демосцену, которая в РФ зиждется на 70-80% на одном только спектруме.
У каждого языка и технологии своя область применения. Писать всегда на асме не призываю (читайте внимательнее), но знать его полезно.
Вот это огромное зло, только дрова (и то не все) можно простить.
С какого я должен armv7, aarch64, mipsel, mipsbe, power7 и riscv это во обще что. Есть куча программ где можно просто забить на какие другие архитектуры. Windows понимает практически только x86, вроде как то бесятка АРМ может. Я только с виндой работаю и других систем не вижу, суперкомп с не х86 мне точно никогда не видать.
Ассемблер - это сборщик. Язык ассемблера - это мнемоническое представление машинного кода. Так как машинный код - это набор команд конкретной архитектуры процессора, то конечно он не может зависеть от ОС для которой пишется программа, вообще. И именно по этой причине отдельные блоки, написанные на "ассемблере", и даже довольно большие, будут работать на любой ОС на одной и той же архитектуре процессора.
У разных ОС (я не имею в виду разные версии Windows или разные сборки Linux, разумеется, хотя и там есть свои нюансы), во-первых, разный формат исполняемых файлов, а во-вторых, разный набор и метод вызова системных функций. Конечно, 2+2*2 и т.п. будет работать на любой ОС, а что вы будете делать с этим дальше?
К примеру, автоматическую векторизацию кода (приведение обычных скалярных вычислений к параллельным вычислениям с использованием SIMD: SSE, AVX и т.п.), компиляторы делают весьма посредственно.
Небольшое уточнение по цитируемому — компиляторы С/C++. Интеловский компилятор с фортрана делает векторизацию на своих процах очень хорошо. Кроме этого, он генерирует более безопасный код в отношении разного рода непредвиденных вычислительных ситуаций типа переполнений, потери точности и прочего вычислительного дерьма, с которыми мы сталкиваемся на си, если не мониторим постоянно операнды.
На ассемблере можно написать ВСЁ, он всемогущ!
Домашнюю страничку Васи Пупкина с гостевой книгой слабо?
Есть один недостаток, который перекрывает все достоинства. Стоимость владения кодом.
Если ваша программа достаточно велика и при этом делает нечто полезное, то её сопровождение и модификация может оказаться дорогой или неразрешимой задачей. Это явление присуще многим программам на разных ЯП, но в случае использования большого объёма кода на ассемблере, оно гарантированно превращается в неразрешимую проблему.
но в случае использования большого объёма кода на ассемблере, оно гарантированно превращается в неразрешимую проблему.
Это иллюзия. Просто потому, что размер кода на ассемблере всегда переоценивается. Код на ассемблере не читается и не пишется, как код на языках высокого уровня.
Я бы назвал это чтением и писанием аккордами — когда несколько строк кода всегда воспринимаются как один логически связанный фрагмент.
Именно поэтому прочитать (и написать) 1000-строчную программу на языке ассемблера значительно проще, чем прочитать (и написать) 1000-строчную программу на языке высокого уровня.
Так что, будет ли сопровождение просто или сложно как всегда зависит от:
Качество кода – структурное программирование и хорошие практики на ассемблере так же важны как и на ЯВУ.
Знают ли программисты и насколько хорошо данный язык программирования.
Конечно на ассемблере и 1 и 2 труднее найти. Но ассемблер в этом не виновен. Он просто предоставляет свободу. А как вы ею воспользуетесь, ваша ответственность.
С вами согласен. Сам обожаю ассемблер. За возможности, за красоту и за самоудовлетворение. Хотя в большинстве своём люди всё же покупают компьютер, а не собирают его на уровне активных и пасивных эллементов сами. Хотя такие тоже имеются. Но увы, количество таких стойких и любящих своё дело всё меньше и меньше.
Он просто предоставляет свободу.
Обратный интересный пример Rust, тоже низкоуровневый, минимум свободы, всё для обеспечения безопасности исполнения кода.
Плохой стиль кодирования на любом ЯП -это катастрофа. И 1000 строк кода, это как раз небольшая программа, любопытно, насколько быстро вы прочитаете свой код через полгода, ещё интереснее, сможет ли другой человек не только прочитать чужой код, но и его модифицировать.
В качестве средств для организации кода, асм предлагает макросы. И всё...
Сопоставимая программа, например, на с, будет по количеству строк в несколько раз меньше, и простейшую модификацию кода чаще всего можно выполнить в несколько раз быстрее, так как с в смысле наглядности (при аккуратном кодировании) явно лучше. Большой бонус с- это переносимость кода между разными архитектурами процессоров.
Другое заблуждение, связанное с асмом, заключается в том, что всегда удается сократить требования к потребляемым ресурсам. Это не всегда так. Я однажды обнаружил, что алгоритм, переписанный с асма на фортран стал потреблять в несколько раз меньше памяти, при сопоставимой скорости выплнения кода. Так что производительность -это свойство конкретной программы, а не ЯП.
, любопытно, насколько быстро вы прочитаете свой код через полгода, ещё интереснее, сможет ли другой человек не только прочитать чужой код, но и его модифицировать.
Я легко прочитаю свой код через полгода. Более, часто именно так и происходит, потому что проекты мои по сути хобби и пишутся медленно и с перерывами.
А насчет других людей, вот, почитайте и скажите:
Более абстрактный код:
https://asm32.info/fossil/asmbb/file?name=source/accounts.asm&ci=tip
Менее абстрактный код:
https://asm32.info/fossil/asmbb/file?name=source/fcgi.asm&ci=tip
fresh.flatassembler.net
Open source, визуальное проектирование аля Delphi, относительная мультиплатформенность…
Тоже люблю асм, по началу MASM, потом перешёл на UASM, а вот FASM не заладилось, оставил его на потом, но когда освоил UASM, понял что фасм мне просто не нужен.
Хотя на С++ можно кодить в стиле ассемблер. Код получается на уровне с ассемблером. Называю такой стиль C/C++, с библиотеками по минимуму и меньше новых наворотов новых С++. Получаешь и компактность(ну немного больше асм) и скорость ассемблера. При этом меньше ошибок, код более читабельный. Хотя у меня код на асм тоже не плохо выглядит, кроме структурности я ещё С строками комментирую.
Говоря об ассемблере я бы предложил упоминать, о каком конкретно ассемблере идет речь. Я, например, как старый ассемблерщик помню ассемблеры IBM/360 и PDP-11 и они сильно отличаются от Intel. Отличия не только в архитектуре и системе команд, но и в самих ассемблерах.
Полностью соглашаясь с автором, я хотел бы добавить, что ассемблер - это основа программирования и каждому будущему программисту, независимо от того на какую систему он будет ориентироваться, надо в процессе обучения хотя бы курсовик сделать на ассемблере, не важно на каком, на любом. Это должно быть как курс молодого бойца.
ЯВУ, не путайте с Java
Точно, потому что Java - это бейсик, а не язык высокого уровня.
У Рихтера за 2008 год более новая книга(на русском "Windows via C/C++"). Там добавили некоторые особенности висты(юак) и зачем-то убрали главу про оконные сообщения. А самая новая книга по винапи - Павел Иосифович Windows System Programming.
Статья отличная, привет с васма!
<blockquote>В ассемблере есть особая магия и притягательность!
Это особый кайф, азарт, челлендж.
</blockquote>
Спасибо, читаю и думаю.
<blockquote>
Для сравнения: VCL-программа на Delphi 10.2 Tokyo
</blockquote>
Насколько помню, окно с перерисовкой и рисованием графика в win32
VCL Delphi 6 - 256 KB
Visual C++ 6 12 KB
Fasm 5KB
И да, "История одного байта"
https://wasm.in/blogs/istorija-odnogo-bajta.290/
Юров В.И. Ассемблер
PS. SoftIce
Ссылка ведёт на дорвей.
И вот универ, наконец-то курсы программирования и моё первое разочарование. Высокоуровневые языки. Готовые блоки, из которых составляются шаблонные программы которые делают одно и тоже. Никакой романтики, всё просто и прямолинейно. Но в какой-то момент на сцене появился ассемблер. И та самая магия из детства вернулась! Это было именно то, как я себе и представлял программирование: ощущение «силы» в руках, настоящее волшебство. Он был сложен и не пользовался популярностью. Но от этого ещё более притягательным был для меня — то самое чувство удовлетворения от «знания».
В итоге ассемблер для меня так и остался увлечением, но то самое чувство «настоящего программирования» навсегда теперь ассоциируется именно с ним.
Или, скажем, для проделывания чего-то вроде Mixing x86 with x64 codeО как, а мне попался перевод чуть более поздней статьи из Valhalla zine (5.Ⅷ.2011) «Врата рая: 64-битный код в 32-битном файле». Убийца эмуляторов. ;) –
А есть литература описывающая использование dot net на ассемблере?
Привет! Отличная статья, не теряешь вкуса к жизни и ассемблеру. Так держать! Очень приятно встретить спустя 20 лет. Исходники, как это было давно ))
jin_x :
Благодарю за post.
Возможно Вы согласитесь с тем,
1. чтобы эффективно писать на assembler
(assembly language
) желательно, помимо прочего,
знать «логику работы»
микропроцессоров,
железа ПК,
операционных систем на уровне инженера;
2. чтобы эффективно писать на C/C++
желательно, помимо прочего,
знать «логику работы»
железа ПК,
операционных систем на уровне инженера;
3. чтобы эффективно писать на PHP
, JavaScript
, etc.
уровня инженера можно и не иметь;
4. чтобы эффективно писать на Python
желательно иметь математическое образование.
0xd34df00d :
>Зачем? Питон сам по себе этого не требует.
Весь набор типов данных, которые Python предлагает разработчику, ориентирован на математически подготовленного специалиста. Прикладные Data Science на сегодняшний момент - это всё-таки Python.
Но Вы правы, например, «со строками» можно и так «взаимодействовать». Благо Python и тут очень доброжелателен, в том числе своими всевозможными встроенными функциями.
Спасибо автору. Хоть что-то полезное на хабре.
Нельзя быть действительно хорошим программистом, не понимая, как устроен и работает процессор. Ассемблер в смысле "потроха процессора" знать полезно. Отдельно полезно понимать, как работают немейнстримные процессоры. Тот же Эльбрус и Мультиклет.
ПРОГРАММИРОВАТЬ на ассемблере что-то отличное от бут сектора - сумасшествие. То есть делать это можно - но исключительно в порядке онанизма - ради удовольствия от процесса, а не ради результата. Как программист с почти сорокалетним стажем, имеющий представление о нескольких десятках ассемблеров (начиная с ламповых ЭВМ и заканчивая IAPX432) и программировавший на половине из них я это могу сказать с уверенностью. Пишите на Java, если вам нужен результат, а не процесс.
И да, на любом ЯП можно написать всё.
За что я люблю ассемблер?