io_uring: vì sao mô hình I/O này nhanh hơn hẳn cách cũ
Ảnh: Sematext

io_uring: vì sao mô hình I/O này nhanh hơn hẳn cách cũ

Mỗi lời gọi hệ thống đều có phí; máy chủ xử lý hàng trăm nghìn thao tác mỗi giây thì phí đó cộng dồn lớn. io_uring gần như xoá nó bằng hai vòng đệm chia sẻ — và trả giá.

Mỗi lời gọi hệ thống — mỗi lần chương trình nhờ nhân làm giúp một việc như đọc tệp hay nhận gói tin — đều có phí: chuyển từ chế độ người dùng sang chế độ nhân, lưu trạng thái, kiểm tra tham số, rồi chuyển ngược lại. Một lời gọi đơn lẻ thì phí này nhỏ đến mức không đáng kể. Nhưng với một máy chủ xử lý hàng trăm nghìn thao tác vào ra mỗi giây, phí đó cộng dồn thành một phần đáng kể của toàn bộ công việc CPU — bạn đang trả thuế cho mỗi lần gõ cửa nhân.

Ý tưởng: hai vòng đệm chia sẻ

io_uring dựng hai hàng đợi vòng nằm trong vùng nhớ mà cả chương trình lẫn nhân đều nhìn thấy trực tiếp:

  • Hàng gửi — chương trình ghi các yêu cầu vào đây: "đọc tệp này", "gửi gói này".
  • Hàng nhận — nhân ghi kết quả trả về vào đây khi làm xong.

Vì cả hai bên cùng thấy chung vùng nhớ này, chương trình có thể xếp hàng chục thao tác vào hàng gửi rồi chỉ báo nhân một lần duy nhất — thay vì gõ cửa nhân hàng chục lần. Ở chế độ có luồng nhân thăm dò, thậm chí không cần một lời gọi hệ thống nào: nhân tự nhìn vào hàng và thấy có yêu cầu mới. Phí gọi hệ thống, thứ trước đây là bắt buộc cho mỗi thao tác, gần như biến mất.

Khác gì các cách trước đó

Điểm dễ nhầm nhất: epoll, cơ chế bất đồng bộ phổ biến trước đây, chỉ báo cho bạn biết khi nào một tệp sẵn sàng — rồi bạn vẫn phải tự gọi read để thật sự đọc. Nó bất đồng bộ ở khâu chờ nhưng vẫn đồng bộ ở khâu làm. io_uring bất đồng bộ ở cả hai: bạn gửi yêu cầu "đọc" và nhận về dữ liệu đã đọc khi nó xong, không có bước gọi thủ công ở giữa.

Nó cũng không giới hạn ở socket mạng như nhiều cơ chế cũ. Đọc ghi tệp thường, mở tệp, đổi tên, đồng bộ đĩa — thậm chí nhiều thao tác nối tiếp phụ thuộc nhau ("đọc xong tệp này thì ghi sang tệp kia") — đều xếp chung vào hàng được, để nhân tự chạy cả chuỗi mà không cần chương trình can thiệp giữa chừng.

Cái giá

Sức mạnh đó đi kèm bề mặt tấn công lớn hơn hẳn, và trong lịch sử ngắn của mình io_uring đã dính một loạt lỗ hổng nghiêm trọng — đến mức vài nhà cung cấp dịch vụ đám mây chủ động vô hiệu hoá nó trong môi trường nhiều người dùng chung, vì rủi ro không đáng đánh đổi. Mô hình lập trình cũng khó hơn nhiều: khi một thao tác đang "bay" trong hàng, bộ đệm dữ liệu của nó phải giữ nguyên vẹn cho tới khi nhân báo xong — quản lý sai vòng đời bộ đệm là một trong những nguồn lỗi tinh vi nhất.

Kết luận thực dụng: nếu ứng dụng của bạn không bị nghẽn ở số lời gọi hệ thống — mà phần lớn ứng dụng thì không — io_uring chỉ mang thêm độ phức tạp chứ không mang thêm tốc độ. Hãy đo để biết mình có thật sự nghẽn ở đó không, trước khi đánh đổi sự đơn giản lấy một tối ưu có thể không tồn tại trong trường hợp của bạn.

Chia sẻ

Thảo luận