Perfiles de build: dev, release y el ajuste fino
Un perfil elige un punto en el triángulo tiempo de compilación, velocidad de ejecución y tamaño del binario. dev y release son dos puntos institucionalizados; opt-level, lto, codegen-units y panic son las palancas para moverte por el resto. Y los overrides por paquete permiten optimizar una sola dependencia sin sacrificar tu bucle de iteración.
Un perfil de compilación es una elección sobre un triángulo de tensiones: tiempo de compilación, velocidad de ejecución y tamaño del binario. No puedes maximizar los tres a la vez —optimizar más cuesta compilar más, y el binario más rápido rara vez es el más pequeño—, así que cada perfil fija un punto en esa superficie. Cargo te entrega dos institucionalizados, dev y release, que cubren el noventa por ciento de los casos, y un juego de palancas —opt-level, lto, codegen-units, panic— para moverte a cualquier otro. Saber girarlas, y saber que puedes girarlas por dependencia, es la diferencia entre aceptar los valores por defecto y esculpir el binario que necesitas.
- Distinguir los perfiles
dev,release,testybenchy su herencia. - Ajustar
opt-level,lto,codegen-unitsypaniccon criterio. - Optimizar una sola dependencia con overrides por paquete.
- Diseñar un perfil de release minimizado en tamaño.
Los cuatro perfiles y el triángulo
Cargo trae cuatro perfiles. dev gobierna cargo build; release, cargo build --release; y dos que heredan de ellos: test hereda de dev y bench hereda de release. Sus valores por defecto encarnan los dos extremos del triángulo:
[profile.dev]
opt-level = 0 # sin optimizar: compila rapido, itera rapido
debug = true # simbolos completos para el depurador
overflow-checks = true # panic si un entero desborda
[profile.release]
opt-level = 3 # optimizacion agresiva
debug = false
overflow-checks = false
dev prioriza el bucle de feedback: compilación veloz, aritmética comprobada, símbolos para depurar. release prioriza el binario final: código rápido, sin comprobaciones de desbordamiento, sin símbolos. Elegir perfil es elegir en qué vértice del triángulo quieres estar hoy.
Las palancas finas
Más allá de esos dos puntos, cada palanca mueve una tensión concreta:
opt-levelacepta0,1,2,3y también"s"y"z", que optimizan por tamaño en vez de por velocidad ("z"es el más agresivo con el tamaño, hasta desactivar el desenrollado de bucles).lto—link-time optimization— habilita el inlining entre crates."thin"da casi todo el beneficio a bajo coste;"fat"(otrue) exprime más a cambio de una compilación mucho más lenta.codegen-unitsparte el crate en unidades que se compilan en paralelo (256 en dev, 16 en release). Bajarlo a1da al optimizador visión global del crate —mejor código— renunciando al paralelismo interno.panicpor defecto es"unwind"(desenrolla la pila liberando recursos). Ponerlo en"abort"elimina las tablas de desenrollado —binario más pequeño y algo más rápido— pero aborta el proceso en unpanic, y anulacatch_unwind.stripcon"symbols"elimina la tabla de símbolos, recortando el tamaño del binario en disco.
Combinadas, estas palancas producen recetas con intención. La del binario mínimo, típica para WebAssembly o distribución embebida, sacrifica velocidad de compilación y capacidad de desenrollado por cada byte:
[profile.release]
opt-level = "z" # optimiza por tamano
lto = true # inlining entre crates
codegen-units = 1 # una unidad: mejor optimizacion global
panic = "abort" # sin tablas de desenrollado
strip = true # elimina simbolos
opt-level
De 0 a 3 por velocidad; "s" y "z" por tamaño. Cada escalón cuesta tiempo de compilación a cambio de código más rápido o compacto.
lto
Inlining entre crates. "thin" casi gratis; "fat" exprime más a cambio de un enlazado mucho más lento.
codegen-units
Cuantas más, más paralelismo al compilar; a 1, el optimizador ve el crate entero y produce mejor código.
panic
"unwind" desenrolla y libera; "abort" recorta tamaño y velocidad pero mata el proceso y anula catch_unwind.
Por defecto, release desactiva overflow-checks, así que un u8 que pasa de 255 envuelve a 0 en silencio en lugar de entrar en panic como haría en dev. Es una decisión de rendimiento con consecuencias de corrección: un cálculo que se comporta en pruebas puede desbordar callado en producción. Si tu dominio no tolera esa envoltura, reactiva overflow-checks = true en release o usa aritmética explícita con checked_add y compañía.
Overrides por paquete y perfiles a medida
La palanca más sutil es que la optimización no tiene por qué ser uniforme. Con [profile.<perfil>.package.<crate>] ajustas el nivel de una sola dependencia. El caso de oro: estás en dev para iterar rápido, pero una dependencia numérica —descompresión, imagen, criptografía— corre tan lenta sin optimizar que hace inusable tu programa. La optimizas a ella, y solo a ella:
# En dev, tu crate sigue a opt-level 0 (recompila rapido),
# pero esta dependencia pesada se compila optimizada:
[profile.dev.package.image]
opt-level = 3
# Un perfil propio que hereda de release y afina mas:
[profile.release-fast]
inherits = "release"
lto = "fat"
codegen-units = 1
Un perfil personalizado se invoca con cargo build --profile release-fast. Y hay dos overrides más que resuelven molestias reales del día a día:
# Optimiza los build scripts y las macros procedurales
# aunque tu crate siga sin optimizar:
[profile.dev.build-override]
opt-level = 3
# Optimiza TODAS las dependencias en dev, pero no tu propio crate:
[profile.dev.package."*"]
opt-level = 2
El comodín "*" alcanza a la vez a todas las dependencias: es la receta clásica para que un proyecto con muchas dependencias pesadas siga siendo usable en dev sin renunciar a recompilar tu propio código en un parpadeo.
flowchart TD DEV[perfil dev] --> TEST[perfil test hereda dev] REL[perfil release] --> BENCH[perfil bench hereda release] REL --> FAST[perfil propio hereda release] DEV --> OV[override por paquete: una dependencia optimizada] style DEV fill:#89b4fa,color:#11111b style REL fill:#a6e3a1,color:#11111b style OV fill:#f9e2af,color:#11111b
El perfil de compilación institucionaliza una verdad incómoda de los lenguajes compilados: no existe un único «modo rápido». Tiempo de compilación, velocidad de ejecución y tamaño del binario forman un triángulo cuyos vértices se repelen; mejorar uno suele empeorar otro. dev y release no son «lento» y «rápido», son dos puntos concretos y bien elegidos sobre esa superficie de compromiso —el vértice de la iteración veloz y el de la ejecución veloz—, y su existencia como valores por defecto ahorra a la mayoría tener que pensar en el resto del espacio. Pero el poder real aparece cuando dejas de ver dos botones y ves un continuo que puedes recorrer palanca a palanca: subes opt-level, activas lto, colapsas codegen-units a uno, cambias panic a abort, y te desplazas a un punto que ningún preset nombra pero que tu caso exige. Y el golpe maestro, el que revela la verdadera granularidad de la idea, es el override por paquete: el nivel de optimización no tiene por qué ser global. Puedes tener tu propio crate a opt-level cero para recompilar en un parpadeo mientras la única dependencia que domina el tiempo de ejecución arde a opt-level tres. Esa combinación —iteración rápida en lo que cambias, ejecución rápida en lo que no— es literalmente inexpresable con una sola bandera global, y sin embargo es lo que casi todo proyecto serio de gráficos, audio o simulación acaba necesitando. La lección trasciende a Cargo: el rendimiento no es una casilla que marcas, es un territorio que se cartografía, y el perfil de compilación es el mapa con el que decides, zona por zona, dónde gastar y dónde ahorrar.
Cuatro perfiles: dev (rápido de compilar), release (rápido de ejecutar), y test/bench que heredan de ellos. Palancas: opt-level (0-3, "s"/"z" por tamaño), lto ("thin"/"fat"), codegen-units (a 1 optimiza mejor, compila más lento), panic ("abort" recorta tamaño y anula catch_unwind), strip. release apaga overflow-checks: cuidado con la envoltura silenciosa. Overrides [profile.dev.package.X] optimizan una sola dependencia; inherits crea perfiles a medida.
- Compila un programa en
devy enreleasey compara tamaño y tiempo de ejecución de un bucle intensivo. Explica qué vértice del triángulo elige cada uno. - Escribe el perfil de tamaño mínimo (
opt-level = "z",lto,codegen-units = 1,panic = "abort",strip) y mide cuánto encoge el binario frente alreleaseestándar. - Provoca un desbordamiento de
u8y observa la diferencia entredev(panic) yrelease(envoltura). Reactivaoverflow-checksenreleasey confirma el cambio. - Añade una dependencia numérica pesada, déjala lenta en
dev, y luego optimízala con[profile.dev.package.<crate>]; mide la mejora sin recompilar tu crate optimizado. - Define un perfil
release-fastconinherits = "release"ylto = "fat", invócalo con--profiley compara su binario con elreleasenormal.