Есть что-то прекрасное в программировании на ассемблере. Оно может быть очень медленным и полным ошибок, по сравнению с программированием на языке, таким как Go, но иногда — это хорошая идея или, по крайней мере, очень весёлое занятие. Зачем тратить время на программирование на ассемблере, когда есть отличные языки программирования высокого уровня? Даже с сегодняшними компиляторами все ещё есть несколько случаев, когда захотите написать код на ассемблере. Таковыми являются криптография, оптимизация производительности или доступ к вещам, которые обычно недоступны в языке. Самое интересное, конечно же, оптимизация производительности. Когда производительность какой-то части вашего кода действительно имеет значение для пользователя, а вы уже попробовали все более простые способы сделать его быстрее, написание кода на ассемблере может стать хорошим местом для оптимизации. Хотя компилятор может быть отлично оптимизирован для создания ассемблерного кода, вы можете знать больше о конкретном случае, чем может предположить компилятор. Лучший способ начать — написать простейшую функцию. Например, функция add складывает два int64. Запуск: go build -o add-go && ./add-go Для реализации этой функции на ассемблере создайте отдельный файл add_amd64.s, который будет содержать ассемблерный код. В примерах используется ассемблер для архитектуры AMD64.  add.go: add_amd64.s: Для запуска примера поместите два этих файла в одну директорию и выполните команду go build -o add && ./add Синтаксис ассемблера в лучшем случае… неясен. Существует официальное руководство Go и довольно-таки древнее руководство для ассемблера Plan 9, в котором даются некоторые подсказки относительно того, как работает язык ассемблера в Go. Лучшие источники для изучения — это существующий ассемблерный код Go и скомпилированные версии функций Go которые можно получить, выполнив команду: go tool compile -S <go file>. Наиболее важные вещи, которые нужно знать — это объявление функции и компоновка стека. Волшебное заклинание для запуска функции — TEXT ·add(SB), NOSPLIT, $0. Символ символ Юникода · разделяет имя пакета от имени функции. В данном случае имя пакета — main, поэтому имя пакета здесь пустое, а имя функции — add. Директива NOSPLIT означает, что не нужно записывать размер аргументов в качестве следующего параметра. Константа $0 в конце — это то, где вам нужно будет поместить размер аргументов, но поскольку у нас есть NOSPLIT, мы можем просто оставить его как $0. Каждый аргумент функции кладётся в стек, начиная с адреса 0(FP), означающий смещение на ноль байт от указателя FP, и так для каждого аргумента и возвращаемого значения. Для func add (x, y int64) int64, он выглядит так:
 Разберём код уже знакомой функции add: Ассемблерная версия функции add загружает переменную x по адресу памяти +0(FP) в регистр BX. Затем она загружает из памяти y по адресу +8(FP) в регистр BP, складывает BP и BX, сохраняя результат в BX, и, наконец, копирует BX по адресу +16(FP) и возвращается из функции. Вызывающая функция, которая помещает все аргументы в стек, будет читать возвращаемое значение, оттуда где мы его оставили. Не обязательно писать на ассемблере функцию, складывающую два числа, но для чего действительно нужно его использовать? Допустим, у вас есть куча векторов, и вы хотите их умножить на матрицу преобразования. Возможно, векторы являются точками, и вы хотите переместить их в пространстве (перевод на Хабре — прим. пер.). Мы будем использовать векторы с матрицей преобразования размером 4x4.  Выполнение занимает 140 мс для 128 МБ данных. Какая реализация может быть быстрее? Эталоном будет копирование памяти, которое занимает около 14 мс. Ниже приведена версия функции, написанная на ассемблере с использованием инструкций SIMD для выполнения умножений, позволяющая умножать четыре 32-битных числа с плавающей точкой параллельно: Эта реализация выполняется за 18 мс, поэтому она довольно близка к скорости копирования памяти. Лучшая оптимизация может заключаться в том, чтобы запускать такие вещи на графическом процессоре, а не на процессоре, потому что графический процессор действительно хорош в этом.  Время работы для разных программ, включая инлайн-версию Go и ассемблерную реализацию без SIMD (со ссылками на исходный код): Платой за оптимизацию будет сложность кода, который упрощает работу машины. Написание программы на ассемблере — сложный способ оптимизации, но иногда это лучший доступный метод. Автор разработал ассемблерные части в основном на C и 64-битном ассемблере с использованием XCode, а затем портировал их в формат Go. У XCode хороший отладчик, который позволяет проверять значения регистров процессора во время работы программы. Если включить ассемблерный файл .s в проект XCode, IDE соберёт его и слинкует его с нужным исполняемым файлом. Автор использовал справочник по набору инструкций Intel x64 и руководство Intel Intrinsics, чтобы выяснить, какие инструкции нужно использовать. Преобразование на язык ассемблера Go не всегда простое, но многие 64-битные ассембленые инструкции указаны в x86/anames.go, а если нет, они могут быть закодированы напрямую с двоичным представлением. В оригинале статьи в ассемблерные файлы не включён заголовок #include "textflag.h", из-за чего при компиляции выдаётся ошибка illegal or missing addressing mode for symbol NOSPLIT.
Поэтому выложил на GitHub Gist исправленные версии. Для запуска распаковываем архив, выполняем команды: chmod +x run.sh && ./run.sh.  Запускать код с ассемблером можно лишь собрав бинарник с помощью go build, иначе компилятор ругнётся на пустое тело функции.  К сожалению, в интернете действительно мало информации по ассемблеру в Go. Советую почитать статью на Хабре про архитектуру ассемблера Go. Как известно, Go поддерживает использование кода, написанного на C. Поэтому, ничего не мешает сделать так:  sqrt.h: sqrt.go: И запустить: Ассемблер — это весело! Разработчик программного обеспечения