Dev48
ЯЗЫК
  • О нас
  • Услуги
  • Индустрии
  • Технологии
  • Статьи
  • Контакты
Забронировать звонок
    Главная/Статьи/Platformenno nezavisimyy simd v go
Dev48

© 2026 · All rights reserved.

Платформенно-независимый SIMD в Go

Источник: Go

Платформенно-независимый SIMD в Go

Источник: Go

В Go 1.27 добавлен экспериментальный платформно-независимый API SIMD

27 сентября 2026 г.•Обновлено: 27 сентября 2026 г.

Go 1.26 и 1.27 включают экспериментальные API для операций с одной инструкцией и множественными данными (SIMD). SIMD — это встроенная функция многих современных процессоров, которая позволяет программному обеспечению очень быстро выполнять однотипные операции над векторами данных, например, складывать 8 пар значений float64 за одну инструкцию. Это может значительно ускорить множество ресурсоемких задач: от криптографии и обработки данных до ИИ. Фактически, сборщик мусора Green Tea в Go даже использует SIMD для ускорения сканирования памяти в поисках живых объектов.

До появления этих новых экспериментальных API единственным способом получить доступ к этой функциональности из Go было написание кода на ассемблере Go. Это имело смысл только для действительно критичных к производительности вычислительных ядер, что означало, что множество программ, которые могли бы выиграть от SIMD, просто оставляли значительную часть процессора неиспользуемой.

Go 1.26 представил API SIMD для amd64, а Go 1.27 добавил API для arm64 (а именно NEON) и wasm. Тем не менее, основная сложность для SIMD API заключается в огромных различиях между платформами: не только в том, какие операции они поддерживают, но и в том, как представлены векторы. Некоторые платформы предоставляют векторы фиксированного размера (обычно от 128 до 512 бит), в то время как на других размер вектора неизвестен во время сборки и должен запрашиваться при запуске программы. Чтобы обеспечить полный доступ ко всему спектру этих платформ, эти API находятся в зависимом от архитектуры пакете archsimd.

Но Go 1.27 идет дальше этих зависимых от архитектуры API и представляет экспериментальный, полностью переносимый интерфейс SIMD, не зависящий от платформы и размера, созданный на основе Highway для C++. Цель состоит в том, чтобы поддерживать код «simd», написанный один раз и обладающий производительностью, близкой к ассемблеру, на платформах с поддержкой SIMD, и обеспечивать эффективную эмуляцию на тех платформах, которые (пока) не имеют поддержки SIMD. Пакет simd в настоящее время поддерживает AVX, AVX2 и AVX512 на amd64, NEON на arm64, а также инструкции SIMD для wasm.

Мотивация: различия между архитектурами SIMD

Архитектуры SIMD различаются по нескольким параметрам. Некоторые предоставляют один фиксированный размер вектора (wasm, PowerPC и s390x, 128 бит). Некоторые предоставляют несколько фиксированных размеров векторов (amd64 с 128, 256 и 512; loong64 с 128 и 256). Riscv64 поддерживает векторы неопределенного размера от 128 до 65536 бит, хотя длина ограничена степенями двойки. Arm64 поддерживает один фиксированный размер (128 бит, NEON) и один переменный размер (128–2048 бит, только степени двойки, SVE). На конкретном экземпляре определенной архитектуры определение того, какие именно размеры поддерживает этот экземпляр, требует проверки возможностей: amd64, но это AVX, AVX2 или AVX512? Arm64, но это NEON или SVE? Если SVE, то какого размера? Какой вариант SVE: SVE, SVE2 или SVE2.1?

Различные архитектуры SIMD по-разному обрабатывают маскирование векторов. Для векторов конструкция if-then-else может быть реализована с помощью масок: выполнить операцию, но присвоить результат (или выполнить загрузку/сохранение) только там, где маска истинна («true»). Некоторые варианты SIMD не предоставляют масок; все операции выполняются над всеми элементами, а «маскирование» выполняется с помощью векторных битмасок и векторных булевых операций (wasm, AVX, AVX2, NEON). Другие предоставляют специальные регистры масок, где один бит управляет операциями над одним элементом вектора (AVX512 и RVV). Третьи (SVE) выделяют один бит на байт вектора, но наименее значимый бит маски каждого элемента управляет маскированными операциями. AVX2 также поддерживает маскированные загрузки и сохранения, но с использованием обычной маски-вектора, где операцией управляет наиболее значимый бит.

Третий источник различий — это сами операции. Каждая архитектура предоставляет свои собственные примитивы для переупорядочивания элементов вектора; одни требуют константных входов, другие поддерживают переменные входы. Различные архитектуры SIMD поддерживают разные криптографические операции. Даже базовая арифметика может иметь разную поддержку; например, в wasm отсутствуют сравнения для векторов 64-битных целых чисел. Даже для заданной длины вектора на определенной архитектуре поддержка инструкций зависит от «особенностей», которые необходимо проверять.

Хотя зависящий от архитектуры пакет archsimd в Go был разработан максимально унифицированным для разных архитектур, многие из этих особенностей сохраняются и делают проектирование, написание и тестирование кода для мультиплатформенного SIMD обременительным. Мы могли бы сделать больше в пакете archsimd, чтобы сделать различные архитектуры более похожими друг на друга, но наши возможности ограничены, если мы не хотим пожертвовать эффективностью.

Обзор

Новый пакет simd скрывает эти различия, удаляя векторы фиксированного размера из системы типов и поддерживая только те операции, которые находятся на пересечении всех различных платформ, а пробелы на пересечении заполняет эффективной эмуляцией с помощью других инструкций SIMD. Цель состоит в том, чтобы создать набор операций, который

  • достаточен для поддержки многих алгоритмов обработки данных, выигравших от векторизованной реализации (но не привязанных к конкретному размеру вектора),
  • эффективен так же, как язык ассемблера, когда операции исходного кода соответствуют базовому оборудованию,
  • в остальных случаях эмулируется наилучшим образом,
  • а также прост для чтения и понимания (даже/особенно если код в итоге напишет языковая модель).

На платформах, где отсутствуют инструкции SIMD или нет поддержки в archsimd, все операции эмулируются, поэтому код, написанный с использованием пакета simd, будет запускаться всегда.

Чтобы использовать этот экспериментальный пакет, установите GOEXPERIMENT=simd во время сборки, точно так же, как при использовании экспериментального пакета archsimd.

Векторные типы simd представляют собой просто написанные с большой буквы во множественном числе примитивные типы, например simd.Uint8s или simd.Float32s. Векторы загружаются из срезов и сохраняются в них, например:

Этот пример также демонстрирует одно из ограничений первого экспериментального выпуска этого пакета; поскольку не существует общего способа суммирования всех элементов вектора, это не поддерживается simd в Go 1.27, хотя ReduceSum появится в следующем выпуске, поэтому sum можно будет заменить просто на simd.ReduceSum.

Сравнения SIMD создают значения масок, которые зависят от ширины соответствующего элемента вектора, так что сравнения Int8s создают Mask8s и т. д., а значения масок могут использоваться для выбора и фильтрации векторов.

Поддерживаемые операции пакета simd по состоянию на Go 1.27

В этой таблице V и U — векторные типы, M — тип маски, E — скалярный тип, а W — ширина.

Функции загрузки/широковещательной рассылки на уровне пакета

Операции сохранения и со строками

Арифметические операции

Булевы операции и операции маскирования векторов

Операции сравнения

Операции преобразования

Методы масок

Операции сдвига и вращения

Операции изменения формы с нулевой стоимостью

Переход к платформо-специфичному коду и обратно

Может случиться так, что пакет simd окажется слишком ограниченным для всех частей конкретного приложения или что мы еще не обеспечили адекватную эмуляцию какой-либо необходимой функции. В этом случае пакет simd поддерживает переход к архитектурно-специфичному SIMD и обратно. Каждый векторный тип в пакете simd имеет метод преобразования ToArch(), возвращающий any. Для этого any можно выполнить приведение типов (type-assertion) к одному из архитектурно-специфичных типов для конкретной платформы. Для обратного преобразования используйте одну из функций simd.<SimdType>FromArch. Для переносимого кода это создает необходимость писать архитектурно-специфичный код для каждой из платформ, включая эмуляцию.

Вот полный пример для метода или функции, которых в настоящее время не хватает, но которые должны быть добавлены в Go 1.28. Предположим, вашему алгоритму требуется Int8s.OnesCount() (чего пакету simd в Go 1.27 не хватает). Вместо переписывания всего алгоритма для каждой платформы можно просто реализовать недостающую операцию.

Во-первых, для amd64, в котором отсутствуют инструкции для AVX и AVX2, но не для AVX512:

Преобразование интерфейса и переключатель типов (type switch) выглядят так, будто они должны работать неэффективно, но реализация simd на стороне компилятора специализирует код и оптимизирует переключатель типов, удаляя его.

NEON и Wasm поддерживают Int8s.OnesCount(), поэтому их реализация намного проще, хотя в ней все равно используются Int8s.ToArch и Int8sFromArch.

Не забывайте, что у некоторых людей нет аппаратной поддержки SIMD:

И в завершение примера — отдельная функция эмуляции, используемая в качестве запасного варианта (fallback) для всех реализаций:

Пересечение API и эмуляция методов

Любые операции, предлагаемые пакетом simd, должны работать приемлемо на большинстве архитектур. В качестве первого шага любая операция, поддерживаемая везде, может быть легко поддержана в simd. Обычно это включает в себя загрузки, сохранения, арифметические операции и сравнения (но не все сравнения!).

Наивное пересечение SIMD-методов с разных архитектур все равно оставляет множество пробелов. Они заполняются добавлением эмуляций в различные архитектурно-специфичные API archsimd. Эти API уже содержат множество тривиальных эмуляций, чтобы облегчить жизнь программистам на Go; сложение знаковыми и беззнаковыми целыми числами использует одну и ту же инструкцию, но, подобно тому как Go поддерживает оператор + как для int, так и для uint, пакет archsimd предоставляет как Int8x16.Add(Int8x16), так и Uint8x16.Add(Uint8x16), даже если они компилируются в одну и ту же инструкцию. Современные языки программирования также не ожидают от программистов знания того, как реализовать отрицание чисел с плавающей запятой и абсолютное значение с помощью манипуляций с битами, поэтому archsimd реализует это там, где необходимо, или «эмулирует», если посмотреть на это с определенной точки зрения.

Существует множество эмуляций, требующих всего 2 или 3 инструкции; например, некоторые архитектуры поддерживают только одинаковое для всех элементов вектора расстояние сдвига, в то время как другие поддерживают разное расстояние сдвига для каждого элемента вектора. Чтобы поддержать скалярный сдвиг в simd, мы эмулируем скалярный сдвиг с помощью векторного. На некоторых архитектурах отсутствуют некоторые беззнаковые сравнения — они представляют собой просто знаковое сравнение плюс два исключающих ИЛИ (XOR) с константой.

Не все недостающие инструкции так просты. Инструкция «умножения без переноса» (carryless multiply) важна для криптографии и контрольных сумм CRC, но она поддерживается не всегда. Исключение ее из API simd помешало бы ее использованию в некоторых важных алгоритмах. Поэтому мы предоставляем эмуляцию, и поскольку одним из важных вариантов использования является криптография, время ее выполнения не зависит от входных данных.

В других случаях, вместо реализации примитивной инструкции вроде «сложения пар» (также называемого «горизонтальным сложением»), для пакета simd в следующем релизе мы предоставим операцию более высокого уровня, для которой обычно используется сложение пар, а именно редукцию суммы (sum reduction). Это также помогает оградить пользователей от зависимости от длины векторов; даже при наличии аппаратной инструкции для сложения пар количество шагов редукции зависит от длины вектора.

Ограничение по поддержке всех платформ, включая те, которые, как мы прогнозируем, появятся в archsimd в течение следующего года или около того, заставляет использовать несколько консервативный подход к тому, какие методы мы добавляем в simd. Riscv64, ppc64, s390x и loong64 имеют свои собственные расширения SIMD.

Настройки GODEBUG

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

В Go 1.27 уровни поддержки SIMD грубо описываются длиной вектора:

  • GODEBUG=simd=0 означает использование эмуляции для SIMD-операций, даже если доступна аппаратная поддержка.
  • GODEBUG=simd=128 означает использование 128-битных векторов и их возможностей. Если возможности недоступны, немедленно вызывается паника.
  • GODEBUG=simd=256 означает использование 256-битных векторов и их возможностей, если это возможно.
  • GODEBUG=simd=512 означает использование 512-битных векторов и их возможностей, если это возможно.
  • GODEBUG=simd=+128 означает использование 128-битных векторов и их возможностей, даже если некоторые возможности не поддерживаются. Если используются неподдерживаемые инструкции, код вызовет панику, но если нет — он все равно может выполниться. Примером этого является Raspberry Pi, которая поддерживает NEON, но не имеет PMULL (умножение без переноса).
  • GODEBUG=simd=+256 означает использование 256-битных векторов и их возможностей, даже если некоторые возможности не поддерживаются. Если используются неподдерживаемые инструкции, код вызовет панику, но если нет — он все равно может выполниться. Примером этого является эмуляция amd64 в Apple Silicon, которая поддерживает AVX2, но не VPCLMULQDQ (опять же, умножение без переноса).
  • GODEBUG=simd=+512 означает использование 512-битных векторов, даже если некоторые возможности не поддерживаются.

Детали реализации

Если вы отлаживаете код, использующий simd, или даже просто просматриваете трассировку стека (stack trace), вы заметите странные дополнительные типы и методы. Причина в том, что simd — это одновременно и пакет, и внутренний пакет реализации, а также результат перезаписи синтаксического дерева (AST) во фронтенде компилятора.

Перезапись AST создает несколько специализированных копий функций, переменных и типов, в которых упоминаются типы simd, причем типы simd заменяются на ссылки на типы с конкретным размером в simd/internal/bridge. Каждый из этих мостовых типов (bridge types) определяется как тип archsimd, но с ограниченным набором методов. Специализированные функции, переменные и типы получают суффикс вида @simdNNN, где NNN — это либо длина вектора (128, 256 или 512), либо 0, обозначающий эмуляцию. Функции, которые упоминают simd внутри себя, но не в своей сигнатуре, преобразуются в обертки (wrappers), которые переключаются на уровень SIMD, обнаруженный при запуске программы, и вызывают соответствующую специализированную версию этой функции. Специализированные функции вызывают другие специализированные функции напрямую без накладных расходов на диспетчеризацию (и, возможно, с инлайнингом). Такая стратегия перезаписи была выбрана в качестве компромисса между дублированием кода и производительностью SIMD; накладные расходы поднимаются наверх настолько высоко, насколько это необходимо во избежание диспетчеризации внутри SIMD-вычислений, но не выше. Если диспетчеризация SIMD оказывается «слишком низкой» в вычислениях, избыточное упоминание типа simd переместит ее вверх, как в этом примере:

Что нас ждет в будущем

Мы планируем в ближайшее время опубликовать запись в блоге с подробным описанием archsimd.

В Go 1.28 мы намерены добавить поддержку SVE в archsimd, а также надеемся добавить ее в simd. Что еще более важно, мы надеемся добавить дополнительные SIMD-операции к тем, которые пакет simd уже поддерживает (например, OnesCount, операции с масками, операции редукции, операции перестановки векторов). Go 1.28 также будет включать небольшое количество «вариантов функций», чтобы избежать полного перехода на эмуляцию для платформ, у которых есть аппаратная реализация векторов, но отсутствует всего одна или несколько операций, таких как Raspberry Pi.

← Все статьи

Ещё в разделе «Разработка ПО»

Все →
Обзор Sennheiser Momentum 5: великолепный звук, невероятное время автономной работы и минимум компромиссовПресса
Momentum

Обзор Sennheiser Momentum 5: великолепный звук, невероятное время автономной работы и минимум компромиссов

Boeing сообщает о программном сбое в 737 Max, затрагивающем некоторые функции автоматического захода на посадкуПресса
Boeing

Boeing сообщает о программном сбое в 737 Max, затрагивающем некоторые функции автоматического захода на посадку

Apple обязали выплатить 5,7 млрд долларов по иску о нарушении патентных прав на тактильные технологии в iPhone и Apple Watch
Пресса
Apple

Apple обязали выплатить 5,7 млрд долларов по иску о нарушении патентных прав на тактильные технологии в iPhone и Apple Watch

Компании выбирают САПР от PTC для разработки и проектирования продуктов
PTC

Компании выбирают САПР от PTC для разработки и проектирования продуктов

Clas Ohlson выбирает PTC FlexPLM для поддержки своей стратегии роста
PTC

Clas Ohlson выбирает PTC FlexPLM для поддержки своей стратегии роста

Canon ITS и PTC Japan запускают «Smart PLM Support Service» для поддержки трансформации рабочих процессов в сфере производства
PTC

Canon ITS и PTC Japan запускают «Smart PLM Support Service» для поддержки трансформации рабочих процессов в сфере производства

Ещё от Go

Выделение памяти, специализированное по размеру
Go

Выделение памяти, специализированное по размеру

Профили утечек горутин
Go

Профили утечек горутин

Обобщенные методы
Go

Обобщенные методы

Выпущен Go 1.27
Go

Выпущен Go 1.27