Pull to refresh

Comments 91

UFO just landed and posted this here
Что то подобное видел на wasm еще в 4 году или около того.
В свое время в небезызвестной книге С.В.Зубкова восхитило это:

Самый компактный вариант преобразования шестнадцатеричной цифры в ASCII-код соответствующего символа:

cmp al,10
sbb al,96h
das
Компактный, но не самый быстрый. Эти команды, aas, das и прочие медленно работают… ведь только в скорости смысл при написании сервера на асм =)
Интересно, будет ли работать такое (для hex->bin):
mov ah,al
shr ah,4
xor ah,33h
sub al,ah

Команд столько же, сколько у автора, зато без переходов.
Не стоит так же забывать про таблички и XLAT.
hex db '0123456789ABCDEF'
Не так уж они медленно работают:

cmp ~1 такт, sbb — задержка 2 такта, das — 4.5 такта — для SandyBridge users.atw.hu/instlatx64/GenuineIntel00206A7_SandyBridge_InstLatX86.txt с сайта instlatx64.atw.hu/

Вот на P4 медленнее, das около 100 тактов (57 микроопераций), sbb 6-10 тактов. Да еще и у AMD 16-20 микроопераций на das, задержка 7-10 тактов.

По микрооперациям для Sandy — das =3, sbb =2, cmp=1, все на p015 www.agner.org/optimize/instruction_tables.pdf — стр 120-121

По конкретному коду разбивку по портам и общую задержку можно попытаться посмотреть в Intel IACA, software.intel.com/en-us/articles/intel-architecture-code-analyzer

Другое дело что команды das в 64-битном режиме нет — 2F — Invalid Instruction in 64-Bit Mode ref.x86asm.net/coder64.html#x2F (вот для 32-битного ref.x86asm.net/coder32.html#x2F)
HTTP заголовок может содержать параметр Range, где задается смещение и длина.
Если есть возможность задавать заголовки, то можно настроить обычный сервер на подобные манипуляции.
А так вполне себе решение
не совсем понимаю как вы собираетесь заставить браузер использовать Range для загрузки скажем изображения, если известно смещение и размер, при использовании предложенного сервера мы просто прописываем урл в теге img.
Nginx может творить чудеса, из того же пути получить смещение и размер, проставить их в заголовки и спроксировать на себя же.
Не проверял, но теоретически ничего не мешает это сделать.
Я так понял, что проблема на стороне браузера: если браузер отправил Range запрос, то он вряд ли станет считать, что ему пришел целый файл, и не станет обрабатывать полученный кусок.
Если мы сами себе злобные буратины, то можно полученные данные через canvas отобразить. Но это ж костыль, лучше offs-len через URL передавать, как и предлагается.
Проблема в том что автору нужно прочитать хотябы обзор о HTTP протоколе.
Вообще-то не так. Если возвращается 200, то Range проигнорирован, и браузер всё съест правильно. На правильный Range-запрос возвращается 206.
В реальных задачах Range, конечно, нужно учитывать.
Зачем именно так сделано — на 100% мне неизвестно. Мне кажется что это связано с возможным отсутствием Content-Length в заголовках, когда файл принять надо, а сколько данных браузер заведомо не знает. Буду признателен если кто-то откроет тайну )))
Вы или указывайте Content-Length, либо Transfer-Encoding: chunked. Или попробуйте отвечать HTTP/1.0.
Потрясающе!

единственное, что немного цепляло взгляд — сишные комменты после ассмовских. Причем, только вначале. Вызывают небольшой когнитивный диссонанс. Видя такие комменты подсознание отказывается воспринимать асм, хотя ты и видишь код и все понимаешь)
Вёзёт вам! Смотрите асм и всё понимаете. А я вот за 15 лет всё напрочь забыл :)
Потестил ab, первый раз ab -c 100 -n 1000 выдал 0.5 мс на запрос, каждый последующий запуск был медленнее, после 3-го запуска стал выдвать 23 мс. Нода и Эрланг с cowboy выдают примерно по 0.1 мс на запрос стабильно. Почему так?
Нода и Эрланг крутяться в одном физическом процессе, а тут сделанно через форк, который происходит в режиме ядра, да и вообще не самая дешевая операция. Да и вообще, тоже самое можно было бы написать на С, а компилятор сгенерировал бы аналогичный код. В данном случае написание на асемблере не дает никакого особого выгрыша по производительности.
Насколько я знаю сервера типа Apache взаимодействуют с libc которая в свою очередь является оберткой для системных вызовов, что (незначительно) снижает производительность. Да, в данном случае какого-либо выигрыша скорей всего не заметишь
Вы по этому решили написать сей «шедевр»?

Если бы вы вместо fork-on-accept использовали однопозадачный паттерн с select или стартовали трёды или epool… То было бы на много быстрее. А на сишке сразу и быстрее написалось бы и быстрее работало бы.

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

А если вам так не иметься — возьмите и напишите.

Статья получилась сама собой, и код таков каков он есть.
f0rk,
Не стоит сравнивать epoll/select с sys_fork.
А на счет результатов, — попробуйте потестить все таки на многоядерной машине.
Тестил на 4-х ядерном i5-2430M
Для ОС он логически 4х поточный. Если уже придираться к мелочам.
Ну, может быть… Я глянул cat /proc/cpuinfo, увидел 4 ядра и написал, могу не шарить :)
C 0W сравните, пожалуйста (http://0w.ru/httpd/).
Как лаба зачет. Но даже на курсач не тянет.

же только ленивый не писал подобных статей, — сервер на perl, node.js, по-моему даже были попытки на php.

Вот только на ассемблере еще не было, — значит нужно заполнить пробелы.

Писали то писали, но это бло хоть на что-то похожее. Хотя бы вместо «HTTP/1.1 200 OK» версию 1.0 поставили бы. А то несоответствия, и вообще ужас.

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

Вообще интел асм достаточно приятный язык. Когда-то я любил на нём писать конечные автоматы и стейт машины.
Тю. Я рассчитывал на high-performance хардкор, с параллельным использованием порта несколькими процессами (недавная фишка линуксов), а получил в результате тупой listen-n-fork, даже без epoll.

Любой приличный веб-сервер уделает это чудо в разы.
Не подскажете, где можно почитать про «использование порта несколькими процессами»?
Спасибо. Я правильно понимаю, что это позволяет открыть на прослушку скажем порт 80 несколькими процессами одновременно и расхватывать входящие соединения параллельно?
Верно. Сделано чтобы устранить узкое место в виде одного слушающего процесса.
Под наш любимый nginx тоже патчики есть. Прирост весьма существенный.
И соединения теряются при обновлении конфигурации или бинарника. Пока реализацию в ядре не починят — штука малоюзабельна.

Статью на LWN надо читать со слов:
The other noteworthy point is that there is a defect in the current implementation of TCP SO_REUSEPORT.

p.s. У nginx нет узкого места «в виде одного слушающего процесса».
Хмм… А тогда за счёт чего вышеупомянутый патч даёт прирост?
Из сообщения не совсем ясно, но весьма вероятно, что сравнение идет против включенного accept_mutex-а.
Я по-быстрому потестил на дефолтном конфиге с разницей только во включенном или выключенном so_reuseport.
До 2-х тысяч одновременных соединений — быстрее без so_reuseport, свыше (проверял вплоть до 20-ти тысяч) — уже быстрее с so_reuseport, на ~10%.
Так не надо на дефолтном. Вы сравниваете nginx с включенным accept_mutex-ом (по умолчанию) против патча, который помимо всего прочего accept_mutex отключает. Последний является частой причиной заниженных цифр в искуственных бенчмарках.
accept_mutex on;
so_reuseport off;
Requests per second: 3342

accept_mutex off;
so_reuseport off;
Requests per second: 3307

accept_mutex off;
so_reuseport on;
Requests per second: 3874

ab -c 20000
среднее 4-х итераций
Intel Atom D525 :)

ab локально и в одном экземпляре? Оно вам показывает положение звезд на небе, ab использует select() и работает в разы медленнее nginx-а, во всех трех случаях последний простаивает большую часть времени.
перепроверил с weighttp, он использует эффективные методы (libev).

C включенным so_reuseport — 3723 req/s
Без патча — 2933 и 3117 req/s (accept_mutex off/on)

Таким образом по положению звёзд тоже можно ориентироваться. Хотя бы направление понять. :)
То же самое.
Предлагаю обсудить
p.s. У nginx нет узкого места «в виде одного слушающего процесса».
Тут и обсуждать нечего, каждый рабочий процесс слушает сокет. Иными словами процессов много и на многоядерной системе они могут исполняться параллельно, а с отключенным accept_mutex-ом accept'ить соединения одновременно. Значит узкое место имеет место быть в ядре, но только вместо его устранения был придуман способ требующий дополнительных усилий со стороны разработчиков, плюс лишающий nginx одного из ключевых преимуществ — обновление версии и конфигурации без потери соединений. Разработчик DragonFlyBSD осознал свою ошибку и ушел править ядро, а когда тем же самым займутся в линуксе — неизвестно.
Разве не будет EADDRINUSE, если ещё раз вызвать listen() для сокета на том же порту?
Не нужно делать несколько раз listen(). Мастер-процесс nginx-а, который читает конфигурацию, делает по одному listen() на каждый сокет, а затем уже форкается на указанное количество рабочих процессов.
Ну или просто бенчмарк слишком искусственный, тестирует достаточно вырожденный случай, а в реальности ситуация не так однозначна.

Как утверждает автор:
In case #2, the proportion of connections accepted per thread tends
to be uneven under high connection load (assuming simple event loop:
while (1) { accept(); process() }, wakeup does not promote fairness
among the sockets. We have seen the disproportion to be as high
as 3:1 ratio between thread accepting most connections and the one
accepting the fewest. With so_reusport the distribution is
uniform.

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

Но реальность она чаще всего отличается от этих идеальный условий. Представьте, что на сервер поступают разные по тяжести запросы, легкие, средние и тяжелые. Когда ядро раскладывает соединения, оно не знает, в каком из них будет легкий запрос, а в каком тяжелый, в итоге один воркер может получить много легких запросов и быстро с ними справиться, обслужит всю свою очередь соединений на своем дескрипторе, а затем будет простаивать, в то время как другой всё ещё пыхтит над несколькими тяжелыми запросами. И не только запросы отличаются, отличаются клиенты, один сможет быстро прислать запрос, другой будет слать медленно, пакет за пакетом, один сможет быстро забрать ответ, другой будет вытягивать его долго.
Когда ядро раскладывает соединения, оно не знает, в каком из них будет легкий запрос, а в каком тяжелый
Собственно nginx тоже не знает, каким будет соединение, и какие запросы потом ещё придут.
итоге один воркер может получить много легких запросов и быстро с ними справиться, обслужит всю свою очередь соединений на своем дескрипторе, а затем будет простаивать
Простаивать точно не будет, так как запросы на сервер продолжают поступать (если это не бенчмарк).

Так что я думаю, что на момент accept()-а соединения, что у ядра, что у nginx-а примерно одинаковые возможности по балансировке — по загрузке процессора.

Если бы nginx поддерживал потоки, ситуация с равномерностью загрузки была бы красивее.
Собственно nginx тоже не знает, каким будет соединение, и какие запросы потом ещё придут.

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

Баланс по нагрузке и баланс по кол-ву соединений — не когерентные соотношения, потому что разные соединения не равны между собой.

Простаивать точно не будет, так как запросы на сервер продолжают поступать (если это не бенчмарк).

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

Так что я думаю, что на момент accept()-а соединения, что у ядра, что у nginx-а примерно одинаковые возможности по балансировке — по загрузке процессора.

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

Если бы nginx поддерживал потоки, ситуация с равномерностью загрузки была бы красивее.
В чем же разница между ядерными потоками и процессами в этом аспекте? =)
В легковесности — их можно создавать быстро и много.
В качестве отправной точки для значения worker_processes документация предлагает использовать количество ядер.

Вот есть у нас 16 ядер, и, соответственно 16 воркеров. И заняты он тем, что все жмут gzip-ом большие HTML-страницы — ну очень процессороёмкая операция. Приходит новое соединение с запросом на маленькую картинку. Оно принимается мастер-процессом (или откладывается в очередь) и ждёт, пока освободится воркер. А ведь ресурсов, чтобы его отдать прямо сейчас, практически не нужно.
В легковесности — их можно создавать быстро и много.
В Windows системах оно может быть и так. В *nix их создание эквивалентно по стоимости.

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

Вот есть у нас 16 ядер, и, соответственно 16 воркеров. И заняты он тем, что все жмут gzip-ом большие HTML-страницы — ну очень процессороёмкая операция. Приходит новое соединение с запросом на маленькую картинку.… и ждёт, пока освободится воркер. А ведь ресурсов, чтобы его отдать прямо сейчас, практически не нужно.
Как это связано с обсуждаемой темой про SO_REUSEPORT? =) Очевидно никак.

Вы же понимаете разницу между асинхронным подходом, используемым в nginx, и многопоточным синхронным сервером? В последнем случае скедулинг осуществляет ОС, а не сам nginx. И проблема в обоих случаях одинакова — на что отдать процессорное время, в какой момент переключить задачу. Nginx сжав кусочек страницы, заполнив буфер, примет запрос на картинку.
Мастер-процесс nginx-а, который читает конфигурацию, делает по одному listen() на каждый сокет
Мастер-процесс не работает с пользовательскими соединениями вообще

Я уже немного запутался. Попробую подобрать более показательные тесты. Для gzip-ов в том числе.
Вы упустили из первой цитаты:
, а затем уже форкается на указанное количество рабочих процессов.
Именно рабочие процессы зовут accept(). Мастер этим не занимается.
Три часа ночи. Выглянул в окно, бомжи трощат мусорные баки. Больная фантазия выдала аналогию.

Действующие лица:
Бомжи — это воркеры (worker_processes).
Мусорные баки — сокеты
Жильцы с мусорными вёдрами — сессии (connection)
Процесс переработки/сортировки мусора бомжами — полезная работа

Сцена первая

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

Сцена вторая

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

Сцена третья

Жильцов становится больше, появляется очередь к единственному мусорному баку. Бомжи нервничают, мешают друг другу, т.к. в один момент времени копаться в баке может только один.

Сцена четвёртая

Бомжи не справляются. Рассерженные жильцы начинают кидать мусор вокруг бака (backlog). Бомжи ругаются, обвиняя жильцов в чрезмерной генерации мусора (DDoS), и не выдержав, начинают кидать в них разные бумажки (syncookies).

Обиженные жильцы вместе со своим мусором, приходят жаловаться в ЖЭК/управляющую компанию, грозясь высыпать мусор им на головы и сменить поставщика услуг.
ЖЭК говорит, спокойно — вот есть решение (SO_REUSEPORT) и выделяют каждому бомжу свой персональный мусорный бак.

Но, замечают, что есть один недостаток (defect in the current implementation) — если во время того, как жилец высыпает мусор, бачок вдруг взять и забрать, то часть мусора выпадет на землю, а что упало — то пропало (connection lost).
в один момент времени копаться в баке может только один.

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

ЖЭК говорит, спокойно — вот есть решение (SO_REUSEPORT) и выделяют каждому бомжу свой персональный мусорный бак.

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

Но во всей этой аналогии есть одно большое упущение. Вы считаете, что копаться в мусорном баке — это основная работа бомжей и у них там действительно постоянно возникает contention. Но реальность такова, что на порядок большую часть времени бомжи занимаются тем, что разгребают то, что извлекли из баков, а сама процедура извлечения занимает менее 1% времени в работе бомжа. Причем в случае nginx-а, бомжей сравнительно мало, но каждый из них копается одновременно в сотнях и тысячах уже извлеченных мешков.
Отличный ответ, Спасибо!

К сожалению, у меня нет рабочего сервера, где было бы 20к подключений, обычно на порядок меньше. Поэтому нигде nginx не является узким местом — разгребает весьма эффективно. Синтетика (и комментарии с патчу) говорят, что выгода будет на очень большом объёме. На 2к у меня nginx и так работет быстрее, чем с патчем.

Буду благодарен, если вы предложите какую-нибудь методику тестирования, более приближенную к полевым условиям.
Спасибо, а я уже полез в архивы за статьёй.
Любой приличный веб-сервер уделает это чудо в разы.
ИМХО, целью автора было совсем не скорость, т.к.
Времени было прилично, решил я немного извратиться и написать это на асме.
И кроме того это чудо тоже уделает любой приличный веб-сервер! По размеру кода, разумеется.
Есть две причины, почему в современном мире имеет смысл писать на ассемблере (есть ещё третья — под процессор нет компилятора, но это не наш случай):
* Показ того, как оно на самом деле работает, показ принципов архитектуры
* Оптимизация на скорость

Оптимизация на размер для веб-сервера — нонсенс, не заслуживающий упоминания. Просто потому, что для того, чтобы эта программа стала нормальной, ей нужно: init-скрипт, поддержка правильной демонизации и остановки, control-файл и всё это завёрнутое в deb/rpm. На выходе будем иметь кратное увеличение размера, так что никого просто не будет волновать — 100кб оно там или 30.

В остальном ни одна из целей выполнена не была — не было показано культурное ipc на ассемблере (что крайне любопытный вопрос) — вместо этого подложили скучный fork, не были показаны особенности архитектуры (epoll хотя бы, не говоря уже про новую семантику tcp-сокетов), под которую реализация. Про скорость уже сказано.

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

К тому же, на многих из них, функциональность веб-сервера является далеко не основной задачей. Он изредка используется для контроля/управления устройством.
Если мы поработаем со встраивыеми устройствами, то поймем, что в MIPS'ах и ARM\ах нет никаких int 80h
Это был комментарий на «оптимизацию на размер», ни слова по ARM, MIPS или int 80h. (где вы их увидели?)
И да, оказывается на x86 есть масса встраиваемых решений, вы не знали?
Не поленился посмотреть маныулы. Так вот ARM-овскойм асме есть инструкция SWI которая точно так же (через механизм прерываний) используется для Linux-овых системных вызовов. Пример использования. Так что ваш комментарий «мимо кассы».
Да чего вы на меня так накинулись, как будто я сказал, что линукс гавно. Я просто сказал, что в большинстве встраиваемых систем нету привычного int 80h, вот и все. И да, конечно есть похожие механизмы вызова системных функций.
Я просто сказал, что в большинстве встраиваемых систем нету привычного int 80h, вот и все.
А где-то утверждалось обратное? Открытие Америки — надо же, на других аппаратных платформах, используется другой ассемблер, и там нет x86-ых инструкций! Вау! К чему вообще был этот комментарий? Почему он получил плюсик? Почему мой, абсолютно корректный, получил минус?
Если вы используете x86 в встраиваемом устройстве, вам уже насрать на сотни килобайт. Да. Если же вы хотите выпендрится старинными 8-битными однокристалками, то в посте не тот ассемблер не с тем ядром.
Ещё раз, комментарий был на
Оптимизация на размер для веб-сервера — нонсенс, не заслуживающий упоминания.
т.е. без привязки к конкретной платформе и применению. Веб-сервер может быть на погодной станции, где достаточно выдавать несколько чисел в браузер. Или на такой симпатичной x86 плате с 32МБ памяти.
не говоря уже про новую семантику tcp-сокетов

Подскажите пожалуйста, а о чем идет речь? Или где об этом можно почитать подробнее?
Оказывается что после отправки самого файла, браузер шлет заголовок, который сервер должен принять. Не важно какие там данные, — его все равно можно отослать в /dev/null но очень важно что бы сервер его прочел. Иначе браузер посчитает что с файлом что-то не то. Зачем именно так сделано — на 100% мне неизвестно. Мне кажется что это связано с возможным отсутствием Content-Length в заголовках, когда файл принять надо, а сколько данных браузер заведомо не знает. Буду признателен если кто-то откроет тайну )))

А что за заголовок, какой браузер? А вообще, как сказали выше — Content-Length отдавайте. Так браузер хотя бы будет уверен, что файл пришел полностью, а если не отдавать, то при разрыве связи в процессе передачи браузер может посчитать, что передача файла завершилась.
Хмм… Возможно, бразуер пытается использовать заголовки Range: и Content-Range: и брать файл по частям? Я вот сейчас с этим столкнулся: отдаю аудио-файл — и даже посылаю Content-Length, но проигрыватель в браузере глючит — хотя музыку играет, а файл полностью уходит с сервака. Видимо, у меня более сложный случай :)
Действительно если портировать этот веб-сервер на KolibriOS посмотреть бы результаты бенчмарков.
Сильно смущают всякие
cmp al, 39h

вместо
cmp al, '9'

и
mov eax, 6            ; close() syscall
mov ebx, edi        ; The socket descriptor
int 0x80            ; Call the kernel

вместо
mov ebx, edi        ; The socket descriptor
call close


Или целью было написать еще и максимально запутанный код?

Плюс, если мне память не изменяет, в FASM тоже есть invoke, и вместо всяких
push A
push B
push C
call X

можно просто писать
invoke X, A, B, C

Если смареть с точки зрения быстродействия, то call, jmp и всякие работы со стеком — это ОГРОмадный минус этому самому быстродействию. Если хочется скорости — не нужно никаких процедур, циклов, и тем более, работы со стеком. Нужно побороть в себе «эстета» и если цикл дублируется, скажем, раз 10, то можно его вообще развернуть и не делать цикл (loop тоже тормозная команда). Так же и, казалось бы, процедурами — да, код много раз повторяется, противоречит всем «правилам» относящимся к языкам высокого уровня (хотя тут еще ка посмотреть) но гораздо лучше копипастить хоть тыщу раз, чем call'ы и стек.
Я тут ниже писал, что код в статье не похож на написанный с учетом оптимизации скорости/размера. А если писался для изучения темы — тогда уже и подход к написанию ожидается другой.
call close — это будет линковка с glibc, а автор хотел:
Сразу скажу, что мы не будем использовать дополнительные библиотеки типа libc. А будем пользоваться тем, что предоставляет нам ядро.
Поэтому через system call.
Я имел ввиду вынести все эти syscall'ы (или взять готовый файл) вида:
call:
    mov eax, 6            ; close() syscall
    int 0x80            ; Call the kernel
    ret
...

чем использовать константы непосредственно в коде.
Вариант автора занимает 18 байт (два вызова по 9 байт), ваш — 22 ( два вызова по 7 байт плюс 8 на тело функции) и медленней.
Но мой выходит понятнее. У автора вроде не было гонки ни за каждым байтом, ни за скоростью. Судя по комментам в коде и обхяснению в статье — пример это был учебный. Тогда уж и писать понятный код :)

Я вон когда в свое время развлекался подобным образом и делал растровый графический редактор или драйвер HDD с AES шифрованием на MASM, не гнался за максимально компактным кодом. Для этого есть категория demo-прог. Вообще в этом плане я поддерживаю коммент amarao.
Еще вот бегло глянул макросы fasm, и получается, что в качеcтве эквивалента 18 байтного кода достаточно написать:
macro fclose sock
{
    mov ebx, sock
    mov eax, 6
    int 0x80
}

Тогда вызов получается:
fclose edi

Круто же! Почему бы не использовать такие возможности.
Круто, но: во-первых далеко не всегда короткий код самый быстрый; во-вторых макросы обоснованы, когда хочется получить высокое (предсказуемое) качество выполнения программы, игнорируя размер и скорость работы, ибо если взять более серьезные макросы (а не обычные процедуры, которые в макросы запихивать тоже тот еще изврат), типа сравнения чего-то с чем-то, то на выходе получается говнокод похлеще индусского. В основном за счет добавления мусорных опкодов, которые даже никогда не исполняются.
Я правильно понимаю, что расширение .FBF — это f#$%ing big file?
Topface делал то же самое на node-js habrahabr.ru/post/184652/, и в отличии от этой статьи, там есть реальные бенчмарки в сравнении.
Кроме того там в комментариях советуют elliptics от Yandex той же парадигмой.
Sign up to leave a comment.

Articles