2 điểm bởi GN⁺ 2025-07-19 | 1 bình luận | Chia sẻ qua WhatsApp
  • 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ộisố 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 thimứ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

 
GN⁺ 2025-07-19
Ý 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ề lsr dựa trên io_uring tại đây

    • Chia sẻ kinh nghiệm từng làm dự án I18N tại Sun. Để hỗ trợ nhiều môi trường khác nhau (bản địa hóa, utf8, v.v.) thì phải thêm nhiều xử lý vào chương trình, nên chi phí để tạo ra kết quả và tốc độ có xu hướng tỉ lệ nghịch. ls(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ý
    • Dự án bfs cũng dùng io_uring (liên kết mã nguồn). Rất tò mò về so sánh hiệu năng giữa lsr và bfs -ls. Hiện tại bfs chỉ dùng io_uring khi đa luồng, nhưng cũng đáng cân nhắc liệu có nên tận dụng nó cả ở single thread (bfs -j1) hay không
    • Nếu đo thời gian bằng tim (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ị
    • Đang tính đến việc port dự án C++ sang Zig. libevring tự 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 Zig
    • Bài giới thiệu đó giải thích bối cảnh tốt hơn nên dự định dùng nó làm liên kết chính, còn thread repo sẽ được thêm lên phía trên
  • Tò 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

    • Người thiết kế NFS đã triển khai để hệ thống phân tán hoạt động rất nhất quán như một ổ cứng. Ưu điểm là các công cụ hiện có (ls v.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ào
    • Ở nhà dùng NFS $HOME trên nhiều PC, và nếu mạng tốt, tránh các trường hợp khó như ghi song song, thì trải nghiệm sử dụng NFS nhìn chung khá ổn. Tuy nhiên đã từng rất khổ sở vì hiện tượng treo/ngắt khi cáp mạng không ổn định
    • Tình huống ctrl+c khô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 mount intr từ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ọn soft) (tham khảo1, tham khảo2(hỗ trợ trên FreeBSD))
    • Samba cũng có vấn đề tương tự
  • 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

    • Phần lớn syscall được thực hiện qua VDSO nên chi phí không quá lớn
    • Từng đọc một benchmark về io_uring trong đó syscall dựa trên io_uring đôi khi còn nặng hơn syscall truyền thống. Dù vậy, cảm nhận thực tế vẫn là một cải thiện khá lớn. Không nhớ chính xác nguồn nhưng nó để lại ấn tượng mạnh
  • 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

    • Đây là một dự án thử nghiệm làm ra chỉ để vui và học cách dùng io_uring. Lợi ích tiết kiệm thời gian thực tế là rất nhỏ (cả đời chắc tiết kiệm được cỡ 5 giây), và đó cũng không phải điểm cốt lõi
    • Trên thực tế, ở các thư mục chứa hàng triệu tệp JSON thì chạy ls/du có 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 đại
  • lsr 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 (.opus v.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êm cat và 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 Rust

    • bat là bản thay thế cat hiện đại (xem bat)
    • Về hỗ trợ màu sắc, có lẽ tốt nhất là triển khai cách chuẩn như LS_COLORS/dircolors. GNU ls cho màu rất đẹp
  • Từ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

    • Trước đây để dễ xây dựng tính portable, người ta thường viết mà không dùng các nhánh macro kiểu #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ước
    • Để dùng io_uring hiệu quả thì cần mô hình bất đồng bộ dựa trên sự kiện. Phần lớn công cụ CLI hiện có được viết theo kiểu trực quan và tuần tự. Nếu async được dùng tự nhiên ở cấp độ ngôn ngữ thì việc port đã dễ hơn, nhưng hiện tại cần refactor khá lớn. io_uring cũng chưa hoàn toàn ổn định, nên có thể cứ chờ công nghệ mới xuất hiện hoặc các công cụ port tự động/AI ra đời
    • io_uring từng có các vấn đề bảo mật lớn ở giai đoạn đầu phổ biến (khoảng 2 năm trước). Giờ nhiều vấn đề đã được giải quyết nhưng nó vẫn ảnh hưởng xấu đến mức độ phổ cập
    • io_uring rất khó về mặt bảo mật
    • Vì io_uring còn là công nghệ rất mới, trong khi coreutils (và các gói đi trước nó) có truyền thống hàng chục năm, nên sẽ còn mất thời gian nữa trước khi đưa io_uring vào. Để kiểu system call “shared ring buffer” trở thành chuẩn thay cho kiểu đồng bộ cũ thì cần thời gian
  • Quan sát bằng strace cho thấy lsd gọi clock_gettime khoả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ện

    • Ngày nay clock_gettime thực ra không phải syscall thật mà được xử lý qua vDSO (xem man 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ông
  • Hơ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ơn

    • Nếu POSIX chuẩn hóa phép toán “readdirplus” của NFS (getdents + stat) thì dự đoán một phần lợi thế riêng của io_uring sẽ bị triệt tiêu
  • Thấy khá thú vị khi có biểu tượng cho phần mở rộng .mjs.cjs, nhưng lại không có cho các phần mở rộng như .c, .h, .sh