
Нас ждёт мозговыносящая смесь 64/32-битного ассемблера и старого-доброго C++. Мы сделаем собственную реализацию... Волокон (fibers) без вызова Win API и звонков в службу спасения.
Язык программирования низкого уровня
Нас ждёт мозговыносящая смесь 64/32-битного ассемблера и старого-доброго C++. Мы сделаем собственную реализацию... Волокон (fibers) без вызова Win API и звонков в службу спасения.
В этой статье разбирается решение задачи «Гистограммы» с контеста Route 256 от Ozon с помощью SIMD.
Условие задачи
Гистограммой является массив, каждый элемент которого указывает высоту столбика на соответствующей позиции. Две гистограммы считаются совпадающими, если при совмещении одной гистограммы с другой гистограммой, повёрнутой на угол 180°, получается ровный прямоугольник без наложений и пропусков.
В этой части нас ждёт погружение в один из способов организации мультипоточности на базе единственного ядра процессора. Мы научимся принудительно переключать выполнение между полностью зацикленными участками кода, ничего не "знающими" о каком-то другом коде, конкурирующем за процессорное внимание. По ходу повествования будут даны все необходимые пояснения и читателю не придётся обращаться к другим источникам, кроме первой части статьи.
В первой части статьи мы рассмотрели, как можно вручную ускорить Go-код с помощью векторизации и SIMD-инструкций, реализованных через Go-ассемблер. Написали простую, но показательно быструю реализацию sliceContains и увидели, что даже базовая векторизация может дать ускорение в 10–14 раз по сравнению со стандартной реализацией.
Во второй части статьи погрузились в практическое применение SIMD в Go-ассемблере, реализовали функцию SliceContainsV1 и изучили, как с помощью VADD, VDUP и других инструкций можно добиться 10–14-кратного ускорения простых задач.
Но возможности оптимизации Go-программ на этом не заканчиваются. В этой части мы пойдём дальше: рассмотрим другие техники низкоуровневой оптимизации — от использования C-кода и альтернативных компиляторов с поддержкой векторизации до работы с аппаратными транзакциями памяти на Intel. Поговорим о том, как внедрять ассемблер в продакшен-код, не боясь за его поддержку, и как обойти ограничения стандартного Go-компилятора.
Привет, Хабр! Меня зовут Игорь Панасюк, я работаю в Яндекс, преподаю в ИТМО, а также в свободное время выступаю на конференциях, делюсь опытом в соцсетях и помогаю развитию Go-сообщества, веду телеграм-канал и youtube-канал. Если вы уже знакомы с базовыми техниками векторизации, эта часть поможет глубже понять, как устроены продвинутые способы ускорения Go-кода и на что стоит обратить внимание при работе с архитектурно-зависимыми оптимизациями.
Ускорить простые задачи, вроде поиска в массиве и сравнения слайсов, поможет мощь SIMD. Эти векторные инструкции, которые обрабатывают десятки байт данных за один такт процессора, отличная замена традиционным циклам. Во второй части статьи мы погружаемся глубже в практическое применение SIMD в Go-ассемблере, реализуем функцию SliceContainsV1 и изучим, как с помощью VADD, VDUP и других инструкций можно добиться 10–14-кратного ускорения простых задач.
Из этой статьи вы узнаете:
• Как устроено сравнение массивов с помощью SIMD-инструкций;
• Почему векторизация быстрее бинарного поиска;
• Как правильно работать с регистрами, фреймами и указателями в Go-ассемблере;
• Что нужно учесть при переносимости и поддержке низкоуровневого кода;
• Когда ассемблер оправдан и безопасен в реальных проектах на Go.
Информация будет актуальна как разработчикам, оптимизирующим критически важный код, так и тем, кто хочет глубже понять архитектуру выполнения и взаимодействие с «железом».
В первой части статьи мы разобрали саму идею ускорения кода на Go с помощью ассемблера. А в этой разберём её практическое применение.
Даже в 2025 году, когда вокруг нейросети, автогенерация кода и IDE с предиктивным интеллектом, работа с редкими микроконтроллерами всё ещё может обернуться настоящим хардкором. Особенно, если речь идёт о «слепой» отладке без отладчика, когда в арсенале только прошивка, HEX-файл и пара байтов на выводе. В этой статье — личный опыт, много хардкора, дизассемблирование вручную и поиск глюка в 2 КБ бинаря.
Когда говорят «отладка», в 2025 году чаще всего имеют в виду жмяк на F5 в Visual Studio Code или лог с CI/CD. Но в embedded-мире, особенно если ты копаешься в системах с 8-битным контроллером 2006 года выпуска, это слово может означать кое-что пострашнее. Например — «прошивка вылетает на 4-й секунде, данных в UART нет, отладочного интерфейса нет, документации почти нет, а заказчик просит сделать "как раньше работало"». И вот тут начинается старый добрый reverse engineering.
Когда речь заходит о производительности в Go, большинство разработчиков полагаются на стандартные библиотеки и встроенные инструменты оптимизации, но компилятор Go не всегда генерирует оптимальный машинный код. В таких случаях можно взять дело в свои руки и использовать ассемблерные инструкции для ускорения критически важных участков.
Ассемблер может показаться сложным и пугающим, но он открывает большие возможности для работы с низкоуровневыми оптимизациями. Готовы разобраться, как это работает? Тогда погнали!
Привет, Хабр! Меня зовут Игорь Панасюк, я работаю в Яндекс, преподаю в ИТМО, а также в свободное время выступаю на конференциях, делюсь опытом в соцсетях и помогаю развитию Go-сообщества.
Одним днём я читал отчёты по TI из различных источников. В одном из таких отчётов упоминался некий алгоритм для распаковки вредоносного ПО, под названием PolyUnpack. Мне стало интересно, и я решил изучить данную тему. Оказалось, что в Интернете очень мало информации по данному алгоритму. Из интересного я нашёл статью, в которой впервые был описан алгоритм и чью-то курсовую работу.
Я решил написать свою реализацию данного алгоритма, так как часто приходится иметь дело с запакованным ВПО. В этой статье я расскажу, в чём заключается алгоритм, как я его реализовал, с какими проблемами я столкнулся и какие есть альтернативы для распаковки вредоносов. В конце я оставил ссылку на GitHub.
Вторая часть уже здесь.
В первой части мы:
• посмотрим, как работать с памятью и регистрами 8086
• узнаем, как написать простую программу на ассемблере прямо в отладчике
• изучим работу механизма прерываний и сделаем демонстрационный пример
Статья рассчитана на тех, кто имеет начальный опыт программирования, но хочет понять основы низкоуровневого программирования и многозадачности.
Недавняя публикация о векторном расширении RISC-V архитектуры, подтолкнула меня к мысли написать небольшую заметку об использовании данного расширения в задаче, имеющей практическое применение. После появления векторного расширения, в сети начали публиковаться статьи о применении RISC-V ядер с данным расширением в задачах, ранее в которых безальтернативно использовались только процессоры ЦОС. В данной статье рассматривается тест, в котором используется алгоритм декодирования Витерби - задача, требующая значительных вычислительных ресурсов.
В очередной дискуссии о вреде и пользе новомодных "искусственных интеллектов" (или как точнее их называют LLM) в программировании, пришёл на ум такой простой эксперимент, который я приглашаю повторить всех желающих с разными вариациями.
Возьмём очень простую задачу - скомпилировать код. Для наиболее однозначного соответствия я беру код на ассемблере - и хочу получить HEX-файл. Это задача которую можно выполнить на бумажке (имея под рукой список команд и помня формат файла) - да в древние времена кому-то и приходилось такую "ручную компиляцию" выполнять. А что нам ответит, например DeepSeek?
Цель не в том чтобы снова поиронизировать над пропонентами LLM, а просто показать как кажущаяся "логика умозаключений" LLM не выстраивается в цепочку, иными словами рвётся от утверждения к утверждению. Думал также, что пример также может служить подспорьем в общении с менеджерами требующими бурного внедрения ИИ в разработку - но возможно он слишком технический.
Был проведён эксперимент для проверки, можно ли существенно уменьшить объём вычислений в алгоритме обратного распространения ошибок с параллельными вычислениями за счёт использования на каждом шаге обучения только части обучающих образцов, выбранных случайным образом, а также определение того, какой выигрыш по времени даст использование языка Ассемблера в самых внутренних циклах (в программе, написанной на языке C++).
За основу был взят классический персептрон и алгоритм обратного распространения ошибок, основанный на методе градиента, который объяснялся на курсе Mashine Learning Стэнфордского университета. Он был доработан, чтобы можно было использовать параллельные вычисления. Была написана программа на языке C++ для Linux, её функции (создание, обучение нейронной сети, распознавание данных, закачка больших файлов на сервер и т. п.) вызываются из программ, написанных на любых языках программирования, по протоколу Socket.
Для параллельных вычислений создаётся ntheads объектов нейронной сети, где ntheads — количество потоков (процессоров), в которые записываются части большого массива обучающих образцов, и на каждом шаге алгоритма обратного распространения ошибок совершается прямое и обратное распространение для каждого образца, имеющегося у объекта нейронной сети. Вычисления для каждого объекта производятся в отдельном потоке. Результатом этих вычислений являются суммарные градиенты слоёв сети каждого объекта, они суммируются друг с другом, и полученные градиенты используются для модификации матриц весов нейронной сети, которые затем прописываются во все слои сети объектов нейронной сети.
Построение множества Мандельброта — классический пример чрезвычайно параллельной задачи (embarrassingly parallel problem).
На первом курсе я впервые столкнулся с такой проблемой: тогда мы изучали SIMD-инструкции в курсе архитектур вычислительных систем. Эта тема сразу меня увлекла, и я захотел углубиться в дальнейшие оптимизации, но в течение семестра мне не хватало ни времени, ни знаний. Спустя год я решил восполнить этот пробел.
Вначале мы разберем наивную реализацию, поиграемся с интринсиками (intrinsics) и, не теряя переносимости, заставим компилятор генерировать нам SIMD-инструкции. Далее добавим многопоточность и в заключение обесценим все наши старания несколькими строчками на CUDA.
Возможно, эта статья поможет таким же, как я, впервые столкнувшимся с подобными задачами.
Многие современные вычислительные задачи, в частности повсеместная обработка изображений и звука или работа с матрицами для ИИ, хорошо поддаются параллелизации на уровне данных. Чтобы ускорить такие вычисления, производители процессоров добавили в архитектуры специальные SIMD инструкции, которые позволяют работать за одну инструкцию сразу с несколькими элементами.
В процессорах архитектуры x86 SIMD инструкции добавляются по принципу ad hoc. Из-за такого подхода, легаси и требований обратной совместимости в x86 накопилось много проблем.
Архитектура RISC-V относительно молодая, и при её разработке учтён прошлый опыт. В основе подхода к SIMD в RISC-V заложили идею чистого векторного процессора.
В этой статье рассмотрим основные принципы работы векторного процессора и базовые векторные операции с памятью и арифметикой.
Как же исходный код превращается в бинарный файл, который потом исполняется на компьютере? Не нашёл ни одной статьи, которая описывала бы полный процесс от начала до конца, поэтому я написал данный материал.
При вызовах функций на языке С активно используется стек, который также именуется «стек вызовов». По мере того, как мы вызываем функции, они формируют так называемый «стек кадров». При каждом вызове функции образуется кадр, и эти кадры укладываются в стеке, где под них выделяется место. Далее в кадре из стека выделяется память под переменные и промежуточные значения. В кадре стека также содержится указатель на предыдущий кадр и значение счётчика команд. Та команда, которой оно соответствует, должна быть выполнена, как только кадр будет вытолкнут из стека. Далее давайте дизассемблируем вызовы функций в C, чтобы понять, как устроен стек кадров в ассемблере для ARM.
Здравствуйте, меня зовут Дмитрий. Сегодня я хотел бы рассказать вам про очень интересную игру Turing Complete. Я назвал бы её симулятор разработчика компьютеров. А также мы прямо в этой игре соберем компьютер SAP-1 более известный как компьютер Бена Итера.
Сразу скажу что мне игра очень понравилась. Ну вот знаете иногда про игру все говорят какая крутая игра, а ты в неё поиграешь и думаешь. Ну игра ничего особенного. А иногда встречаются игры про которые никто не говорит особо, но стоит в неё начать играть, как обнаруживаешь что ты просто не можешь от неё оторваться. Как раз эта игра относится ко второму типу. У меня такое было c Factorio я в неё тоже долго не мог перестать играть. Так что возможно я буду чуть-чуть предвзятым.
Должен признать, у меня есть некая особая любовь к классическому DOOM. Несмотря на то, что игре уже 31 год, в нее все еще весело играть самому (хотя и выходит у меня так себе) или просто смотреть на то, как другие в нее играют (вот в этом я показываю себя лучше); и поскольку исходный код игры открыт, ей можно наслаждаться на любой современной платформе — ПК, смартфон, камера, осциллоскоп — да и вообще на любой вещи, которая придет вам в голову. В результате чего, благодаря ряду обстоятельств, я оказался меинтейнером нескольких связанных с DOOM пакетов в Fedora Linux.
Итак, за несколько месяцев до нового релиза, проект Fedora Linux осуществляет массовую сборку всех пакетов. Это имеет несколько преимуществ — позволяет убедиться в совместимости ABI, обновить статически линкуемые зависимости, использовать новые оптимизации компилятора и так далее. Как бы то ни было, с приближением релиза Fedora Linux 42 в середине апреля, пришло время для массовой пересборки, и как часто бывает, не все пакеты выжили. Одним из пакетов, которые не удалось собрать оказался chocolate-doom
.