- lsr là chương trình thay thế
ls(1) mới được phát triển bằng thư viện IO dựa trên io_uring là ourio
- So với
ls hiện tại và các công cụ thay thế (eza, lsd, uutils ls), tốc độ chạy lệnh nhanh vượt trội và số lượng system call ít hơn hơn 10 lần
- Mọi IO chính như mở thư mục, stat, lstat đều được xử lý bất đồng bộ và theo lô bằng io_uring để tối đa hóa hiệu năng. Càng nhiều tệp càng nhanh
- Tận dụng StackFallbackAllocator của Zig để giảm thiểu các lệnh gọi
mmap khi cấp phát bộ nhớ
- Được build tĩnh không cần dynamic linking nên kích thước file thực thi còn nhỏ hơn
ls hiện tại
Giới thiệu và ý nghĩa
- Dự án lsr là công cụ liệt kê thư mục nhanh sử dụng io_uring như một phần thay thế cho lệnh
ls thông thường
- Khi so sánh với
ls, eza, lsd, uutils ls, công cụ này cho thấy hiệu năng nổi bật về tốc độ thực thi và mức sử dụng system call
- Tự thực hiện nhiều IO nhất có thể bằng thư viện io tự phát triển là ourio
- Thông qua benchmark, lsr chứng minh được hiệu năng nhanh và chất lượng ngay cả trong môi trường có số lượng tệp lớn
Kết quả benchmark
- Dùng
hyperfine để đo thời gian thực thi của từng lệnh trong thư mục có n tệp thông thường
lsr -al với 10–10.000 tệp cho thấy thời gian chạy ngắn hơn vượt trội so với ls và các công cụ thay thế
- Ví dụ: với 10.000 tệp, lsr đạt 22.1ms, nhanh nhất so với
ls hiện tại (38.0ms), eza (40.2ms), lsd (153.4ms), uutils ls (89.6ms)
- Việc thống kê system call được thực hiện bằng
strace -c
lsr -al: duy trì số lần gọi rất thấp, từ tối thiểu 20 lần (n=10) đến tối đa 848 lần (n=10.000)
ls đạt tối đa 30.396 lần (n=10.000), lsd là 100.512 lần, các công cụ thay thế khác cũng ở mức hàng nghìn đến hàng trăm nghìn lần
- Trong cùng điều kiện, lsr dùng ít syscall hơn ít nhất 10 lần và đạt hiệu quả cao nhất
Cấu trúc và cách triển khai của lsr
- Chương trình hoạt động qua 3 giai đoạn: phân tích tham số, thu thập dữ liệu, xuất dữ liệu
- Mọi IO đều xảy ra ở giai đoạn thứ hai là thu thập dữ liệu, và mọi truy cập tệp/truy vấn thông tin có thể đều được xử lý bằng io_uring
- Mở thư mục đích, stat, lstat, truy vấn thời gian/thông tin user/group đều được thực hiện trên nền io_uring
- Xử lý stat theo lô giúp giảm mạnh số lượng system call
- Dùng Zig StackFallbackAllocator để cấp phát trước 1MB bộ nhớ, giảm thiểu các system call bổ sung như
mmap
Build tĩnh và tối ưu hóa
- Do được build tĩnh hoàn toàn không cần liên kết động với libc, overhead khi chạy thấp hơn đáng kể
- So với GNU
ls, kích thước bản build ReleaseSmall của lsr nhỏ hơn: 138.7KB so với 79.3KB
- Tuy nhiên, lsr không hỗ trợ locale (ngôn ngữ/khu vực).
ls thông thường phát sinh overhead để hỗ trợ nhiều ngôn ngữ khác nhau
Phân tích system call và vấn đề hiệu năng
lsd gọi clock_gettime hơn 5 lần cho mỗi tệp; lý do chưa rõ (có thể là để đo thời gian nội bộ)
- Công đoạn sắp xếp (sorting) chiếm phần đáng kể trong toàn bộ công việc (khoảng 30%)
uutils ls có hiệu quả system call cao nhưng chậm ở phần xử lý sắp xếp
- Chỉ riêng việc áp dụng io_uring cũng cho thấy khả năng cải thiện hiệu năng đột phá trong các môi trường IO tải cao như máy chủ
Kết luận
- Thời gian phát triển không quá dài, và hiệu quả tối ưu syscall vượt xa kỳ vọng
- lsr là một công cụ thay thế
ls mang tính thử nghiệm, đồng thời đạt được tốc độ cao, ít system call và kích thước gọn nhẹ
- Rất phù hợp cho môi trường có lượng tệp lớn hoặc các hệ thống coi trọng IO hiệu năng cao
- Dù có một số hạn chế như không hỗ trợ locale, công cụ này vẫn cho thấy kết quả mang tính đột phá cả trong thực tế lẫn benchmark
1 bình luận
Ý kiến trên Hacker News
Tác giả dự án cho biết mình là người viết ra nó, đồng thời có thể xem bài giới thiệu về
lsrdựa trên io_uring tại đâyls(1)nguyên bản của UNIX rất nhanh nhờ thiết kế đơn giản, nhưng sau khi thêm nhiều tính năng, VFS, nhiều bộ ký tự, hỗ trợ màu sắc, v.v. thì các chi phí nhỏ dần tích tụ và làm nó chậm đi. Đây là một cuộc thảo luận thú vị về chi phí trừu tượng mà io_uring xử lýbfs -j1) hay khôngtim(liên kết giới thiệu) thì có lẽ sẽ tốt hơn hyperfine. Nó được viết bằng Nim nên có thể là một thử thách, nhưng việc tên gọi giống nhau một cách ngẫu nhiên cũng khá thú vịlibevringtự viết cũng còn rất sơ khai, nên nếu cần thì vẫn sẵn sàng thay bằng ourio. Có cảm giác rằng nếu các dự án nền Zig hỗ trợ binding C/C++ thì sẽ hữu ích khi di chuyển từ C/C++ sang ZigTò mò không biết hiệu năng của lsr trên máy chủ NFS sẽ ra sao, đặc biệt trong điều kiện mạng không tốt. Việc dùng blocking POSIX syscall với dịch vụ mạng thiếu ổn định rõ ràng là một nhược điểm trong thiết kế NFS. Cũng đáng quan sát xem io_uring có thể giảm nhẹ vấn đề này đến mức nào
lsv.v.) không cần tự xử lý trực tiếp tình huống lỗi mạng. Giao thức NFS ban đầu là stateless nên ngay cả khi máy chủ khởi động lại, client vẫn tự phục hồi được. Muốn biết liệu io_uring có chuyển tiếp lỗi đúng cách trong những trường hợp như vậy hay không. Cũng quan tâm nó xử lý timeout của NFS theo cách nàoctrl+ckhông có tác dụng trong ứng dụng đang đọc thư mục NFS là một bất tiện ai cũng biết. Về mặt lý thuyết, tùy chọn mountintrtừng hỗ trợ chuyển tín hiệu tới thao tác đang chạy trên máy chủ từ xa để có thể ngắt, nhưng trên Linux nó đã bị loại bỏ từ lâu (hiện chỉ còn tùy chọnsoft) (tham khảo1, tham khảo2(hỗ trợ trên FreeBSD))Khá thú vị khi số lần gọi syscall giảm tới 35 lần nhưng tốc độ cải thiện chỉ khoảng 2 lần
Dự án này gây hứng thú hơn như một ví dụ ứng dụng io_uring để tìm kiếm lợi ích tốc độ dài hạn, hoặc như một hướng dẫn cách dùng, hơn là vì lợi ích thực tế rõ ràng. So với các công cụ hiện có như eza, chưa thấy động lực trực quan về việc tại sao cần nó. Nếu liệt kê 10 nghìn tệp là 40ms so với 20ms thì với một lần chạy đơn lẻ chắc khó mà cảm nhận được khác biệt
ls/ducó thể mất vài phút. Các lệnh mặc định của coreutils thường không tận dụng tốt hiệu năng SSD hiện đạilsr thì tốt, nhưng eza vượt trội hơn về hỗ trợ tô màu và biểu tượng. Bản thân đang đặt thành
eza --icons=always -1, nên các tệp nhạc (.opusv.v.) tự hiện bằng biểu tượng và màu sắc, còn trong lsr thì chỉ hiện như tệp bình thường. Dù vậy, vẫn cảm nhận rất rõ rằng lsr cực nhanh và cũng dễ vá. Ngoài ra còn mong có thêmcatvà các tiện ích khác được làm theo kiểu này, đồng thời thấy thú vị khi nó dùng tangled.sh và atproto. Vì viết bằng Zig nên với người mới còn thấy dễ tiếp cận hơn Rustbatlà bản thay thếcathiện đại (xem bat)lscho màu rất đẹpTừng thắc mắc vì sao không phải mọi công cụ CLI đều dùng io_uring. Khi kết nối nvme qua usb 3.2 gen2, tốc độ với công cụ thông thường là 740MB/s nhưng với công cụ dựa trên aio hoặc io_uring thì lên tới 1005MB/s. Có lẽ còn có tác dụng từ chiến lược queue length và giảm lock
#ifdef, nên việc đưa vào các công nghệ mới chỉ dành cho nền tảng/phiên bản cụ thể diễn ra chậm. Giờ đây cảm thấy lợi ích của tương thích giữa nhiều nền tảng kiểu POSIX cũng không còn lớn như trướcQuan sát bằng strace cho thấy lsd gọi
clock_gettimekhoảng 5 lần cho mỗi tệp. Không rõ chính xác nguyên nhân là gì; có thể là để tính “vài phút/giờ/ngày trước” cho từng mốc thời gian, hoặc cũng có thể là di sản từ thư việnclock_gettimethực ra không phải syscall thật mà được xử lý qua vDSO (xemman 7 vDSO). Có người tự hỏi liệu Zig có đang không tận dụng cấu trúc này hay khôngHơi lạc đề một chút, nhưng muốn biết kinh nghiệm thực tế hoặc số liệu benchmark về việc io_uring giảm bao nhiêu overhead độ trễ socket ở mức micro giây so với LD_PRELOAD trong môi trường NIC 10G trên các máy chủ enterprise cao cấp như Mellanox 4 hoặc 5. Có vẻ hiệu ứng của chúng không cộng dồn, nên nếu ai có trải nghiệm trực tiếp thì rất muốn nghe con số cụ thể
io_uring không hỗ trợ getdents nên lợi thế đáng kể chủ yếu xuất hiện ở bulk stat (ví dụ:
ls -l). Cũng có chút tiếc vì nếu có thể bất đồng bộ hóa và xử lý chồng lấp phần getdents thì sẽ tốt hơngetdents+stat) thì dự đoán một phần lợi thế riêng của io_uring sẽ bị triệt tiêuThấy khá thú vị khi có biểu tượng cho phần mở rộng
.mjsvà.cjs, nhưng lại không có cho các phần mở rộng như.c,.h,.sh