- lsr es un nuevo reemplazo de ls(1) desarrollado con ourio, una biblioteca de E/S basada en io_uring
- Frente a
ls y herramientas alternativas existentes (eza, lsd, uutils ls), la ejecución del comando es muchísimo más rápida y usa más de 10 veces menos llamadas al sistema
- Maximiza el rendimiento procesando de forma asíncrona y por lotes con io_uring todas las operaciones principales de E/S, como abrir directorios,
stat y lstat. Cuantos más archivos hay, más rápido resulta
- Usa StackFallbackAllocator de Zig para minimizar las llamadas a
mmap durante la asignación de memoria
- Se compila de forma estática, sin enlazado dinámico, por lo que el ejecutable es incluso más pequeño que el
ls tradicional
Introducción e importancia
- El proyecto lsr es una alternativa al comando
ls tradicional: una herramienta rápida para listar directorios que aprovecha io_uring
- Comparado con
ls, eza, lsd y uutils ls, muestra un rendimiento sobresaliente tanto en velocidad de ejecución como en uso de llamadas al sistema
- Realiza la mayor cantidad posible de E/S directamente con la biblioteca desarrollada por el autor, ourio
- Los benchmarks demuestran que lsr ofrece gran velocidad y buena calidad incluso en entornos con grandes volúmenes de archivos
Resultados de benchmarks
- Se usó
hyperfine para medir el tiempo de ejecución de cada comando en directorios con n archivos regulares
lsr -al registró tiempos de ejecución claramente más cortos que ls y sus alternativas en pruebas con 10 a 10,000 archivos
- Ejemplo: con 10,000 archivos, lsr marcó 22.1ms, superando a
ls (38.0ms), eza (40.2ms), lsd (153.4ms) y uutils ls (89.6ms) para lograr la mejor velocidad
- El conteo de llamadas al sistema se realizó con
strace -c
lsr -al: mantuvo un número de llamadas muy bajo, desde 20 (n=10) hasta un máximo de 848 (n=10,000)
ls llegó a 30,396 llamadas (n=10,000), mientras que lsd alcanzó 100,512; las demás alternativas también se ubicaron entre miles y decenas de miles
- En las mismas condiciones, lsr logra la mayor eficiencia con al menos 10 veces menos syscalls
Estructura de lsr y método de implementación
- El programa funciona en tres etapas: análisis de argumentos, recolección de datos y salida de datos
- Toda la E/S ocurre en la segunda etapa, la de recolección de datos, donde todos los accesos a archivos y consultas de información posibles se manejan con io_uring
- La apertura del directorio objetivo,
stat, lstat y la consulta de información de tiempo, usuario y grupo se realizan sobre io_uring
- Procesa
stat por lotes para reducir drásticamente la cantidad de llamadas al sistema
- Con Zig StackFallbackAllocator, preasigna 1MB de memoria para minimizar llamadas adicionales al sistema como
mmap
Compilación estática y optimización
- Al ser una compilación completamente estática, sin enlazado dinámico con libc, el overhead de ejecución es notablemente menor
- Frente a GNU
ls, el tamaño de la compilación ReleaseSmall de lsr es menor: 138.7KB vs 79.3KB
- Eso sí, lsr no tiene soporte de locale (idioma/región). El
ls normal incurre en overhead para soportar múltiples idiomas
Análisis de llamadas al sistema y problemas de rendimiento
lsd llama a clock_gettime más de 5 veces por archivo; la razón no está clara, aunque se especula con mediciones de tiempo internas u otras causas
- La clasificación (sorting) representa una parte importante del trabajo total (aprox. 30%)
uutils ls es eficiente en syscalls, pero se vuelve lento en el procesamiento del ordenamiento
- Solo con adoptar io_uring ya se confirma la posibilidad de una mejora de rendimiento revolucionaria en entornos de E/S de alta carga, como servidores
Conclusión
- El tiempo de desarrollo tampoco fue largo, y el efecto de optimizar las syscalls superó las expectativas
- lsr es un reemplazo experimental de
ls que logra al mismo tiempo gran velocidad, pocas llamadas al sistema y un tamaño compacto
- Es muy adecuado para entornos con muchos archivos o sistemas donde la E/S de alto rendimiento es importante
- Aunque tiene algunas limitaciones, como la falta de soporte para locale, muestra resultados innovadores tanto en uso real como en benchmarks
1 comentarios
Comentarios en Hacker News
El autor del proyecto se identifica como tal y comenta que se puede ver una introducción sobre
lsrbasado en io_uring aquíls(1)era muy rápido gracias a su diseño simple, pero con la suma de funciones adicionales, VFS, distintos juegos de caracteres, soporte de color y otros pequeños costos, se fue volviendo más lento. Le parece una discusión interesante sobre el costo de abstracción que maneja io_uringbfs -j1)tim(enlace de presentación), probablemente sería mejor que hyperfine. Está hecho en Nim, así que podría ser un reto, pero le parece graciosa la coincidencia de que el nombre sea tan parecidolibevringtodavía está en etapa temprana, así que mantiene una postura abierta a reemplazarlo por ourio si hace falta. Piensa que, si hubiera soporte de bindings de C/C++ para proyectos basados en Zig, sería útil al migrar de C/C++ a ZigDa curiosidad cómo rendirá lsr sobre un servidor NFS, especialmente en condiciones de red deficientes. Es evidente que uno de los problemas del diseño de NFS es usar syscalls POSIX bloqueantes sobre un servicio de red inestable. También sería interesante ver hasta qué punto io_uring puede mitigar eso
ls, etc.) no necesitaban manejar por sí mismas los errores de red. El protocolo NFS original además no guardaba estado, así que si el servidor se reiniciaba, el cliente se recuperaba automáticamente. Le interesa saber si io_uring entrega bien los errores en casos así y cómo se maneja un timeout de NFSctrl+c. En teoría, la opción de montajeintrpermitía interrumpir operaciones contra un servidor remoto en ejecución enviando señales, pero en Linux fue eliminada hace ya mucho tiempo (ahora solo queda la opciónsoft) (referencia 1, referencia 2, soporte en FreeBSD)Resulta interesante que, aunque redujeron 35 veces la cantidad de llamadas syscall, la mejora de velocidad sea de apenas unas 2 veces
Le interesa más como proyecto por la promesa de ganancias de velocidad a largo plazo usando io_uring, o como tutorial de uso, que por una necesidad inmediata. Frente a herramientas existentes como eza, no sintió una motivación práctica clara de por qué hacía falta esto. Si listar diez mil archivos tarda 40 ms frente a 20 ms, cree que en una sola ejecución no notaría ninguna diferencia
lsodusí puede tomar minutos. Muchas veces los comandos básicos de coreutils no aprovechan bien el rendimiento de los SSD modernoslsr está bien, pero eza es mejor en coloreado y soporte de íconos. Tiene configurado
eza --icons=always -1, así que archivos de música como.opusaparecen automáticamente con íconos y colores, mientras que en lsr se ven como archivos normales. Aun así, siente claramente que lsr se parchea fácil y es rapidísimo. También le gustaría vercaty otras utilidades hechas de esta forma, le parece curioso el uso de tangled.sh y atproto, y al estar escrito en zig le resulta más accesible para principiantes que rustbates un reemplazo moderno decat(ir a bat)lsmuestra colores bonitosLe daba curiosidad por qué no todas las herramientas CLI usan io_uring. En su caso, al conectar un nvme por usb 3.2 gen2, las herramientas normales llegan a 740MB/s, mientras que con herramientas basadas en aio o io_uring sube hasta 1005MB/s. Cree que también influye la estrategia de profundidad de cola y la reducción de locks
#ifdef, así que la adopción de tecnologías nuevas específicas de plataforma o versión ha sido lenta. A estas alturas, cree que la ventaja de compatibilidad entre tantas plataformas “posixy” ya no pesa tanto como antesasyncse usara de manera más natural, portar sería más fácil, pero por ahora haría falta una refactorización grande. Además, io_uring todavía no está completamente asentado, así que quizá convenga esperar a la siguiente tecnología nueva o a que aparezcan herramientas de portabilidad automática o IAEn
stracese observa que lsd llama aclock_gettimeunas 5 veces por archivo. No está claro por qué; tal vez sea para calcular cosas como “hace unos minutos/horas/días” para cada marca de tiempo, o quizá sea por algún legado de libreríasclock_gettimeya no es una syscall real, sino que se atiende por vDSO (verman 7 vDSO). Se pregunta si tal vez zig no está aprovechando esa estructuraUn poco fuera de tema, pero le interesa saber por experiencia real o benchmarks cuánto reduce io_uring el overhead de latencia de sockets, en microsegundos, frente a LD_PRELOAD en servidores enterprise bastante grandes con NIC 10G como Mellanox 4 o 5. Le parece que ambos efectos no se acumulan, y si alguien tiene experiencia directa, quisiera escuchar cifras concretas
io_uring no soporta
getdents, así que su ventaja fuerte se nota enbulk stat(por ejemplols -l). Le queda la sensación de que estaría bueno poder asincronizar y superponer también el manejo degetdentsreaddirplusde NFS (getdents+stat), parte de la ventaja específica de io_uring probablemente se compensaríaLe parece curioso que haya íconos para extensiones
.mjsy.cjs, pero no para extensiones como.c,.ho.sh