- lsr เป็นโปรแกรมทดแทน
ls(1) แบบใหม่ที่พัฒนาด้วยไลบรารี IO ชื่อ ourio ซึ่งสร้างบน io_uring
- เมื่อเทียบกับ
ls แบบดั้งเดิมและเครื่องมือทางเลือก (eza, lsd, uutils ls) พบว่า รันคำสั่งได้เร็วมาก และ ใช้ system call น้อยกว่ามากกว่า 10 เท่า
- IO หลักทั้งหมด เช่น การเปิดไดเรกทอรี,
stat, lstat ถูกจัดการแบบ อะซิงโครนัสและแบบแบตช์ด้วย io_uring เพื่อรีดประสิทธิภาพสูงสุด ยิ่งมีไฟล์มากยิ่งเร็ว
- ใช้ StackFallbackAllocator ของ Zig เพื่อลดการเรียก
mmap ระหว่างการจัดสรรหน่วยความจำให้น้อยที่สุด
- คอมไพล์แบบสแตติกโดยไม่ใช้ dynamic linking ทำให้ ขนาดไฟล์ไบนารีเล็กกว่า
ls แบบเดิมอีกด้วย
บทนำและความสำคัญ
- โปรเจกต์ lsr เป็น เครื่องมือแสดงรายการไดเรกทอรีความเร็วสูง ที่ใช้ io_uring ในฐานะ ตัวแทนของคำสั่ง
ls ทั่วไป
- เมื่อเทียบกับ
ls, eza, lsd, uutils ls เดิม ๆ แล้ว ให้ประสิทธิภาพโดดเด่นทั้งในด้าน ความเร็วในการรัน และ ปริมาณการใช้ system call
- ใช้ไลบรารี IO ที่พัฒนาขึ้นเอง (ourio) เพื่อจัดการ IO ให้ได้มากที่สุดด้วยตัวเอง
- ผลเบนช์มาร์กยืนยันว่า lsr ให้ทั้งประสิทธิภาพและคุณภาพที่ดีแม้ในสภาพแวดล้อมที่มีไฟล์จำนวนมาก
ผลการเบนช์มาร์ก
- ใช้
hyperfine วัดเวลาการรันของแต่ละคำสั่งในไดเรกทอรีที่มีไฟล์ทั่วไปจำนวน n ไฟล์
lsr -al ที่ช่วง 10–10,000 ไฟล์ ทำเวลาได้ สั้นกว่ามาก เมื่อเทียบกับ ls และทางเลือกอื่น
- ตัวอย่าง: ที่ 10,000 ไฟล์ lsr ทำได้ 22.1ms เร็วกว่า
ls เดิม (38.0ms), eza (40.2ms), lsd (153.4ms), uutils ls (89.6ms) อย่างชัดเจน
- การนับ system call ใช้
strace -c
lsr -al: ใช้เพียง 20 ครั้งเป็นอย่างน้อย (n=10) และสูงสุดแค่ 848 ครั้ง (n=10,000) ซึ่งต่ำมาก
ls ใช้สูงสุด 30,396 ครั้ง (n=10,000), lsd สูงถึง 100,512 ครั้ง และตัวเลือกอื่นก็อยู่ในระดับหลายพันถึงหลายแสนครั้ง
- ภายใต้เงื่อนไขเดียวกัน lsr ใช้ syscall น้อยกว่าอย่างน้อย 10 เท่า และให้ประสิทธิภาพสูงสุด
โครงสร้างและแนวทางการทำงานของ lsr
- โปรแกรมทำงานเป็น 3 ขั้นตอน ได้แก่ การพาร์สอาร์กิวเมนต์, การรวบรวมข้อมูล, การแสดงผลข้อมูล
- IO ทั้งหมดเกิดขึ้นในขั้นตอนที่สองคือ การรวบรวมข้อมูล โดย พยายามจัดการการเข้าถึงไฟล์และการดึงข้อมูลทั้งหมดผ่าน io_uring ให้มากที่สุดเท่าที่เป็นไปได้
- ทั้งการเปิดไดเรกทอรีเป้าหมาย,
stat, lstat, การอ่านข้อมูลเวลา/ผู้ใช้/กลุ่ม ล้วนทำบนพื้นฐานของ io_uring
- การประมวลผล
stat แบบแบตช์ช่วยลดจำนวน system call ได้อย่างมาก
- ใช้ Zig StackFallbackAllocator เพื่อจองหน่วยความจำล่วงหน้า 1MB และลด system call เพิ่มเติมอย่าง
mmap ให้น้อยที่สุด
การคอมไพล์แบบสแตติกและการปรับแต่งประสิทธิภาพ
- เนื่องจากเป็น การคอมไพล์แบบสแตติกเต็มรูปแบบโดยไม่ลิงก์กับ libc แบบไดนามิก จึงมีโอเวอร์เฮดขณะรันต่ำมาก
- เมื่อเทียบกับ GNU
ls ขนาดบิลด์แบบ ReleaseSmall ของ lsr เล็กกว่า คือ 138.7KB เทียบกับ 79.3KB
- อย่างไรก็ตาม lsr ยังไม่รองรับ locale (ภาษา/ภูมิภาค) ขณะที่
ls ทั่วไปมีโอเวอร์เฮดจากการรองรับหลายภาษา
การวิเคราะห์ system call และประเด็นด้านประสิทธิภาพ
lsd เรียก clock_gettime มากกว่า 5 ครั้งต่อไฟล์ โดยยังไม่ทราบสาเหตุแน่ชัด (คาดว่าอาจใช้วัดเวลาในภายในโปรแกรม)
- การเรียงลำดับ (sorting) กินเวลาส่วนสำคัญของงานทั้งหมด (ประมาณ 30%)
uutils ls มีประสิทธิภาพด้าน system call ที่ดี แต่ช้าลงในขั้นตอนการเรียงลำดับ
- เพียงแค่นำ io_uring มาใช้ ก็แสดงให้เห็นถึงศักยภาพของ การเพิ่มประสิทธิภาพแบบก้าวกระโดด ในสภาพแวดล้อมที่มี IO หนัก เช่น เซิร์ฟเวอร์
บทสรุป
- ใช้เวลาในการพัฒนาไม่นาน แต่ผลของการปรับแต่ง syscall ดีเกินคาด
- lsr เป็นตัวแทน
ls เชิงทดลองที่ทำได้พร้อมกันทั้ง ความเร็วสูง, system call ต่ำ, และขนาดกะทัดรัด
- เหมาะมากกับสภาพแวดล้อมที่มีไฟล์จำนวนมาก หรือระบบที่ความสำคัญอยู่ที่ประสิทธิภาพ IO สูง
- แม้จะยังมีข้อจำกัดบางอย่าง เช่น ไม่รองรับ locale แต่ทั้งในการใช้งานจริงและผลเบนช์มาร์กก็แสดงผลลัพธ์ที่น่าทึ่ง
1 ความคิดเห็น
ความคิดเห็นบน Hacker News
ผู้เขียนโครงการมาแสดงตัว และบอกว่าสามารถอ่านบทความแนะนำ
lsrที่สร้างบน io_uring ได้ที่นี่ls(1)ออกแบบมาเรียบง่ายและเร็วมาก แต่เมื่อมีการเพิ่มฟีเจอร์ต่าง ๆ, virtual file system (VFS), ชุดอักขระหลากหลาย, การรองรับสี ฯลฯ ต้นทุนเล็ก ๆ ก็สะสมจนช้าลง มองว่านี่เป็นประเด็นถกเถียงที่น่าสนใจเรื่องต้นทุนของ abstraction ที่ io_uring จัดการอยู่bfs -j1) ด้วยหรือไม่tim(ลิงก์แนะนำ) วัดเวลา น่าจะดีกว่า hyperfine แม้จะเขียนด้วย Nim เลยอาจท้าทายอยู่บ้าง แต่ชื่อคล้ายกันแบบนี้ก็ตลกดีที่เป็นเรื่องบังเอิญlibevringที่ทำเองก็ยังอยู่ช่วงต้น จึงเปิดกว้างว่าจะเปลี่ยนไปใช้ ourio แทนถ้าจำเป็น มองว่าถ้าโปรเจ็กต์ฝั่ง Zig รองรับ C/C++ bindings ก็จะมีประโยชน์มากเวลาย้ายจาก C/C++ ไป Zigสงสัยว่า lsr จะมีประสิทธิภาพแค่ไหนบนเซิร์ฟเวอร์ NFS โดยเฉพาะในสภาพเครือข่ายที่ไม่ดี การใช้ blocking POSIX syscall กับบริการเครือข่ายที่ไม่เสถียรเป็นจุดอ่อนที่ชัดเจนของการออกแบบ NFS อยู่แล้ว และก็น่าสังเกตว่า io_uring จะช่วยบรรเทาปัญหานี้ได้แค่ไหน
intrเคยรองรับการส่งสัญญาณไปยัง operation ที่กำลังค้างอยู่บนเซิร์ฟเวอร์ระยะไกลเพื่อให้ยกเลิกได้ แต่บน Linux ถูกถอดออกไปนานแล้ว (ตอนนี้มีแค่softoption) (อ้างอิง 1, อ้างอิง 2 (FreeBSD รองรับ))น่าสนใจที่แม้จะลดจำนวนการเรียก syscall ลงได้ 35 เท่า แต่ความเร็วกลับดีขึ้นแค่ราว 2 เท่า
โปรเจ็กต์นี้น่าสนใจกว่าในฐานะตัวอย่างการใช้ io_uring เพื่อดูว่าระยะยาวจะได้ประโยชน์ด้านความเร็วจริงแค่ไหน หรือในฐานะตัวอย่างสอนใช้งาน มากกว่าจะรู้สึกว่าจำเป็นต้องมีเมื่อเทียบกับเครื่องมือเดิมอย่าง eza ถ้าลิสต์ไฟล์หนึ่งหมื่นไฟล์ใช้เวลา 40ms เทียบกับ 20ms ก็คงไม่รู้สึกถึงความต่างเลยในการใช้งานครั้งเดียว
lsr ก็ดี แต่เรื่องการลงสีและรองรับไอคอน eza ยังดีกว่า ตัวผู้แสดงความเห็นตั้งค่าเป็น
eza --icons=always -1เลยทำให้ไฟล์เพลง (.opus เป็นต้น) แสดงเป็นไอคอนและสีอัตโนมัติ ขณะที่ใน lsr มันดูเป็นแค่ไฟล์ธรรมดา ถึงอย่างนั้นก็รู้สึกชัดว่า lsr แพตช์ง่ายและเร็วมาก อีกทั้งยังหวังว่าจะมีการทำ cat และยูทิลิตีอื่น ๆ ในแนวเดียวกันด้วย รวมถึงรู้สึกว่าน่าสนใจที่ใช้ tangled.sh และ atproto และเพราะเขียนด้วย Zig เลยรู้สึกว่าเข้าถึงง่ายกว่า Rust สำหรับมือใหม่batคือโปรแกรมแทนcatแบบสมัยใหม่ (ไปที่ bat)เคยสงสัยว่าทำไมเครื่องมือ CLI ทุกตัวไม่ใช้ io_uring ทั้งหมด ตัวผู้แสดงความเห็นบอกว่าตอนต่อ nvme ผ่าน usb 3.2 gen2 เครื่องมือทั่วไปได้ความเร็ว 740MB/s แต่เครื่องมือที่ใช้ aio หรือ io_uring ไปได้ถึง 1005MB/s และมองว่าน่าจะมีผลจากกลยุทธ์ความยาวคิวหรือการลด lock ด้วย
มีคนสังเกตผ่าน strace ว่า lsd เรียก
clock_gettimeราว 5 ครั้งต่อหนึ่งไฟล์ ไม่แน่ใจสาเหตุที่แท้จริง อาจเป็นการคำนวณว่าแต่ละ timestamp คือ “กี่นาที/ชั่วโมง/วันก่อน” หรืออาจเป็นมรดกจากไลบรารีเก่าก็ได้clock_gettimeไม่ใช่ syscall จริงแล้ว แต่จัดการผ่าน vDSO (man 7 vDSO) เลยมีคนสงสัยว่า Zig อาจไม่ได้ใช้โครงสร้างนี้หรือเปล่าอาจจะนอกประเด็นไปนิด แต่มีคนอยากรู้จากประสบการณ์จริงหรือค่า benchmark ว่าในสภาพแวดล้อม 10G NIC บนเซิร์ฟเวอร์ระดับ enterprise สูงอย่าง Mellanox 4 หรือ 5 นั้น io_uring ช่วยลด socket latency overhead ได้ระดับกี่ไมโครวินาทีเมื่อเทียบกับ LD_PRELOAD และดูเหมือนผลของทั้งสองอย่างจะไม่ทับซ้อนกัน จึงอยากได้ตัวเลขจากคนที่เคยลองจริง
io_uring ยังไม่รองรับ getdents ดังนั้นประโยชน์หลักจึงออกมาเด่นกับ bulk stat (เช่น
ls -l) เลยรู้สึกเสียดายว่าถ้าทำ getdents ให้ asynchronous และประมวลผลซ้อนกันได้ก็น่าจะดีreaddirplus(getdents + stat) ขึ้นมาได้จริง ข้อได้เปรียบบางส่วนของ io_uring ก็น่าจะถูกหักล้างไปรู้สึกขำดีที่มีไอคอนให้กับนามสกุล
.mjsและ.cjsแต่กลับไม่มีสำหรับนามสกุลอย่าง.c,.h,.sh