2 puntos por GN⁺ 2025-07-19 | 1 comentarios | Compartir por WhatsApp
  • 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

 
GN⁺ 2025-07-19
Comentarios en Hacker News
  • El autor del proyecto se identifica como tal y comenta que se puede ver una introducción sobre lsr basado en io_uring aquí

    • Comparte su experiencia trabajando en proyectos de I18N en Sun. Señala que, para soportar múltiples entornos (localización, utf8, etc.), hay que añadir mucha lógica al programa, por lo que el costo de producir resultados y la velocidad terminan yendo en direcciones opuestas. Originalmente, UNIX 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_uring
    • El proyecto bfs también usa io_uring (enlace al código fuente). Le da curiosidad comparar el rendimiento entre lsr y bfs -ls. Actualmente bfs usa io_uring solo en multihilo, pero cree que valdría la pena pensar si también aprovecharlo en single thread (bfs -j1)
    • Si se mide el tiempo con 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 parecido
    • Tiene en mente portar un proyecto en C++ a Zig. Su propio libevring todaví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 Zig
    • Como ese texto introductorio explica mejor el contexto, planea usarlo como enlace principal y añadir arriba el hilo del repositorio
  • Da 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

    • Los diseñadores de NFS implementaron un sistema distribuido que se comporta de manera muy consistente, casi como si fuera un disco duro. Una ventaja era que las herramientas existentes (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 NFS
    • En casa usa un $HOME por NFS desde varias PCs, y mientras la red sea buena y se eviten casos complicados como escritura paralela, la usabilidad promedio de NFS le parece bastante satisfactoria. Aun así, cuando tuvo un cable de red inestable, sufrió bastante por los cortes
    • Es una molestia bien conocida que en apps leyendo una carpeta NFS a veces no funcione ctrl+c. En teoría, la opción de montaje intr permití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ón soft) (referencia 1, referencia 2, soporte en FreeBSD)
    • Samba tiene un problema parecido
  • Resulta interesante que, aunque redujeron 35 veces la cantidad de llamadas syscall, la mejora de velocidad sea de apenas unas 2 veces

    • La mayoría de las syscalls se hacen a través de VDSO, así que no tienen un costo tan alto
    • En benchmarks sobre io_uring que leyó hace tiempo, a veces las syscalls basadas en io_uring incluso salían más pesadas que las tradicionales. Aun así, la mejora percibida le sigue pareciendo bastante grande. No recuerda la fuente exacta, pero se le quedó grabado
  • 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

    • Es un proyecto experimental hecho por diversión para aprender a usar io_uring. El ahorro real de tiempo es mínimo (del orden de ahorrar 5 segundos en toda una vida), y ese nunca fue el punto principal
    • En directorios con millones de archivos JSON, ejecutar ls o du sí puede tomar minutos. Muchas veces los comandos básicos de coreutils no aprovechan bien el rendimiento de los SSD modernos
  • lsr 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 .opus aparecen 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 ver cat y 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 rust

    • bat es un reemplazo moderno de cat (ir a bat)
    • Para el soporte de color, probablemente lo mejor sería implementar el enfoque estándar tipo LS_COLORS/dircolors. GNU ls muestra colores bonitos
  • Le 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

    • Tradicionalmente se escribía software portable sin meter bifurcaciones con macros tipo #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 antes
    • Para usar io_uring de forma eficiente hace falta un modelo asíncrono y orientado a eventos. La mayoría de las herramientas CLI existentes están escritas de forma secuencial e intuitiva. Si a nivel de lenguaje async se 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 IA
    • io_uring tuvo problemas importantes de seguridad cuando recién se adoptó (hace unos 2 años). Aunque muchos ya se resolvieron, eso sí afectó negativamente su difusión
    • io_uring es muy complicado desde el punto de vista de seguridad
    • io_uring sigue siendo una tecnología muy nueva, mientras que coreutils (y sus paquetes previos) arrastran décadas de tradición, así que su incorporación tomará más tiempo. Hará falta tiempo para que las syscalls con enfoque de “ring buffer compartido” se vuelvan estándar en lugar del modelo síncrono tradicional
  • En strace se observa que lsd llama a clock_gettime unas 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ías

    • Hoy en día clock_gettime ya no es una syscall real, sino que se atiende por vDSO (ver man 7 vDSO). Se pregunta si tal vez zig no está aprovechando esa estructura
  • Un 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 en bulk stat (por ejemplo ls -l). Le queda la sensación de que estaría bueno poder asincronizar y superponer también el manejo de getdents

    • Si POSIX estandarizara la operación readdirplus de NFS (getdents + stat), parte de la ventaja específica de io_uring probablemente se compensaría
  • Le parece curioso que haya íconos para extensiones .mjs y .cjs, pero no para extensiones como .c, .h o .sh