# Go 1.26/1.27 giới thiệu giao diện SIMD thử nghiệm độc lập với nền tảng

- Nguồn: Hacker News Nổi bật (buzzing.cc bản dịch tiếng Trung)
- Thời gian phát hành: 2026-09-26 01:36 (giờ Việt Nam)
- Điểm AI: 58/100
- Link AIHOT.vn: https://aihot.vn/items/9f195d4c32aacc6a
- Nguồn dữ liệu AI HOT: https://aihot.news/items/cmuhbu59r080qro3byi488b04
- Link gốc: https://go.dev/blog/simd-experiment

## Tóm tắt AI

Nhóm phát triển Go ra mắt API SIMD thử nghiệm trong Go 1.26 và 1.27, cho phép lập trình viên viết mã tối ưu hóa hiệu suất gần bằng hợp ngữ mà vẫn đảm bảo tính di động cao trên nhiều nền tảng.

## Thân bài

Go 1.26 và 1.27 bao gồm các API thử nghiệm cho các thao tác Single Instruction Multiple Data (SIMD). SIMD là một tính năng gốc của nhiều CPU hiện đại, cho phép phần mềm thực hiện các thao tác đồng nhất trên các vector dữ liệu một cách rất nhanh chóng, chẳng hạn như cộng 8 cặp giá trị float64 trong một lệnh duy nhất. Nó có thể tăng tốc đáng kể nhiều tác vụ đòi hỏi tính toán chuyên sâu, từ mật mã học đến xử lý dữ liệu và AI. Trên thực tế, [bộ thu gom rác Green Tea](https://go.dev/blog/greenteagc) của Go thậm chí còn sử dụng SIMD để tăng tốc việc quét bộ nhớ cho các đối tượng còn sống.

Trước các API thử nghiệm mới này, cách duy nhất để truy cập chức năng này từ Go là viết Go assembly. Điều này chỉ xứng đáng với các nhân tính toán thực sự quan trọng về hiệu năng, đồng nghĩa với việc rất nhiều phần mềm có thể hưởng lợi từ SIMD đơn giản là đã bỏ phí một phần lớn tài nguyên CPU.

Go 1.26 đã giới thiệu một API SIMD cho amd64, và Go 1.27 đã thêm các API cho arm64 (cụ thể là NEON) và wasm. Tuy nhiên, một thách thức cơ bản đối với API SIMD là sự khác biệt to lớn giữa các nền tảng, không chỉ ở các thao tác mà chúng hỗ trợ, mà còn ở cách các vector được biểu diễn. Một số nền tảng cung cấp các vector có kích thước cố định, thường từ 128 bit đến 512 bit, trong khi ở các nền tảng khác, kích thước vector không được biết tại thời điểm biên dịch và phải được truy vấn khi chương trình khởi chạy. Để cung cấp quyền truy cập đầy đủ vào phạm vi rộng lớn của các nền tảng này, các API này nằm trong gói archsimd phụ thuộc vào kiến trúc.

Nhưng Go 1.27 đã vượt xa các API phụ thuộc vào kiến trúc này và giới thiệu một giao diện SIMD thử nghiệm, hoàn toàn di động, không phụ thuộc vào nền tảng và kích thước, được xây dựng dựa trên [Highway](https://google.github.io/highway/en/master/index.html) cho C++. Mục tiêu là hỗ trợ viết mã "simd" một lần với hiệu năng gần bằng asm trên các nền tảng có hỗ trợ SIMD, và cung cấp khả năng mô phỏng hiệu quả trên các nền tảng chưa (hoặc không) hỗ trợ SIMD. Gói simd hiện hỗ trợ AVX, AVX2 và AVX512 trên amd64, NEON trên arm64 và các lệnh SIMD của wasm.

### Động lực: sự khác biệt giữa các kiến trúc SIMD

Các kiến trúc SIMD khác nhau theo nhiều chiều. Một số cung cấp một kích thước vector cố định duy nhất (wasm, PowerPC và s390x, 128 bit). Một số cung cấp nhiều kích thước vector cố định (amd64, với 128, 256 và 512; loong64 với 128 và 256). Riscv64 hỗ trợ các vector có kích thước không xác định từ 128 đến 65536 bit, mặc dù độ dài bị giới hạn ở lũy thừa của 2. Arm64 hỗ trợ một kích thước cố định (128 bit, NEON) và một kích thước biến đổi (128-2048 bit, chỉ lũy thừa của hai, SVE). Trên một phiên bản cụ thể của một kiến trúc nhất định, việc xác định kích thước mà phiên bản đó hỗ trợ đòi hỏi phải kiểm tra tính năng: amd64, nhưng là AVX, AVX2 hay AVX512? Arm64, nhưng là NEON hay SVE? Nếu là SVE, kích thước bao nhiêu? Biến thể nào của SVE: SVE, SVE2 hay SVE2.1?

Các kiến trúc SIMD khác nhau xử lý việc che (masking) vector theo những cách khác nhau. Đối với các vector, cấu trúc if-then-else trên một vector có thể được thực hiện bằng các mặt nạ (mask); thực hiện thao tác, nhưng chỉ gán kết quả (hoặc tải, hoặc lưu) ở nơi mặt nạ là "true". Một số biến thể SIMD không cung cấp mặt nạ; tất cả các thao tác hoạt động trên tất cả các phần tử, và việc "che" được thực hiện bằng các mặt nạ bit vector và các thao tác logic vector (wasm, AVX, AVX2, NEON). Một số cung cấp các thanh ghi mặt nạ đặc biệt, với một bit điều khiển các thao tác trên một phần tử vector (AVX512 và RVV). Những loại khác (SVE) phân bổ một bit cho mỗi byte vector, nhưng bit có trọng số thấp nhất của các bit mặt nạ của mỗi phần tử sẽ điều khiển các thao tác được che. AVX2 cũng hỗ trợ tải và lưu có che, nhưng sử dụng một vector thông thường làm mặt nạ, và với bit có trọng số cao nhất điều khiển thao tác.

Nguồn biến đổi thứ ba nằm ở chính các thao tác. Mỗi kiến trúc cung cấp các nguyên hàm riêng để sắp xếp lại các phần tử vector; một số yêu cầu đầu vào hằng số, số khác hỗ trợ đầu vào biến đổi. Các kiến trúc SIMD khác nhau hỗ trợ các thao tác liên quan đến mật mã khác nhau. Ngay cả số học cơ bản cũng có mức độ hỗ trợ khác nhau; ví dụ: wasm thiếu các phép so sánh cho các vector số nguyên 64-bit. Ngay cả đối với một độ dài vector nhất định trên một kiến trúc cụ thể, việc hỗ trợ lệnh phụ thuộc vào các "tính năng" phải được kiểm tra.

Mặc dù gói archsimd phụ thuộc vào kiến trúc của Go được thiết kế để đồng nhất nhất có thể giữa các kiến trúc, nhiều điểm khác biệt này vẫn tồn tại, khiến việc thiết kế, viết và kiểm thử mã cho SIMD đa nền tảng trở nên nặng nề. Chúng ta có thể làm nhiều hơn trong gói archsimd để làm cho các kiến trúc khác nhau trông giống nhau hơn, nhưng chúng ta chỉ có thể tiến xa đến một mức độ nhất định mà không làm ảnh hưởng đến hiệu quả.

### Tổng quan

Gói simd mới ẩn đi những khác biệt này bằng cách loại bỏ các vector kích thước cố định khỏi hệ thống kiểu dữ liệu, và chỉ hỗ trợ các thao tác nằm trong giao điểm của tất cả các nền tảng khác nhau, đồng thời lấp đầy các khoảng trống trong giao điểm đó bằng cách mô phỏng hiệu quả thông qua các lệnh SIMD khác. Mục tiêu là một tập hợp các thao tác:

1. đủ để hỗ trợ nhiều thuật toán xử lý dữ liệu được hưởng lợi từ việc triển khai vector hóa (nhưng không bị ràng buộc với một kích thước vector cụ thể),
2. hiệu quả như ngôn ngữ assembly khi các thao tác trong mã nguồn khớp với phần cứng bên dưới,
3. được mô phỏng tốt nhất có thể trong các trường hợp còn lại,
4. và dễ đọc, dễ hiểu (ngay cả/đặc biệt là nếu một LLM viết mã đó).

Trên các nền tảng thiếu lệnh SIMD hoặc thiếu hỗ trợ trong archsimd, tất cả các thao tác đều được mô phỏng, để mã được viết bằng gói simd sẽ luôn chạy được.

Để sử dụng gói thử nghiệm này, hãy đặt GOEXPERIMENT=simd tại thời điểm biên dịch, giống như khi sử dụng gói thử nghiệm archsimd.

Các kiểu vector simd chỉ là các kiểu nguyên thủy ở dạng số nhiều và viết hoa, ví dụ: simd.Uint8s hoặc simd.Float32s. Các vector được tải từ và lưu vào các slice, ví dụ:

```
// innerProduct returns the inner product of x and y.
func innerProduct(x, y []float32) float32 {
    var a simd.Float32s
    var i int
    for i = 0; i < len(x)-a.Len()+1; i += a.Len() {
        u := simd.LoadFloat32s(x[i : i+a.Len()])
        v := simd.LoadFloat32s(y[i : i+a.Len()])
        a = u.MulAdd(v, a)
    }
    if i < len(x) {
        u, _ := simd.LoadFloat32sPart(x[i:])
        v, _ := simd.LoadFloat32sPart(y[i:])
        a = u.MulAdd(v, a)
    }
    return sum(a)
}
// sum returns scalar sum of elements of x.
func sum(x simd.Float32s) float32 {
    s := make([]float32, x.Len())
    x.Store(s)
    var r float32
    for _, e := range s {
        r += e
    }
    return r
}
```

Ví dụ này cũng cho thấy một trong những hạn chế của bản phát hành thử nghiệm đầu tiên của gói này; vì không có cách chung để tính tổng tất cả các phần tử của một vector, nên nó không được simd hỗ trợ trong Go 1.27, mặc dù ReduceSum sẽ xuất hiện trong bản phát hành tiếp theo để sum có thể được thay thế bằng simd.ReduceSum.

Các phép so sánh SIMD tạo ra các giá trị mặt nạ, vốn đặc thù cho độ rộng phần tử vector tương ứng, do đó các phép so sánh Int8s tạo ra Mask8s, v.v., và các giá trị mặt nạ có thể được sử dụng để chọn và lọc các vector.

### Các thao tác gói simd được hỗ trợ tính đến Go 1.27

Trong bảng này, V và U là các kiểu vector, M là kiểu mặt nạ, E là kiểu vô hướng và W là độ rộng.

### Các hàm Tải / Phát sóng cấp gói

| Hàm | Int8s | Int16s | Int32s | Int64s | Uint8s | Uint16s | Uint32s | Uint64s | Float32s |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| LoadV([]E) V | Y | Y | Y | Y | Y | Y | Y | Y | Y |
| LoadVPart([]E) (V, int) | Y | Y | Y | Y | Y | Y | Y | Y | Y |
| BroadcastV(E) V | Y | Y | Y | Y | Y | Y | Y | Y | Y |

### Các thao tác lưu trữ/chuỗi

| (x V).Method(...) | Int8s | Int16s | Int32s | Int64s | Uint8s | Uint16s | Uint32s | Uint64s | Float32s |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Store(s []E) | Y | Y | Y | Y | Y | Y | Y | Y | Y |
| StorePart(s []E) int | Y | Y | Y | Y | Y | Y | Y | Y | Y |
| String string | Y | Y | Y | Y | Y | Y | Y | Y | Y |

### Các thao tác số học

| (x V).Method(...) V | Int8s | Int16s | Int32s | Int64s | Uint8s | Uint16s | Uint32s | Uint64s | Float32s |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Abs V | Y | Y | Y |  |  |  |  |  | Y |
| Add(y V) V | Cộng giá trị y vào V. | Trả về kết quả của phép cộng. | Tham số y là giá trị cần cộng. | Tham số V là giá trị gốc. | Phép toán này hỗ trợ các kiểu dữ liệu số. | Đảm bảo không xảy ra tràn số trong quá trình tính toán. | Kết quả được lưu trữ trong biến V. | Hàm này thường được sử dụng trong các vòng lặp xử lý tensor. | Hiệu suất của hàm được tối ưu hóa cho GPU. |
| AddSaturated(y V) V | Cộng giá trị y vào V với cơ chế bão hòa. | Nếu kết quả vượt quá giới hạn, nó sẽ được giữ ở giá trị cực đại. |  |  | Hữu ích cho các tác vụ xử lý tín hiệu số. | Ngăn chặn hiện tượng tràn số (overflow). |  |  |  |
| Average(y V) V |  |  |  |  | Tính giá trị trung bình giữa y và V. | Trả về trung bình cộng của hai giá trị. |  |  |  |
| Div(y V) V |  |  |  |  |  |  |  |  | Chia V cho y. |
| IfElse(mask MaskWs, y V) V | Thực hiện chọn giá trị dựa trên mask. | Nếu mask là true, chọn y. | Nếu mask là false, chọn V. | Hỗ trợ các toán tử điều kiện logic. | Thường dùng trong các lớp mạng thần kinh có điều kiện. | Tối ưu hóa cho các phép toán vector. | Đảm bảo tính nhất quán của dữ liệu đầu ra. | Hỗ trợ xử lý song song. | Cấu trúc dữ liệu đầu vào phải khớp với mask. |
| Len int | Trả về độ dài của đối tượng. | Giá trị trả về là kiểu số nguyên. | Thường dùng để kiểm tra kích thước tensor. | Không thay đổi trạng thái của đối tượng. | Trả về 0 nếu đối tượng rỗng. | Hỗ trợ các kiểu dữ liệu danh sách. | Hiệu quả trong việc quản lý bộ nhớ. | Được sử dụng để xác định giới hạn vòng lặp. | Trả về giá trị dương. |
| Masked(mask MaskWs) V | Áp dụng mặt nạ (mask) lên giá trị V. | Các phần tử bị che sẽ được đặt về 0. | Giữ nguyên các phần tử không bị che. | Thường dùng trong cơ chế Attention. | Hỗ trợ các kiểu mask nhị phân. | Tối ưu hóa cho các phép toán ma trận thưa. | Đảm bảo tính bảo mật của dữ liệu. | Có thể tùy chỉnh giá trị thay thế cho mask. | Tương thích với các thư viện deep learning phổ biến. |
| Max(y V) V | Trả về giá trị lớn nhất giữa y và V. | So sánh từng phần tử trong tensor. | Hỗ trợ so sánh các kiểu dữ liệu số thực. |  | Thường dùng trong các hàm kích hoạt (activation functions). | Đảm bảo tính ổn định của gradient. | Tối ưu hóa cho các phép toán phần tử (element-wise). |  | Trả về giá trị V nếu y nhỏ hơn V. |
| Min(y V) V | Trả về giá trị nhỏ nhất giữa y và V. | Y | Y |  | Y | Y | Y |  | Y |
| Mul(y V) V | Y | Y | Y |  | Y | Y | Y |  | Y |
| MulAdd(y V, z V) V |  |  |  |  |  |  |  |  | Y |
| Neg V | Y | Y | Y | Y |  |  |  |  | Y |
| Not V | Y | Y | Y | Y | Y | Y | Y | Y |  |
| Or(y V) V | Y | Y | Y | Y | Y | Y | Y | Y |  |
| Sqrt V |  |  |  |  |  |  |  |  | Y |
| Sub(y V) V | Y | Y | Y | Y | Y | Y | Y | Y | Y |
| SubSaturated(y V) V | Y | Y |  |  | Y | Y |  |  |  |
| Xor(y V) V | Y | Y | Y | Y | Y | Y | Y | Y |  |

### Các thao tác mặt nạ (masking) boolean và vector

| (x V).Method(...) V | Int8s | Int16s | Int32s | Int64s | Uint8s | Uint16s | Uint32s | Uint64s | Float32s |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| And(y V) V | Y | Y | Y | Y | Y | Y | Y | Y |  |
| AndNot(y V) V | Y | Y | Y | Y | Y | Y | Y | Y |  |
| CarrylessMultiplyEven(y V) V |  |  |  |  |  |  |  | Y |  |
| CarrylessMultiplyOdd(y V) V |  |  |  |  |  |  |  | Y |  |
| IfElse(mask MaskWs, y V) V | Y | Y | Y | Y | Y | Y | Y | Y | Y |
| Masked(mask MaskWs) V | Y | Y | Y | Y | Y | Y | Y | Y | Y |
| Not V | Y | Y | Y | Y | Y | Y | Y | Y |  |
| Or(y V) V | Y | Y | Y | Y | Y | Y | Y | Y |  |
| Xor(y V) V | Y | Y | Y | Y | Y | Y | Y | Y |  |

### Các phép toán so sánh

| (x V).Method(...) M | Int8s | Int16s | Int32s | Int64s | Uint8s | Uint16s | Uint32s | Uint64s | Float32s |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Equal(y V) MaskWs | Y | Y | Y | Y | Y | Y | Y | Y | Y |
| Greater(y V) MaskWs | Y | Y | Y | Y |  | Y | Y | Y | Y |
| GreaterEqual(y V) MaskWs | Y | Y | Y | Y |  | Y | Y | Y | Y |
| Less(y V) MaskWs | Y | Y | Y | Y |  | Y | Y | Y | Y |
| LessEqual(y V) MaskWs | Y | Y | Y | Y |  | Y | Y | Y | Y |
| NotEqual(y V) MaskWs | Y | Y | Y | Y | Y | Y | Y | Y | Y |

### Các thao tác chuyển đổi

| (x V).Method(...) U | Int8s | Int16s | Int32s | Int64s | Uint8s | Uint16s | Uint32s | Uint64s | Float32s |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| ConvertToFloatW FloatWs |  |  | Y |  |  |  |  |  |  |
| ConvertToIntW IntWs |  |  |  |  | Y | Y | Y | Y | Y |
| ConvertToUintW UintWs | Y | Y | Y | Y |  |  |  |  |  |
| ToMask (sang MaskWs) | Y | Y | Y | Y |  |  |  |  |  |

### Các phương thức Mask

| (m M).Method(...) M) | Mask8s | Mask16s | Mask32s | Mask64s |
| --- | --- | --- | --- | --- |
| And(y M) M | Y | Y | Y | Y |
| Or(y V) V | Y | Y | Y | Y |
| String string | Y | Y | Y | Y |
| ToIntWs (to IntWs) | Y | Y | Y | Y |

### Các thao tác dịch và xoay

| (x V).Method V | Int8s | Int16s | Int32s | Int64s | Uint8s | Uint16s | Uint32s | Uint64s | Float32s |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| RotateAllLeft(dist uint64) V |  | Y | Y | Y |  | Y | Y | Y |  |
| RotateAllRight(dist uint64) V |  | Y | Y | Y |  | Y | Y | Y |  |
| ShiftAllLeft(dist uint64) V |  | Y | Y | Y |  | Y | Y | Y |  |
| ShiftAllRight(dist uint64) V |  | Y | Y |  |  | Y | Y | Y |  |

### Các thao tác thay đổi hình dạng (reshape) không tốn chi phí

| (x V).Method(...) U | Int8s | Int16s | Int32s | Int64s | Uint8s | Uint16s | Uint32s | Uint64s | Float32s |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| ToBits UintWs | Y | Y | Y | Y |  |  |  |  | Y |
| ReshapeToUint8s Uint8s |  |  |  |  |  | Y | Y | Y |  |
| ReshapeToUint16s Uint16s |  |  |  |  | Y |  | Y | Y |  |
| ReshapeToUint32s Uint32s |  |  |  |  | Y | Y |  | Y |  |
| ReshapeToUint64s Uint64s |  |  |  |  | Y | Y | Y |  |  |
| BitsToFloatW FloatWs |  |  |  |  |  |  | Y | Y |  |
| BitsToIntW IntWs |  |  |  |  | Y | Y | Y | Y |  |

### Chuyển đổi sang/từ mã đặc thù của nền tảng

Có thể xảy ra trường hợp gói simd quá hạn chế đối với một số phần của ứng dụng cụ thể, hoặc chúng tôi chưa cung cấp trình mô phỏng đầy đủ cho một tính năng cần thiết nào đó. Đối với trường hợp này, gói simd hỗ trợ chuyển đổi sang và từ SIMD đặc thù của kiến trúc. Mỗi kiểu vector trong gói simd đều có phương thức chuyển đổi ToArch trả về một kiểu any. Kiểu any đó có thể được xác nhận kiểu (type-assert) sang một trong các kiểu đặc thù kiến trúc của nền tảng. Để chuyển đổi ngược lại, hãy sử dụng một trong các hàm simd.<SimdType>FromArch. Đối với mã nguồn di động (portable code), điều này tạo ra nghĩa vụ phải viết mã đặc thù kiến trúc cho từng nền tảng, bao gồm cả việc mô phỏng.

_Bài gốc còn tiếp._ Xem tiếp tại: <https://go.dev/blog/simd-experiment>
