Ở phần pipe ta đo một cách nối hai tiến trình cùng máy. Nhưng khi hai tiến trình cần giao tiếp kiểu client-server (nhiều kết nối, hai chiều), người ta thường dùng socket — và có hai lựa chọn cùng máy: Unix domain socket (AF_UNIX) hay TCP qua loopback (127.0.0.1). Trực giác — và rất nhiều lời khuyên trên mạng — nói "dùng Unix socket, nó nhanh hơn TCP loopback vì bỏ qua cả stack mạng". Nghe hợp lý. Tôi đo trong container gcc:13 (ARM), và phép đo bác bỏ huyền thoại đó một cách dứt khoát — một lời nhắc rằng lời khuyên "hiển nhiên" cần đo lại trên kernel hiện đại.
Hai cách nối, và lập luận "bỏ stack"
Unix domain socket (AF_UNIX) là một socket sống hoàn toàn trong nhân, nối hai tiến trình cùng máy — không đụng gì tới mạng. TCP loopback dùng chính giao thức TCP/IP nhưng gửi qua giao diện lo (127.0.0.1), không ra card mạng thật. Lập luận cổ điển cho Unix socket: TCP phải làm nhiều việc "mạng" — đóng gói thành segment, tính checksum, quản lý cửa sổ luồng, xác nhận — còn Unix socket bỏ qua tất cả, nên phải nhanh hơn.
Lập luận đó từng đúng. Nhưng có hai điều nó bỏ sót trên kernel hiện đại: (1) loopback TCP được tối ưu rất mạnh — dùng segment lớn (MTU của lo tới 64 KB), và bỏ qua checksum vì dữ liệu không rời máy; (2) với IPC chặn (blocking), chi phí át không phải giao thức mà là đánh thức tiến trình đang ngủ — cái ~9,5 µs ta đã gặp ở pipe và chuyển ngữ cảnh. Tôi đo để xem hai điều đó lấn át lập luận "bỏ stack" tới đâu.
Đo: độ trễ hòa, throughput TCP còn thắng
Tôi đo cả hai — throughput (đẩy 256 MB) và độ trễ (ping-pong 1 byte):
AF_UNIX : throughput 7,54 GB/s | độ trễ 1 chiều 9.505 ns
TCP loopback : throughput 11,07 GB/s | độ trễ 1 chiều 9.686 ns
-> throughput: UNIX / TCP = 0,68× (TCP nhanh hơn!)
độ trễ: TCP / UNIX = 1,02× (gần như hòa)
Hai kết quả đều ngược huyền thoại. Độ trễ gần như hòa — 9.505 vs 9.686 ns mỗi chiều, chênh 2%. Vì sao? Vì cả hai đều bị chi phí đánh thức tiến trình (~9,5 µs) thống trị: mỗi lần ping-pong, bên nhận đang ngủ chờ dữ liệu và bên gửi phải đánh thức nó — cái giá này bằng nhau bất kể socket loại gì, và nó lớn hơn hàng trăm lần mọi khác biệt giao thức (checksum, đóng gói chỉ tốn vài chục ns). Giao thức gần như không quan trọng khi cái đắt là đánh thức.
Và throughput thì TCP loopback còn thắng — 11,07 vs 7,54 GB/s. Loopback TCP hiện đại gom dữ liệu thành đoạn lớn và bỏ checksum trên lo, chuyển bytes hiệu quả hơn cả AF_UNIX stream trong cấu hình này. "TCP có overhead nên chậm hơn" đơn giản là không còn đúng cho loopback.
Một lần tôi đo hớ: huyền thoại "bỏ stack nên nhanh"
Tôi vào đo với niềm tin sách vở và diễn đàn: "Unix socket nhanh hơn hẳn TCP loopback — luôn ưu tiên nó cho IPC cùng máy vì tránh được overhead mạng". Phép đo đập tan cả hai vế. Độ trễ: hòa (2%). Throughput: TCP còn nhanh hơn 47%. Không hề có "nhanh hơn hẳn".
Bài học đo lường: lời khuyên hiệu năng "hiển nhiên" — nhất là loại dựa trên "tầng này bỏ được tầng kia nên nhanh hơn" — cần đo lại trên hệ thống hiện đại, vì kernel đã tối ưu nhiều thứ mà trực giác cũ không tính tới. Lập luận "bỏ stack mạng" nghe rất thuyết phục, nhưng nó giả định (a) stack mạng đắt đáng kể so với các chi phí khác, và (b) khác biệt đó nổi lên trên nền chi phí chung. Đo cho thấy cả hai giả định sai với IPC cùng máy: chi phí chung (đánh thức tiến trình ~9,5 µs, copy dữ liệu) át hẳn phần "mạng", và loopback TCP đã được tối ưu tới mức không còn là gánh nặng. Nếu tôi tin huyền thoại mà không đo, tôi đã đổi TCP sang Unix socket kỳ vọng tăng tốc — và nhận về không gì cả, thậm chí chậm hơn ở throughput.
Cần công bằng: điều này không có nghĩa Unix socket vô dụng. Nó có những ưu điểm thật — chỉ là không phải tốc độ: không cần cấp phát cổng (tránh xung đột port), phân quyền truy cập theo file (một socket file có chủ và mode như file thường), và khả năng truyền file descriptor giữa tiến trình (SCM_RIGHTS) mà TCP không làm được. Đó là những lý do chính đáng để chọn Unix socket — nhưng "nhanh hơn" thì không nằm trong danh sách đó, ít nhất trên kernel này.
Vì sao điều này quan trọng khi lập trình
Hệ quả đầu tiên: chọn Unix socket vì tính năng, không vì tốc độ. Nếu bạn cần tránh xung đột cổng, kiểm soát truy cập bằng quyền file, hay truyền fd giữa tiến trình — Unix socket đúng. Nhưng nếu bạn đang cân nhắc đổi TCP loopback sang Unix socket chỉ để nhanh hơn, hãy đo trước: rất có thể bạn không được gì. Với IPC cùng máy nói chung, cả hai (và pipe) cùng một bậc hiệu năng.
Hệ quả thứ hai: với IPC chặn, nút thắt là đánh thức tiến trình, không phải giao thức. Nếu độ trễ IPC của bạn quá cao, đổi socket loại này sang loại kia sẽ không cứu — cái ~9,5 µs mỗi lần đánh thức là sàn chung. Muốn thấp hơn thật sự thì phải tránh chặn (busy-poll, shared memory với vòng lặp thăm dò), đổi bằng CPU — hoặc giảm số lần trao đổi (gom thông điệp), đúng bài học batching xuyên suốt.
Hệ quả thứ ba là tinh thần đo lường: đo lại lời khuyên "hiển nhiên" trên hệ thống hiện đại — tối ưu của kernel có thể đã lật ngược nó. Con số mang theo: với IPC cùng máy, độ trễ Unix domain socket và TCP loopback gần như HÒA (9.505 vs 9.686 ns/chiều, 1,02×) — vì chi phí át là đánh thức tiến trình (~9,5µs, như pipe/chuyển ngữ cảnh), loại socket gần như không đổi; còn throughput TCP loopback thậm chí NHANH HƠN (11,07 vs 7,54 GB/s) vì loopback hiện đại tối ưu mạnh (đoạn 64KB, bỏ checksum trên lo). Huyền thoại "Unix socket nhanh hơn vì bỏ qua stack mạng" không sống sót phép đo; Unix socket vẫn hơn ở TÍNH NĂNG (không cổng, phân quyền file, truyền fd), không phải tốc độ. "Bỏ được một tầng" không tự động nghĩa "nhanh hơn".
Thử ba mươi giây
Nếu code của bạn nối hai tiến trình cùng máy và bạn từng "tối ưu" bằng cách đổi từ TCP loopback sang Unix socket (hay định làm) — hãy đo cả hai bằng một ping-pong đơn giản trước khi tin nó nhanh hơn. Rất có thể bạn thấy độ trễ gần bằng nhau, vì cả hai cùng trả ~9,5 µs đánh thức tiến trình mỗi lần trao đổi. Rồi hỏi câu quan trọng hơn: bạn chọn Unix socket vì tốc độ (có thể không có thật) hay vì tính năng (không cổng, phân quyền file, truyền fd — những thứ thật sự khác biệt)? Ba mươi giây đo đó cứu bạn khỏi một "tối ưu" phổ biến mà không mang lại gì — và nhắc rằng lời khuyên hiệu năng truyền miệng, nhất là loại "bỏ tầng X nên nhanh hơn", luôn đáng kiểm lại trên chính máy của bạn.