Bài boxing đo chi phí đưa một giá trị vào interface. Bài này đo thao tác ngược: lấy giá trị ra — h.(Vuong), h.(type). Nhiều người ngại type assertion vì nghĩ nó "đắt như reflection" hay "type switch nhiều nhánh thì chậm dần". Cả hai đều sai. Ta sẽ đọc assembly để thấy cơ chế thật, rồi đo bằng benchmark.
Cơ chế: interface là cặp {itab, data}
Nhắc lại từ bài interface internals: một giá trị interface là cặp {itab, data}. itab là con trỏ tới một bảng mô tả cặp (kiểu động, interface) — và quan trọng: mỗi cặp (kiểu, interface) chỉ có đúng một itab duy nhất trong toàn chương trình. Điều này biến việc kiểm kiểu thành so sánh con trỏ.

Hình 1: Bốn thao tác. Assert cụ thể so một con trỏ itab tĩnh; type switch dựng bảng nhảy; assert tới interface khác tra itab của kiểu động.
Đọc assembly: assert cụ thể là một phép so con trỏ
Đây là phần thuyết phục nhất. Biên dịch h.(Vuong) với -gcflags=-S trên arm64, phần cốt lõi ra đúng ba lệnh:
MOVD $go:itab.main.Vuong,main.Hinh(SB), R2 // nạp địa chỉ itab TĨNH của (Vuong, Hinh)
CMP R2, R0 // so với itab trong interface
BNE sai // khác → nhánh thất bại
Compiler biết tĩnh lúc biên dịch địa chỉ itab của cặp (Vuong, Hinh) — nó là một hằng địa chỉ. Nên assert chỉ là: nạp hằng đó, so với con trỏ itab trong giá trị interface, rẽ nhánh. Không quét bảng, không gọi runtime, không phản chiếu. Đó là lý do nó ~1 ns.
Đo thật: assert rẻ hơn cả một lời gọi
Benchmark bốn thao tác, dùng slice kiểu động trộn (Vuong/Tron) cấp package để buộc kiểu động thật (tránh compiler devirtualize):

Hình 2: AssertCuThe 1,166 ns và TypeSwitch 1,211 ns — rẻ hơn baseline gọi qua interface (1,881 ns). Mọi thao tác 0 cấp phát. Switch 8 nhánh (1,195 ns) nhanh ngang Switch 2 nhánh (1,206 ns).
Con số:
- Assert cụ thể (
h.(Vuong), comma-ok): 1,166 ns, 0 cấp phát. - Type switch (2 nhánh): 1,211 ns, 0 cấp phát.
- Gọi qua interface (baseline, không assert): 1,881 ns.
- Assert tới interface khác (
h.(CoDientich)rồi gọi): 1,902 ns.
Điều đáng chú ý: assert cụ thể và type switch (~1,2 ns) rẻ hơn cả một lời gọi phương thức qua interface (~1,88 ns). Vì assert chỉ là so con trỏ, còn dispatch phải nhảy gián tiếp qua itab. Và tất cả 0 cấp phát — lấy giá trị ra khỏi "hộp" interface không tốn heap (ngược với đưa vào có thể tốn, như bài trước đo).
Type switch không tuyến tính theo số nhánh
Ngộ nhận phổ biến: "type switch 8 case phải chậm hơn 2 case vì nó thử từng cái". Đo thật bác bỏ:
- Switch 2 nhánh: 1,206 ns.
- Switch 8 nhánh (khớp mọi kiểu): 1,195 ns.
Bằng nhau. Compiler không so tuần tự từng case; nó dựng một bảng nhảy (jump table) dựa trên con trỏ kiểu, nên tra nhánh đúng là O(1) bất kể số case. Thêm một case gần như miễn phí. Đây là lý do type switch là công cụ chính đáng để phân loại kiểu, không cần né vì sợ chậm.
Ứng dụng thực tế
errors.As và phân loại lỗi. errors.As(err, &target) bản chất là một chuỗi type assertion đi qua chuỗi Unwrap(). Mỗi bước là một phép so itab rẻ. Đừng ngại phân loại lỗi bằng type switch/errors.As — chi phí không đáng kể so với logic xử lý lỗi.
Xử lý giá trị động từ JSON/any. Khi giải mã JSON vào any, bạn nhận map[string]any, []any, float64, string... và phải type switch để xử lý. Đây là chỗ interface thật sự cần thiết (kiểu không biết lúc biên dịch) — và type switch ở đây rẻ, cứ dùng thoải mái.
comma-ok để tránh panic. Luôn ưu tiên dạng v, ok := h.(T) thay v := h.(T) khi không chắc kiểu. Dạng một-trả-về panic nếu sai kiểu; dạng comma-ok trả ok=false an toàn, và như đo được, không đắt hơn gì.
Đánh đổi cần cân nhắc
Type assertion rẻ, nhưng lạm dụng nó là mùi thiết kế. Chi phí runtime không phải lý do để tránh type assertion — nhưng một hàm đầy switch v.(type) để "bắt chước đa hình" thường nên thay bằng một phương thức trên interface (để dispatch lo việc rẽ nhánh) hoặc bằng generics. Đó là vấn đề thiết kế, không phải hiệu năng.
Assert tới interface đắt hơn assert cụ thể một chút. h.(CoDientich) phải tra itab cho cặp (kiểu động, interface đích) thay vì so một con trỏ tĩnh. Chênh lệch nhỏ (và vẫn 0 cấp phát), nhưng trong vòng cực nóng, assert thẳng tới kiểu cụ thể nếu biết trước sẽ nhỉnh hơn.
Generics khi kiểu biết lúc biên dịch. Nếu bạn type switch chỉ để xử lý vài kiểu đã biết, generics cho code không cần assertion, không dispatch runtime, và compiler kiểm kiểu chặt hơn. Type switch dành cho kiểu thật sự chỉ biết lúc chạy.
Ba ý mang về
- Assert tới kiểu cụ thể là một phép so con trỏ itab tĩnh: đọc assembly thấy
h.(Vuong)biên dịch thành nạp hằng địa chỉ itab →CMP→ rẽ nhánh, không quét không phản chiếu — đo thật 1,166 ns, 0 cấp phát, rẻ hơn cả một lời gọi qua interface (1,881 ns). - Type switch không tuyến tính theo số nhánh: đo thật 8 nhánh (1,195 ns) nhanh ngang 2 nhánh (1,206 ns) vì compiler dựng bảng nhảy theo con trỏ kiểu — thêm
casegần như miễn phí. - Lấy giá trị ra khỏi interface không tốn heap: mọi thao tác assert/type switch đo được 0 cấp phát — ưu tiên dạng comma-ok để an toàn, dùng thoải mái cho
errors.Asvà xử lýanytừ JSON, nhưng cân nhắc generics/phương thức khi kiểu biết lúc biên dịch.
Phần sau ta rời chủ đề interface để mổ xẻ một cấu trúc cũng ẩn chứa con trỏ: Phần sau xem con trỏ hàm và closure được biểu diễn thật thế nào trong bộ nhớ — closure thực chất là gì, khi nào nó cấp phát heap, và chi phí gọi qua con trỏ hàm.