
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.
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.
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.
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.
- Notas de la release (agregado de 6 corridas)653 pasan · 21 fallan · 18 omitidas de 692Es el mismo número que publica su repositorio de seguimiento para el run del 16 de septiembre.
- Su CI, run del commit exacto de la etiqueta653 pasan · 23 fallan · 16 omitidas de 692
- Mi corrida agregada (usuario, root y terminal)590 pasan · 29 fallan · 73 omitidas de 692Sin SELinux, SMACK, mount ni mkfs.
- Mi corrida estricta, sin root570 pasan · 27 fallan · 95 omitidas de 692Cuatro corridas: 569 o 570 pasan.
- Control: el GNU 9.11 de verdad, corrida estricta636 pasan · 4 fallan · 95 omitidas de 735De 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: uutilsodreserva unos 14,8 GB de memoria virtual donde GNU procesa en flujo, y eljournalctldel 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-computeyruncon/runcon-no-reordernecesitan 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-n0ffalló en dos corridas y pasó en otras dos.tail/symlinkpasa 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: uutilscutaborta al pedir 8 MiB con el mismo límite de memoria con el que GNU pasa.pr/bounded-memory:prse queda sin memoria incluso con 1 GB de holgura.shuf/shuf: sigetrandomfalla con ENOSYS,shufentra 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,cutyprcuentan como omitidas; yshufprobablemente pasa en su CI porque el runner no traestracey 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,sortde uutils puede morir por SIGPIPE o salir con 0, y GNU sale con 2 yclose 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 ARCHIVOda “Permission denied” si puedes escribir pero no eres el dueño, porque uutils pasa una fecha explícita en vez deUTIME_NOW.rm/rm-readdir-fail. Cuandoreaddir()falla a mitad, GNU escribetraversal failedy uutilscannot 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.
- uutils más lento 91
- sin diferencia medible 21
- uutils más rápido 45
- salida distinta
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.
Tiempo real de un bucle de shell que lanza el comando 5.000 veces.
Tiempo real, 50.000 archivos de 4 KiB en tmpfs (cp) y un directorio de 100.000 entradas (ls).
Tiempo real, 1 millón de líneas, locale en_US.UTF-8.
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
pastede dos archivos de un millón de líneas: 12,6 veces más lento. uutils hace 1.000.000 dewritey 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.sortenen_US.UTF-8: 0,48 s con GNU y 2,30 s con uutils, 4,8 veces. En el localeCno 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 -rycp -ade 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 -tde 5.000 archivos: 6 veces.factorsobre 100.000 números: 3,8 veces, con 100.000 llamadaswritecontra 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.
| Comando | GNU 9.12 | uutils 0.12.0 |
|---|---|---|
mv de tmpfs a ext4 | conserva la fecha de modificación | pone la del momento del movimiento |
wc -m con LC_ALL=C | cuenta bytes | cuenta caracteres UTF-8 |
numfmt --to=si --format=%.2f con 143070000 | 143,08M | 143,07M |
timeout -s KILL sobre un proceso colgado | sale con 137 | sale con 124 |
mkdir -m u=rwx,g+s | crea con modo 2777 | crea con modo 777 |
base64 -d con entrada inválida | escribe lo decodificado y falla | falla sin escribir nada |
date -d '2021-03-04 next friday' | 4 de marzo | 5 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
releasede 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
pasteyfactor(las llamadaswritepor 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
mventre discos, o donde ordenes texto en UTF-8 a gran escala, hasta que se corrija elmvy se cierre el 4,8 desort.
Fuentes
- Rust Coreutils 0.12.0, notas de la release del 17 de septiembre de 2026.
- Rust Coreutils 0.12 Released With Fixes Sought By Ubuntu, Phoronix, 17 de septiembre de 2026.
- coreutils-9.12 released, GNU, 14 de septiembre de 2026.
- uutils issue 11743,
mv: preserve mtime/atime when moving across filesystems, abierto el 10 de abril de 2026. - hyperfine 1.20.0, la herramienta de medición.
- GNU coreutils, la implementación de referencia.
Comentarios
Todavía no hay comentarios. El primero es tuyo.