17°
Portada del artículo: Rust Coreutils 0.12 contra GNU 9.12: casi todo funciona igual, arrancar cuesta casi el triple y mv entre discos pierde las fechas
RustCoreutilsGNUBenchmarksLinux

Rust Coreutils 0.12 contra GNU 9.12: casi todo funciona igual, arrancar cuesta casi el triple y mv entre discos pierde las fechas

Medí Rust Coreutils (uutils) 0.12 contra GNU coreutils 9.12 en contenedores con límites: la suite de pruebas de GNU, 157 comandos con la salida comparada y el arranque de proceso. uutils es más lento en 91 de 157 celdas, tarda 2,8 veces más en lanzar 5.000 procesos, y mv entre sistemas de archivos pierde las fechas.

Efrain Garay 20 de septiembre de 2026

Reproduciendo el resumen

El 17 de septiembre salió Rust Coreutils 0.12, y Phoronix contó que Ubuntu 26.10 completa con esa versión su transición a las utilidades en Rust. Son cp, mv, rm, ls, sort, cat y unas cien más, las que corren cada vez que arrancas un script. Las notas de la release traen dos frases que quería verificar. Una es la cifra de compatibilidad: 653 pruebas de GNU pasan, 21 fallan, 0 dan error. La otra es nueva: una utilidad significativamente más lenta que GNU ahora cuenta como un bug.

Medí las dos cosas, con los mismos binarios en los mismos límites de recursos, y comparé también la salida de cada comando.

La cifra de 653 no se reprodujo en mi contenedor, pero es honesta: es un agregado de seis corridas de su CI. En rendimiento uutils es más lento en 91 de 157 celdas y arrancar un proceso cuesta casi el triple. Y en los comandos que Ubuntu ya usa, fallan solo tres pruebas de GNU, pero mv entre sistemas de archivos pierde la fecha de modificación.

En 72 segundos y narrado: Rust Coreutils (uutils) 0.12 contra GNU coreutils 9.12. El 653 de sus notas es el mejor resultado por prueba de seis corridas de su CI y no se reprodujo en mi contenedor; en cp, mv, rm, install, ls, sort y du fallan solo 3 pruebas. En rendimiento, uutils es más lento en 91 de 157 celdas (arrancar un proceso cuesta casi el triple), pero gana en 45. Y mv entre discos pierde la fecha de modificación. Sin sonido por defecto: actívalo en los controles.Verlo en el visualizador de reels →

Qué es uutils

Es una reescritura de las coreutils de GNU en Rust, con licencia MIT, que apunta a ser un reemplazo directo. El proyecto lleva años; con la 0.12 se centró en corregir lo que pidieron los mantenedores de Ubuntu: una pérdida de datos en install -D paralelo, colisiones de nombres en cp -R, chmod -R a través de enlaces simbólicos y du en arquitecturas de 32 bits. Además dejó de fallar rm en árboles muy profundos.

GNU coreutils 9.12 salió el 14 de septiembre, tres días antes. Es contra esa versión que medí el rendimiento. Para las pruebas usé las de la 9.11, porque es la que fijan los scripts de uutils.

En la imagen ubuntu:26.04 que revisé, /usr/bin/mv apunta a gnumv, que es GNU 9.7, y rust-coreutils 0.8.0 viene como paquete aparte. No sé qué trae un escritorio con 26.10 todavía instalado, porque esa versión sale en octubre.

El banco: dos pods en núcleos separados, los mismos binarios, dos preguntasUn pod mide 157 comandos y compara su salida. El otro corre la suite de pruebas de GNU contra uutils y, de control, contra el GNU de verdad.
El banco: dos pods en núcleos separados, los mismos binarios, dos preguntasdatos deterministastexto de 97,6 MBarchivo de 1 GiBárboles de 50.000 archivospod de tiempos · núcleos 0-1 · 2 CPU · 4 GB · sin redGNU coreutils 9.12-O2, glibc dinámicauutils 0.12.0release, lto fat, glibchyperfine, intercalado157 celdas · salida comparadapod de pruebas · núcleos 4-7 · 4 CPU · 8 GBuutils 0.12.0release-small (como su CI)GNU 9.11 realcontrol, mismas pruebas692 / 735 GNU testsresultadoscocientespasa/falla/omiteEl banco: dos pods en núcleos separados, los mismos binarios, dos preguntasdatos deterministastexto de 97,6 MB · archivo de 1 GiBpod de tiempos · 2 CPU · 4 GBGNU 9.12glibcuutils 0.12.0releasehyperfine157 cellspod de pruebas · 4 CPU · 8 GBuutils 0.12.0release-smallGNU 9.11control692 / 735 GNU testsresultadoscocientes · pasa/falla/omite

Mismos datos, mismos comandos, mismos límites. Lo que cambia es la implementación. Los pods no comparten núcleo físico, pero sí caché y ancho de banda de memoria.

El banco

Todo corrió en fedora, en un Ryzen 7 7800X3D. Los tiempos se midieron con el binario release en un contenedor de dos núcleos físicos (0 y 1) y 4 GiB, sin red y con los datos generados dentro, con semillas fijas, la salida estándar a /dev/null. La suite de GNU corrió con el binario release-small en otro contenedor de 4 núcleos (4 a 7) y 8 GiB, en el mismo host y al mismo tiempo: son dos binarios y dos límites distintos, no el mismo banco repetido dos veces. No comparten núcleo físico; sí comparten caché y ancho de banda de memoria, y eso es una condición de la medida.

Los binarios: GNU coreutils 9.12 compilado por mí desde el tarball oficial, y uutils 0.12.0 compilado con cargo build --release --features unix (el perfil de upstream: lto = "fat", un solo codegen unit, panic = "abort"). Las pruebas de GNU se corrieron con el perfil release-small, que es el que usa su CI. Yo no medí el binario que instala Ubuntu, que puede estar compilado con otras opciones.

Para medir usé hyperfine 1.20.0, con las dos implementaciones intercaladas en la misma sesión, la salida a /dev/null y entre 10 y 300 repeticiones por implementación, según la celda; los cocientes pueden cambiar al escribir a un archivo o a un pipe. Cada celda compara además la salida: el hash de lo que escribe el comando y del árbol de archivos resultante.

La suite de GNU: el 653 es un agregado

Las notas de la release dicen 653 pruebas que pasan, 21 que fallan, 0 con error y 18 omitidas, de 692. El repositorio donde uutils publica sus resultados lo confirma: el run del 16 de septiembre da exactamente esas cifras. Pero es el mejor resultado por prueba, combinando seis corridas de su CI (usuario, root, terminal, una VM con SELinux y otra con SMACK), y el run para el commit exacto de la etiqueta da 653, 23 y 16. Entre el 16 y el 19 de septiembre hay 40 registros con las mismas 692 pruebas, con entre 646 y 655 aprobadas; otros siete registros de esos días usan un tamaño de suite distinto. Como los commits cambian de una corrida a otra, esa dispersión no aísla ni acota el ruido de medición: es solo el rango que observé.

Lo corrí yo, sin root, con los cuatro núcleos del contenedor: 569 o 570 pasan, 27 o 28 fallan y 95 se omiten, en cuatro corridas casi idénticas. Combinando usuario, root y terminal como hace su CI, 590 pasan, 29 fallan y 73 se omiten. Como control, corrí las mismas pruebas con el GNU 9.11 de verdad: 636 pasan, 4 fallan y omite otras 95 pruebas, 90 de ellas las mismas que las mías.

Las pruebas de GNU, cinco vistas de la misma suiteLo que publica uutils, el run de su CI para el commit de la etiqueta, y tres corridas mías en un contenedor.
  1. Notas de la release (agregado de 6 corridas)
    653 pasan · 21 fallan · 18 omitidas de 692
    Es el mismo número que publica su repositorio de seguimiento para el run del 16 de septiembre.
  2. Su CI, run del commit exacto de la etiqueta
    653 pasan · 23 fallan · 16 omitidas de 692
  3. Mi corrida agregada (usuario, root y terminal)
    590 pasan · 29 fallan · 73 omitidas de 692
    Sin SELinux, SMACK, mount ni mkfs.
  4. Mi corrida estricta, sin root
    570 pasan · 27 fallan · 95 omitidas de 692
    Cuatro corridas: 569 o 570 pasan.
  5. Control: el GNU 9.11 de verdad, corrida estricta
    636 pasan · 4 fallan · 95 omitidas de 735
    De 735 pruebas: mi control usa el árbol de GNU sin las ediciones de uutils.

Casi toda la distancia entre las notas y mi corrida son pruebas omitidas por las restricciones de mi entorno: 21 pedían mount, mkfs o chattr, 17 un locale, 9 SELinux. El GNU de verdad también las omite.

Los 95 omitidos son el número que importa. El control con GNU real también los omite: sin SELinux, sin mount, sin mkfs y sin en_US.UTF-8 en la imagen, hay pruebas que no pueden correr. Lo que separa mi 590 de su 653 es sobre todo eso, y lo que separa mi 570 de mi 590 es la fase con root. Por eso digo que la cifra no se reproduce, no que sea falsa.

De sus 21 fallos publicados, 17 fallan también en mi corrida. Aparecen 12 pruebas que fallan aquí y su CI no marca, y las separé una por una:

  • Un fallo por agotamiento de memoria bajo el límite del contenedor. od/big-w: uutils od reserva unos 14,8 GB de memoria virtual donde GNU procesa en flujo, y el journalctl del kernel registra una veintena de muertes por memoria en el contenedor de 8 GiB. En una corrida suelta, fuera de la matriz, sí termina, pero en 154 a 170 s contra 11 s de GNU.
  • Cuatro del entorno. id/context, id/no-context, runcon/runcon-compute y runcon/runcon-no-reorder necesitan SELinux, que no estaba disponible en este entorno de pruebas. Su CI las pasa en una VM aparte.
  • Una inestable y una que cambia entre commits. tail/tail-n0f falló en dos corridas y pasó en otras dos. tail/symlink pasa en el run de las notas y falla en el de la etiqueta; eso solo no basta para llamarla inestable.
  • Una que depende de la carga. sort/sort-compress-proc, que veo más abajo.
  • Cuatro que parecen diferencias reales. cut/bounded-memory: uutils cut aborta al pedir 8 MiB con el mismo límite de memoria con el que GNU pasa. pr/bounded-memory: pr se queda sin memoria incluso con 1 GB de holgura. shuf/shuf: si getrandom falla con ENOSYS, shuf entra en pánico con código 134 y GNU sale con 1. touch/now-owned-by-other, que también veo abajo. En el run de las notas, cut y pr cuentan como omitidas; y shuf probablemente pasa en su CI porque el runner no trae strace y esa comprobación nunca corre (no leí ese log, es una deducción).

En resumen: mis 590, 29 y 73 contra sus 653, 21 y 18 se explican por 55 omitidas del entorno y un artefacto de memoria. Quedan cuatro fallos reales que su CI no lista y uno que depende de la carga.

En lo que Ubuntu ya usa fallan tres pruebas

Miré los fallos de las utilidades que hoy trae Ubuntu: cp, mv, rm, install, ls, sort, du, chmod, ln, mkdir, touch y stat. Solo fallan tres pruebas.

  • sort/sort-compress-proc. Si el programa de compresión termina antes de leer toda la entrada, sort de uutils puede morir por SIGPIPE o salir con 0, y GNU sale con 2 y close failed: Broken pipe. Depende de la carga: pasó sola en una máquina ociosa y falló con la CPU ocupada.
  • touch/now-owned-by-other, solo en la fase de root. touch -d now ARCHIVO da “Permission denied” si puedes escribir pero no eres el dueño, porque uutils pasa una fecha explícita en vez de UTIME_NOW.
  • rm/rm-readdir-fail. Cuando readdir() falla a mitad, GNU escribe traversal failed y uutils cannot remove 'dir': Directory not empty. Los dos salen con 1; cambia el diagnóstico. Está en la lista de pruebas que uutils declara imposibles de arreglar.

Esto no es un visto bueno. En esas mismas utilidades quedaron omitidas siete pruebas de cp, dos de mv, cuatro de rm, dos de install, tres de ls, tres de sort, dos de du y cinco de mkdir, por el entorno. No son evidencia a favor ni en contra.

Rendimiento: 157 celdas

La matriz tiene 157 celdas en cinco grupos: arranque de proceso, hashes, texto, árboles de archivos y disco. Cada una compara la mediana de uutils contra la de GNU, con el rango entre la repetición más rápida y la más lenta. Con ese criterio estricto, uutils resultó más lento en 91 celdas, sin diferencia medible en 21 y más rápido en 45. En 56 celdas tardó más de 1,5 veces lo que GNU (51 con la misma salida), y en 17 tardó menos de dos tercios.

157 comandos, un punto por cada unoCociente entre el tiempo de uutils 0.12.0 y el de GNU 9.12, escala logarítmica. A la derecha del 1, uutils tarda más.
  • uutils más lento 91
  • sin diferencia medible 21
  • uutils más rápido 45
  • salida distinta
Arranque de proceso 15
Hashes 10
Texto 82
Árboles de archivos 34
Disco 16
0.1×0.25×0.5×1×2×4×10×32×
← uutils tarda menosuutils tarda más →

Pasa el ratón sobre un punto para ver su celda. Las líneas discontinuas marcan 1,5 veces más lento y 1,5 veces más rápido. Los aros huecos son celdas donde la salida de uutils difiere de la de GNU: su tiempo no es comparable.

Por grupo, el promedio geométrico del cociente fue 2,73 en arranque, 1,66 en árboles, 1,27 en disco, 1,10 en texto y 1,05 en hashes.

Arrancar un proceso

Un solo true tardó 0,29 ms con GNU y 0,94 ms con uutils, unas 3,2 veces. Suena a nada, salvo que un script lanza miles. Con 5.000 lanzamientos desde un bucle de shell, true tardó 1,78 s contra 5,03 s, expr 2,60 s contra 5,51 s y basename 2,09 s contra 5,52 s.

Cuánto tarda cada implementaciónMediana de entre 10 y 300 repeticiones según la celda, dos núcleos, GNU coreutils 9.12 contra uutils 0.12.0.
GNU · true1,776 s
uutils · true5,032 s
GNU · expr2,597 s
uutils · expr5,511 s
GNU · basename2,094 s
uutils · basename5,518 s

Tiempo real de un bucle de shell que lanza el comando 5.000 veces.

GNU · cp -r0,739 s
uutils · cp -r1,777 s
GNU · cp -a1,068 s
uutils · cp -a3,087 s
GNU · ls -1, UTF-80,0536 s
uutils · ls -1, UTF-80,196 s

Tiempo real, 50.000 archivos de 4 KiB en tmpfs (cp) y un directorio de 100.000 entradas (ls).

GNU · sort0,482 s
uutils · sort2,301 s
GNU · sort -r0,479 s
uutils · sort -r2,301 s
GNU · paste0,0322 s
uutils · paste0,407 s

Tiempo real, 1 millón de líneas, locale en_US.UTF-8.

GNU · wc -m0,147 s
uutils · wc -m0,0103 s
GNU · cut -c1-200,146 s
uutils · cut -c1-200,0323 s
GNU · sort -g -k31,17 s
uutils · sort -g -k30,411 s
GNU · b2sum, 1 GiB0,95 s
uutils · b2sum, 1 GiB0,79 s

Tiempo real. wc -m y cut -c en UTF-8, sort -g -k3 y b2sum en locale C.

Cada carril se llena en el tiempo que tardó esa implementación con la mediana de sus repeticiones. Cambia de escenario con los botones.

¿Por qué? Lo que mido de cerca es esto. El binario de uutils pesa 15 MB porque es una sola pieza para las 106 utilidades, y a un true le cuesta 80 llamadas al sistema contra 29 de GNU. Alrededor del 65 % del tiempo del cargador dinámico se va en aplicar unas 26.300 relocalizaciones relativas. La cifra exacta de ciclos varía cerca de 10 % entre corridas; lo estable es ese orden de magnitud.

Donde pierde por mucho

  • paste de dos archivos de un millón de líneas: 12,6 veces más lento. uutils hace 1.000.000 de write y GNU 4.114. Una comprobación posterior, con la salida a un pipe en lugar de /dev/null, dio unas 19,7 veces; queda como una observación, no como un límite inferior demostrado.
  • sort en en_US.UTF-8: 0,48 s con GNU y 2,30 s con uutils, 4,8 veces. En el locale C no hay pérdida: 0,21 s contra 0,19 s. GNU usa unos dos hilos (0,82 s de CPU para 0,48 s de pared) y uutils en UTF-8 es secuencial, así que el 4,8 vale para mi pod de 2 CPU; por segundo de CPU, unas 3 veces.
  • cp -r y cp -a de un árbol de 50.000 archivos: 2,4 y 2,9 veces más lentos en tmpfs. (Sobre ext4 también salen más lentos, pero esas celdas tienen mucho ruido y no las cito.)
  • mv -t de 5.000 archivos: 6 veces.
  • factor sobre 100.000 números: 3,8 veces, con 100.000 llamadas write contra 1.064.
  • fmt -w 72: el pico de memoria, en una sola corrida, fue de unos 912 MiB con uutils contra 1,6 MiB con GNU.

Donde gana

wc -m en UTF-8 es 14 veces más rápido, head -c 4,7 veces, cut -c 4,5 veces, sort -g -k3 2,9 veces en locale C, y b2sum de 1 GiB 1,2 veces. Y en hashes como sha256sum de un archivo de 1 GiB no hay diferencia medible.

Cuando la salida es distinta

Veinte de las 157 celdas dieron una salida distinta a la de GNU, y sus tiempos no cuentan. Comprobé cada diferencia con un comando mínimo.

ComandoGNU 9.12uutils 0.12.0
mv de tmpfs a ext4conserva la fecha de modificaciónpone la del momento del movimiento
wc -m con LC_ALL=Ccuenta bytescuenta caracteres UTF-8
numfmt --to=si --format=%.2f con 143070000143,08M143,07M
timeout -s KILL sobre un proceso colgadosale con 137sale con 124
mkdir -m u=rwx,g+screa con modo 2777crea con modo 777
base64 -d con entrada inválidaescribe lo decodificado y fallafalla sin escribir nada
date -d '2021-03-04 next friday'4 de marzo5 de marzo

La que más pesa es mv. Al mover entre sistemas de archivos distintos, uutils 0.12.0 no conserva la fecha de modificación: en mi prueba, tras mover un árbol de 50.502 entradas, 50.501 quedaron con la fecha del momento del movimiento, y lo mismo con un archivo de 1 GiB. Es un issue abierto en uutils desde abril. En una comprobación a mano fuera de la matriz, tampoco la conserva la 0.8.0 que trae la imagen de Ubuntu 26.04, aunque ahí mv sigue siendo el de GNU. Para un rsync o una copia de seguridad que dependa de las fechas, es un cambio de comportamiento que no avisa.

Seis de las 20 celdas con salida distinta son de ls: ls -l mostró un + donde GNU pone . (y ? con -Z) en archivos etiquetados solo con SELinux. Eso viene de que mi compilación no tiene la opción de SELinux, no de uutils en general; están en el recuento de 20, pero no las trato como una diferencia de comportamiento del proyecto.

Lo que no puedo afirmar

  • Cómo rinde el binario que instala Ubuntu. Medí el release de upstream. Si Ubuntu compila con otras opciones, el arranque y los tamaños pueden cambiar.
  • Que mis cifras valgan en otro procesador. El 7800X3D tiene 96 MiB de caché L3 y mi archivo de texto de 97,6 MB casi cabe entero. Las celdas de texto no representan un procesador común.
  • Que la frecuencia fuera fija. El governor estaba en powersave: medí entre 4,39 y 4,42 GHz al empezar y entre 3,79 y 4,02 al terminar.
  • La causa de casi todas las celdas lentas. Solo tengo evidencia medida en paste y factor (las llamadas write por línea) y en el arranque. Para el resto sé cuánto, no por qué.
  • El desempate entre las 12 pruebas que fallan aquí y no en su CI. No corrí las fases con SELinux ni con SMACK.
  • Nada sobre lecturas en frío ni otros sistemas de archivos. No tuve root para vaciar cachés.

Mi opinión

Esperaba que uutils pasara las pruebas y perdiera en rendimiento, y las dos cosas salieron a medias. El 653 es un número de CI que no puedo reproducir sin una VM con SELinux, pero en las utilidades que importan las pruebas fallan poco y por razones raras, dentro de lo que pude probar sin SELinux ni SMACK. Y en rendimiento no es una pérdida pareja: en texto queda casi empatado, gana varios casos por mucho y pierde por mucho en cerca de un tercio de las celdas, casi siempre en los mismos sitios.

Lo que no esperaba es mv: una diferencia de comportamiento silenciosa, con issue abierto desde abril, en una herramienta que Ubuntu está por poner por defecto. El proyecto dice que ser significativamente más lento es un bug. Con estos números tiene una lista concreta para empezar: el arranque, paste, sort en UTF-8 y cp.

Cuándo lo usaría

  • Sí: en un servidor o un contenedor donde los comandos se usan poco y no se lanzan en bucles largos, o en un escritorio que ya lo trae por defecto.
  • Sí, con cuidado: si tus scripts lanzan miles de procesos o mueven árboles entre discos. Mide antes con tus propios comandos.
  • Todavía no: donde las fechas de modificación se preserven a través de mv entre discos, o donde ordenes texto en UTF-8 a gran escala, hasta que se corrija el mv y se cierre el 4,8 de sort.

Fuentes

Comentarios

Todavía no hay comentarios. El primero es tuyo.

Se revisa antes de publicarse. El correo no se guarda ni aparece en ninguna parte.