Skip to Content
Nueva versión 12 disponible 🎉

Rendimiento y ajuste fino

La ofuscación del flujo de control sacrifica algo de rendimiento en tiempo de ejecución a cambio de protección. Los algoritmos ligeros prácticamente no cuestan nada; los algoritmos de aplanamiento añaden un despachador que se ejecuta en cada llamada y, por tanto, cuestan más. En esta página se comparan los algoritmos, se explica el ajuste de iteraciones y se muestra cómo mantener rápidos los métodos de uso intensivo.

Comparación de los algoritmos

AlgoritmoFamiliaCoste relativo en tiempo de ejecuciónIL verificableEdición
ifLigeraInsignificanteSíTodas
gotoLigeraInsignificanteSíTodas
switchAplanamientoAltoSíTodas
caseAplanamiento (con switch)AltoSíTodas
callAplanamiento (con switch)AltoSíTodas
valueAplanamiento (con switch)AltoSíTodas
chain (Chained State)AplanamientoAlto (≈ switch)SíUltimate
token—BajoNoTodas
underflow—BajoNoTodas

Cómo leer la tabla.

  • if y goto añaden bifurcaciones y condiciones, pero no un despachador, de modo que su coste en tiempo de ejecución es insignificante: puede aplicarlos de forma generalizada.
  • La familia de aplanamiento (switch, case, call, value) y chain reconstruyen un método en torno a un despachador. En un método pequeño al que se llama decenas de millones de veces en un bucle intensivo, una sola pasada de aplanamiento añade aproximadamente un orden de magnitud al tiempo por llamada de ese método. Chained State se aparta solo unos pocos puntos porcentuales de switch a pesar de ser mucho más difícil de desofuscar: la protección adicional prácticamente no cuesta nada respecto al propio aplanamiento.
  • token y underflow cuestan poco en tiempo de ejecución, pero producen IL no verificable, por lo que Babel los desactiva en .NET y .NET Core.

Son cifras del peor caso, obtenidas con una microprueba de rendimiento que no hace más que llamar en un bucle a un método aplanado diminuto. La mayoría de los métodos no se invocan en bucles tan intensivos, de modo que el impacto real del aplanamiento suele ser pequeño; aun así, conviene reservar el aplanamiento para los métodos que importan y mantenerlo fuera de las rutas de uso intensivo.

Iteraciones del flujo de control

El efecto de ofuscación de cualquier algoritmo de flujo de control depende sobre todo del número de iteraciones que se aplican a un método: cuanto mayor es el número, más complejo es el resultado y, en los algoritmos de aplanamiento, mayor es el coste en tiempo de ejecución, porque cada pasada añade otra capa de despacho.

Babel permite establecer el número de iteraciones con --iterations <n> (o con ILIterations desde MSBuild). Si se establece en 0, la ofuscación del flujo de control se desactiva. Existe un límite superior, que depende de la estructura del método, a partir del cual el flujo de un método dado ya no se puede alterar más, de modo que aumentar el número no siempre añade más instrucciones.

Limitar la ofuscación del flujo de control

Como el aplanamiento añade bifurcaciones a todos los métodos que toca, aplicarlo de forma indiscriminada puede ralentizar el código crítico para el rendimiento. Use las reglas XML para limitar la ofuscación del flujo de control a los métodos que la necesitan y excluir el resto, por ejemplo los métodos generados por el compilador, que rara vez contienen propiedad intelectual que merezca protegerse:

<Rules> <Rule name="rule" feature="control flow" exclude="true" applyToMembers="true"> <Target>Classes</Target> <Pattern>*</Pattern> <HasAttribute onEnclosingType="false">System.Runtime.CompilerServices.CompilerGeneratedAttribute</HasAttribute> </Rule> </Rules>

Esta regla se dirige a la función control flow con exclude="true", y applyToMembers="true" la extiende a todos los miembros de las clases que coinciden. El elemento <Target> selecciona la clase de elemento (aquí, Classes), <Pattern> coincide con sus nombres (* = todos) y <HasAttribute> restringe la coincidencia a las clases que llevan CompilerGeneratedAttribute.

El mismo planteamiento permite excluir cualquier método que sepa que es crítico para el rendimiento, de modo que puede equilibrar protección y velocidad: aplane el código sensible y deje intactas las rutas de uso intensivo.

Last updated on