- La comparación de rendimiento entre Rust y C es una cuestión compleja que depende de cómo se defina la premisa de que “todas las condiciones son iguales”
- En el caso del ensamblador en línea, ambos lenguajes pueden generar el mismo código ensamblador, por lo que no hay una diferencia de velocidad propia del lenguaje
- En la disposición de memoria de structs, Rust puede tener un tamaño menor al reordenar campos, aunque también puede usar el mismo layout que C con el atributo
#[repr(C)]
- Debido a las diferencias en las comprobaciones en tiempo de ejecución y el comportamiento de los desarrolladores, la estructura del código y el rendimiento pueden variar en proyectos reales
- En conclusión, no hay una diferencia de rendimiento causada por limitaciones propias del lenguaje; el resultado cambia según el proyecto y los factores humanos
Planteamiento del problema y ambigüedad de la premisa
- El punto de partida es la pregunta planteada en Reddit: “si las condiciones son las mismas, ¿puede Rust ser más rápido que C?”
- La expresión “todas las condiciones son iguales” es, en sí misma, un concepto muy difícil de definir al comparar lenguajes
- La comparación de rendimiento depende no solo de las diferencias del lenguaje, sino también de la forma del código, las decisiones de desarrollo y la optimización del compilador
Comparación de ensamblador en línea
- Rust admite ensamblador en línea a nivel del lenguaje, mientras que C lo implementa mediante extensiones del compilador
- En ambos lenguajes se puede escribir el mismo ejemplo usando la instrucción
rdtsc
- El ensamblador generado por
rustc 1.87.0 y clang 20.1.0 se muestra con una forma completamente idéntica
- Este caso no demuestra una diferencia de rendimiento entre lenguajes, pero sí prueba que Rust puede realizar control de bajo nivel al mismo nivel que C
Diferencias en el layout de structs
- Los structs de Rust pueden optimizar el uso de memoria reordenando campos
- En el ejemplo, el struct de Rust se calcula en 16 bytes, mientras que el mismo struct en C resulta en 24 bytes
- En C, hay que cambiar manualmente el orden de los campos para obtener el mismo tamaño
- Si se usa el atributo
#[repr(C)] en Rust, se puede forzar el mismo layout de memoria que en C
Factores sociales y del desarrollador
- Gracias a las comprobaciones de seguridad de Rust, hay casos en los que los desarrolladores pueden intentar optimizaciones más agresivas
- En el proyecto Stylo de Mozilla, dos intentos de paralelización en C++ fracasaron, pero en Rust se implementó con éxito
- Incluso en un mismo proyecto, el rendimiento y la estabilidad del código resultante pueden variar según el lenguaje y la experiencia del desarrollador
- Como el resultado de “la misma tarea” cambia según si quien la hace es principiante o experto, y según su dominio del lenguaje, una comparación simple es difícil
Comprobaciones en compilación y en ejecución
- Muchas de las comprobaciones de seguridad de Rust se realizan en tiempo de compilación, pero algunas permanecen como comprobaciones en tiempo de ejecución
- Por ejemplo, al acceder a
array[0], Rust realiza una verificación de límites, mientras que C no lo hace
- Si se usa
get_unchecked() en Rust, se puede obtener el mismo comportamiento que en C
- Cuando el compilador puede demostrar la seguridad, ambos lenguajes pueden eliminar las comprobaciones mediante optimización
- Estas diferencias influyen en la forma de escribir el código y, como resultado, pueden provocar diferencias de rendimiento
Conclusión
- Incluso si se asumiera que C es “el lenguaje más rápido”, no hay razón para que Rust no pueda alcanzar el mismo nivel de rendimiento
- Más que las limitaciones del lenguaje, son las características del proyecto, la capacidad del desarrollador, las restricciones de tiempo y otras variables externas las que determinan la diferencia de rendimiento
- Por lo tanto, la pregunta “¿Rust es más rápido que C?” se interpreta mejor como un problema de contexto de ingeniería que como una simple comparación de lenguajes
9 comentarios
Opiniones en Hacker News
En resumen, la velocidad máxima es casi la misma, pero en código real hay una gran diferencia.
En especial, el multithreading es una variable muy importante. En Rust, todas las variables globales deben ser seguras para hilos, se usen o no, y el borrow checker limita el acceso a memoria a compartir o modificar, pero no ambas cosas a la vez.
Por eso, escribir código multihilo en Rust es casi lo normal. En cambio, en C crear hilos ya implica una carga por temas de compatibilidad entre plataformas y riesgos al depurar.
En C no es tan difícil crear hilos, pero sí es más engorroso que en Rust con
std::thread::spawn(move || { ... });Más que la seguridad de memoria, influye más el modelo de concurrencia del lenguaje. Go permite paralelizar fácilmente con
go f()aunque no sea memory-safe.Personalmente, he visto más heisenbugs en Go.
#pragma omp fortambién se puede paralelizar fácilmente en C.Gracias a los traits de Rust, se pueden crear abstracciones más rápidas y flexibles.
En C se puede imitar con macros o punteros a función, pero en Rust quien llama puede elegir entre despacho dinámico o despacho estático.
En entornos embebidos, los punteros a función perjudican la caché y reducen el rendimiento, mientras que los traits de Rust permiten optimización por inline y resultan mucho más eficientes.
Tanto en Rust como en C, al final hay que trabajar a nivel de bytes, y hoy en día las herramientas de parcheo binario también han mejorado mucho y son fáciles de aprovechar.
Box<dyn Trait>en la firma de una función, obligas a quien llama a usar despacho dinámico.Si usas
impl Trait, la elección queda del lado del llamador.Personalmente, veo a Rust, C y C++ casi como parte de la misma familia de lenguajes de bajo nivel, así que la diferencia de rendimiento me parece mínima.
Las reglas estrictas de aliasing de Rust favorecen la optimización, y el UB (comportamiento indefinido) de C/C++ existe en parte por rendimiento.
Además, los genéricos de Rust y C++ son mucho más potentes que en C, así que, por ejemplo, es más fácil optimizar por inline un ordenamiento basado en plantillas que
qsort().Creo que este tipo de debates de velocidad entre lenguajes casi siempre carecen de sentido.
Más que el lenguaje en sí, lo que determina el rendimiento es la implementación del compilador.
Rust, C y C++ son todos lenguajes de bajo nivel, pero importa cómo se define eso de “rápido”.
No es lo mismo hablar de la velocidad máxima de código optimizado por un experto que de la probabilidad de que un desarrollador promedio escriba código rápido dentro del presupuesto.
Pero si se optimiza manualmente, las diferencias entre lenguajes casi desaparecen.
Aun así, Rust tiene una ligera ventaja porque es un lenguaje en el que es más fácil escribir código rápido.
Yo pensaba que la ventaja de Rust estaba en el multithreading y la asignación en stack.
Gracias al modelo de ownership, se pueden poner más cosas en el stack que en C/C++, reduciendo la sobrecarga de malloc/free.
Como estos temas suelen provocar debates emocionales, quise hablar más de diferencias de enfoque que de cifras específicas.
Al hablar de la “velocidad” de un lenguaje, hay que mirar dos cosas:
Rust y C casi no tienen checks en runtime, así que son más rápidos que Python o JS.
Aun así, Rust transmite mejor la información de aliasing, lo que deja más margen para optimizar.
En modo debug puede ser tan lento como Ruby, pero en modo release alcanza velocidades del nivel de C.
C++ y Rust tienen más funcionalidades en tiempo de compilación que C, por lo que es más fácil escribir código rápido.
Por ejemplo, este código en C es casi imposible.
En C se necesitan herramientas externas como re2c.
Como ensamblador no forma parte del estándar de C, es difícil compararlo directamente con Rust, y Rust en realidad se parece más al proyecto GCC en su naturaleza.
Que un lenguaje sea “rápido” al final depende de la implementación y el contexto.
Más que la velocidad del lenguaje en sí, influye mucho más la combinación de compilador y hardware.
No sé qué lenguaje será el más rápido en promedio, pero me parece que la dispersión más grande la tendría C++.
En sistemas embebidos se programa incluso considerando el tamaño de la línea de caché del hardware. Al final, esto depende de hasta dónde puede llegar el programador con optimizaciones extremas por encima del lenguaje, además del rendimiento de la biblioteca estándar y del compilador; de todos modos, como ambos soportan bajo nivel, parece que la diferencia de overhead sería mínima. Por eso no parece una discusión muy significativa... Si se necesita una optimización al límite, al final hace falta intervención humana. Los compiladores no son tan perfectos como uno imagina.
Creo que Rust terminará siendo más un reemplazo de C++ que de C. C es prácticamente el único (quizá el último) lenguaje en el que se puede anticipar el código que generará el compilador…
Zig tampoco está mal... :'(
Al escribirlo, terminé usando un estilo de resumen de IA :(
¿No lo hizo a propósito? Jeje
Eso depende de la capacidad del compilador.
Si ensamblas el mismo código, se verá.
Parece que la gente de ffmpeg no cree que rust sea más rápido que c jajaja https://www.memorysafety.org/blog/rav1d-perf-bounty/