Estoy usando un int
tipo para almacenar un valor. Según la semántica del programa, el valor siempre varía en un rango muy pequeño (0 - 36), y int
(no a char
) se usa solo debido a la eficiencia de la CPU.
Parece que se pueden realizar muchas optimizaciones aritméticas especiales en un rango tan pequeño de enteros. Muchas llamadas de función en esos enteros pueden optimizarse en un pequeño conjunto de operaciones "mágicas", y algunas funciones pueden incluso optimizarse en búsquedas de tablas.
Entonces, ¿es posible decirle al compilador que esto int
siempre está en ese rango pequeño, y es posible que el compilador haga esas optimizaciones?
unsigned
tipos, ya que es más fácil para el compilador razonar con ellos.var value: 0..36;
.int
y esunsigned int
necesario extender el signo o cero de 32 a 64 bits, también, en la mayoría de los sistemas con punteros de 64 bits. Tenga en cuenta que en x86-64, las operaciones en registros de 32 bits se extienden de cero a 64 bits de forma gratuita (no se extiende la señal, pero el desbordamiento firmado es un comportamiento indefinido, por lo que el compilador solo puede usar matemática firmada de 64 bits si lo desea). Por lo tanto, solo verá instrucciones adicionales para argumentos de función de 32 bits de extensión cero, no resultados de cálculo. Lo haría para tipos sin signo más estrechos.Respuestas:
Sí, es posible. Por ejemplo,
gcc
puede usar__builtin_unreachable
para decirle al compilador acerca de condiciones imposibles, así:Podemos envolver la condición anterior en una macro:
Y úsalo así:
Como puede ver ,
gcc
realiza optimizaciones basadas en esta información:Produce:
Sin embargo, una desventaja es que si su código alguna vez rompe tales suposiciones, obtendrá un comportamiento indefinido .
No le avisa cuando esto sucede, incluso en las versiones de depuración. Para depurar / probar / atrapar errores con suposiciones más fácilmente, puede usar una macro híbrida asumir / afirmar (créditos a @David Z), como esta:
En las compilaciones de depuración (
NDEBUG
sin definir), funciona como unassert
mensaje de error de impresión ordinario y unabort
programa 'ing', y en las compilaciones de lanzamiento utiliza una suposición, produciendo código optimizado.Sin embargo, tenga en cuenta que no es un sustituto de la versión regular
assert
:cond
permanece en las versiones de lanzamiento, por lo que no debe hacer algo asíassume(VeryExpensiveComputation())
.fuente
return 2
el compilador eliminó la rama del código.__builtin_expect
es una pista no estricta.__builtin_expect(e, c)
debe leerse como "e
es más probable que evalúec
" y puede ser útil para optimizar la predicción de rama, pero no se limitae
a ser siemprec
, por lo que no permite que el optimizador descarte otros casos.Mira cómo se organizan las ramas en el ensamblaje .__builtin_unreachable()
.assert
, por ejemplo, definirassume
comoassert
cuandoNDEBUG
no está definido, y que__builtin_unreachable()
cuandoNDEBUG
se define. De esa forma, obtiene el beneficio de la suposición en el código de producción, pero en una compilación de depuración aún tiene una comprobación explícita. Por supuesto, debe realizar suficientes pruebas para asegurarse de que la suposición se cumplirá en la naturaleza.Hay soporte estándar para esto. Lo que debe hacer es incluir
stdint.h
(cstdint
) y luego usar el tipouint_fast8_t
.Esto le dice al compilador que solo está usando números entre 0 y 255, pero que es libre de usar un tipo más grande si eso proporciona un código más rápido. Del mismo modo, el compilador puede suponer que la variable nunca tendrá un valor superior a 255 y luego realizar optimizaciones en consecuencia.
fuente
uint_fast8_t
realidad es un tipo de 8 bits (por ejemplounsigned char
) como en x86 / ARM / MIPS / PPC ( godbolt.org/g/KNyc31 ). A principios de DEC Alpha antes de 21164A , las cargas / tiendas de bytes no eran compatibles, por lo que cualquier implementación sensata lo usaríatypedef uint32_t uint_fast8_t
. AFAIK, no existe un mecanismo para que un tipo tenga límites de rango adicionales con la mayoría de los compiladores (como gcc), por lo que estoy bastante seguro de queuint_fast8_t
se comportaría exactamente igualunsigned int
o lo que sea en ese caso.bool
es especial y tiene un rango limitado a 0 o 1, pero es un tipo incorporado, no definido por los archivos de encabezado en términos dechar
, en gcc / clang. Como dije, no creo que la mayoría de los compiladores tengan un mecanismo eso lo haría posible.)uint_fast8_t
es una buena recomendación, ya que usará un tipo de 8 bits en plataformas donde sea tan eficiente comounsigned int
. (Estoy realmente no estoy seguro que losfast
que se supone tipos que ser rápido para , y si la caché huella de compensación se supone que es parte de ella.). x86 tiene un amplio soporte para operaciones de bytes, incluso para agregar bytes con una fuente de memoria, por lo que ni siquiera tiene que hacer una carga separada de extensión cero (que también es muy barata). gcc creauint_fast16_t
un tipo de 64 bits en x86, que es una locura para la mayoría de los usos (frente a 32 bits). godbolt.org/g/Rmq5bv .La respuesta actual es buena para el caso cuando sabe con certeza cuál es el rango, pero si aún desea un comportamiento correcto cuando el valor está fuera del rango esperado, entonces no funcionará.
Para ese caso, descubrí que esta técnica puede funcionar:
La idea es una compensación de datos de código: está moviendo 1 bit de datos (ya sea
x == c
) a la lógica de control .Esto sugiere al optimizador que,
x
de hecho, es una constante conocidac
, lo que lo alienta a alinear y optimizar la primera invocación porfoo
separado del resto, posiblemente en gran medida.Sin
foo
embargo, asegúrese de factorizar el código en una sola subrutina , no duplique el código.Ejemplo:
Para que esta técnica funcione, debe ser un poco afortunado: hay casos en los que el compilador decide no evaluar las cosas estáticamente, y son algo arbitrarios. Pero cuando funciona, funciona bien:
Simplemente use
-O3
y observe las constantes previamente evaluadas0x20
y0x30e
en la salida del ensamblador .fuente
if (x==c) foo(c) else foo(x)
? Si solo para atraparconstexpr
implementaciones defoo
?constexpr
y nunca me molesté en "actualizarla" después (aunque realmente nunca me he molestado en preocuparmeconstexpr
incluso después), pero la razón por la que no lo hice inicialmente fue porque quería facilita que el compilador los descomponga como código común y elimine la rama si decide dejarlos como llamadas a métodos normales y no optimizarlos. Esperaba que si ponía quec
fuera realmente difícil para el compilador c (lo siento, broma) que los dos son el mismo código, aunque nunca verifiqué esto.Solo estoy diciendo que si quieres una solución que sea más estándar en C ++, puedes usar el
[[noreturn]]
atributo para escribir el tuyounreachable
.Así que volveré a utilizar el excelente ejemplo de Deniss para demostrar:
Lo que, como puede ver , da como resultado un código casi idéntico:
La desventaja es, por supuesto, que recibe una advertencia de que una
[[noreturn]]
función, de hecho, regresa.fuente
clang
, cuando mi solución original no lo hace , un buen truco y +1. Pero todo depende mucho del compilador (como nos mostró Peter Cordes, yaicc
que puede empeorar el rendimiento), por lo que todavía no es de aplicación universal. Además, una nota menor: launreachable
definición debe estar disponible para el optimizador y en línea para que esto funcione .