⚡️ SIMD-ускорение для слайсов в Go без unsafe и CGo — 27 мая 2026 г. в 05:35:01.698
⚡️ SIMD-ускорение для слайсов в Go без unsafe и CGo Go отлично справляется с циклами. Компилятор умеет автовекторизировать простые паттерны, и на современных процессорах с AVX-512 стандартный for range работает быстро. Но не для всех операций. Сравнения: min, max, count; компилятор векторизирует хуже, и на больших массивах это заметно. TurboSlice закрывает именно эту нишу. turboslice — Go-библиотека, которая заменяет ручные циклы по слайсам на вызовы с SIMD-ускорением через 128-битные SSE-инструкции. Построена на пакете simd/archsimd из Go 1.26. Работает на любой платформе, но ускорение даёт на AMD64. На ARM64 и остальных архитектурах автоматически откатывается на скалярную реализацию. Без CGo, без ассемблерных файлов, без unsafe в публичном API. Что умеет Агрегации (Sum, Min, Max, MinMax), поиск (Find, Contains, Count), поэлементная математика (AddSlices, MulSlices, DotProduct), а также набор дженерик-утилит (Map, Filter, Reduce, Chunk, Unique, Flatten и другие). Когда SIMD помогает, а когда нет Авторы честно описывают границы применимости. Реальный выигрыш от SSE получают Min, Max, MinMax, Count — от 2x до 2.6x на всех размерах. Для Sum и DotProduct[int32/float32] SIMD включается только на слайсах от 4K и 16K элементов соответственно. Ниже этих порогов накладные расходы на настройку SIMD съедают всю выгоду. А вот Find, Contains, DotProduct[float64] и AddSlices[float64] остаются скалярными даже в SIMD-сборке. Компилятор Go на этих паттернах генерирует код лучше, чем ручной SSE. Для 64-битных целых умножений (MulSlices[int64], DotProduct[int64]) SSE/AVX2 просто не имеют нужной инструкции. Нужен Go 1.26+. Для SIMD-ускорения собираем с флагом: GOEXPERIMENT=simd go build ./... Пример использования: import "github.com/atul-007/turboslice" data := []int32{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} turboslice.Sum(data) // 55 turboslice.Find(data, 7) // 6 turboslice.Min(data) // 1 turboslice.Max(data) // 10 turboslice.Contains(data, 5) // true Вместо типичного цикла: total := 0 for _, v := range data { total += v } Пишем одну строку: total := turboslice.Sum(data) Typed API для горячих путей Если тип известен заранее и нужна максимальная производительность, есть типизированные функции (SumInt32, MinFloat64, CountInt32 и т.д.). Они инлайнятся без прохода через interface{} или type switch. На скалярной сборке работают наравне с ручным циклом, на SIMD-сборке получают ускорение. turboslice.SumInt32(data) // инлайнится напрямую turboslice.MinFloat64(vals) // без диспатча через интерфейс Пример из практики Обработка сигналов: signal := loadSensorData() weights := precomputeWeights(len(signal)) weighted := turboslice.MulSlices(signal, weights) energy := turboslice.DotProduct(signal, signal) lo, hi := turboslice.MinMax(signal) Аналитический пайплайн: scores := fetchAllScores() // []int32, миллионы записей total := turboslice.Sum(scores) lo, hi := turboslice.MinMax(scores) outliers := turboslice.Filter(scores, func(s int32) bool { return s > 3*stddev }) Что стоит учесть Min, Max, MinMax паникуют на пустых слайсах, как и slices.Min/Max из стандартной библиотеки. AddSlices, MulSlices, DotProduct молча обрезают до минимальной длины. Переполнение целых чисел не проверяется. Обработка NaN различается между SIMD и скалярной реализацией для Min/Max. TurboSlice — нишевый инструмент. Если вы работаете с числовыми слайсами от десятков тысяч элементов на AMD64 и ваш bottleneck — агрегации типа min/max/count, библиотека даёт ощутимый прирост без компромиссов в безопасности кода. Для мелких слайсов и операций, где компилятор уже справляется, она просто не мешает. ➡️ Репозиторий

