2009-02-24 9 views
15

Algunas personas aman el uso de la palabra clave inline en C, y ponen grandes funciones en encabezados. ¿Cuándo considera esto ineficaz? Lo considero en algún momento incluso molesto, porque es inusual.¿Cuándo es "inline" ineficaz? (en C)

Mi principio es que inline se debe usar para las pequeñas funciones a las que se accede con mucha frecuencia, o para tener una verificación de tipo real. De todos modos, mi gusto me guía, pero no estoy seguro de cómo explicar mejor las razones por las que inline no es tan útil para grandes funciones.

En this question la gente sugiere que el compilador puede hacer un mejor trabajo adivinando qué hacer. Esa fue también mi suposición. Cuando trato de usar este argumento, las personas responden que no funciona con funciones provenientes de diferentes objetos. Bueno, no sé (por ejemplo, usando GCC).

Gracias por sus respuestas!

+0

Sospecho que, al igual que el "registro" se volvió obsoleto a finales de la década de 1980, "en línea" ha quedado obsoleto durante varios años. –

+0

\ o /, muere "en línea"! – elmarco

+0

Inline definitivamente no está obsoleto. ¿Alguna vez has leído el código fuente de un kernel moderno? Inline es irrelevante para las personas que escriben aplicaciones, pero esas personas no deberían usar C hoy en día de todos modos, y es tan relevante para la programación de sistemas como lo fue alguna vez. – nosatalian

Respuesta

26

inline hace dos cosas:

  1. le da una exención de la "una regla definición" (véase más adelante). Este siempre se aplica.
  2. Da al compilador una pista para evitar una llamada a la función. El compilador es libre de ignorar esto.

# 1 Puede ser muy útil (por ejemplo, poner definición en el encabezado si es corto) incluso si # 2 está deshabilitado.

En la práctica, los compiladores a menudo hacen un mejor trabajo al determinar qué alinearse ellos mismos (especialmente si está disponible la optimización guiada de perfil).


[EDIT: referencias completas y texto pertinente]

los dos puntos anteriores tanto siguen de la norma ISO/ANSI (ISO/IEC 9899: 1999 (E), comúnmente conocido como "C99") .

En §6.9 "Definición externo", párrafo 5:

Un definición externa es una declaración externa que es también una definición de una función (que no sea una definición en línea) o un objeto. Si se utiliza un identificador declarado con enlace externo en una expresión (que no sea parte del operando de un operador sizeof cuyo resultado es una constante entera), en alguna parte del programa completo habrá exactamente una definición externa para el identificador; de lo contrario, no habrá más de uno.

Si bien la definición equivalente en C++ se denomina explícitamente Regla de una definición (ODR) tiene el mismo propósito. Externo (es decir, no "estático", y por lo tanto local a una sola Unidad de traducción, generalmente un solo archivo de origen) solo se puede definir una vez solo a menos que sea una función y en línea.

En §6.7.4, "función de especificadores", se define la palabra clave en línea:

haciendo una función de una función en línea sugiere que las llamadas a la función de ser lo más rápido posible . [118] El grado en que tales sugerencias son efectivas es definido en la implementación.

y la nota (no normativo), pero proporciona aclaración:

Mediante el uso de, por ejemplo, una alternativa al mecanismo de llamada a la función de costumbre, tales como ‘‘inline sustitución’’. La sustitución en línea no es una sustitución textual, ni crea una nueva función. Por lo tanto, por ejemplo, la expansión de una macro utilizada dentro del cuerpo de la función usa la definición que tenía en el punto en que aparece el cuerpo de la función, y no donde se llama a la función; e identificadores se refieren a las declaraciones en el alcance donde ocurre el cuerpo. Del mismo modo, la función tiene una sola dirección, independientemente de la cantidad de definiciones en línea que se producen además de la definición externa.

Resumen: lo que la mayoría de los usuarios de C y C++ esperan de la línea no es lo que obtienen. Su objetivo principal aparente, para evitar gastos generales de llamadas funcionales, es completamente opcional. Pero para permitir la compilación por separado, se requiere una relajación de la definición única.

(Todo el énfasis en las citas de la norma.)


EDIT 2: Algunas notas:

  • Hay varias restricciones a las funciones en línea externos. No puede tener una variable estática en la función y no puede hacer referencia a objetos/funciones de alcance de TU estáticas.
  • Acabo de ver esto en "whole program optimisation" de VC++, que es un ejemplo de un compilador que hace su propia cosa en línea, en lugar del autor.
+0

Ni siquiera pensé en el # 1, pero tienes razón, ¡muy útil! Gracias por el consejo. – Mike

+0

-1: # 1 no es cierto: al menos para gcc, también debes agregar 'static'. – Christoph

+1

@Christoph: eso es solo gcc. Acabo de verificar en el documento estándar C99. – Richard

1
  1. inline actúa como sólo un indicio.
  2. Agregado solo muy recientemente. Así que funciona solo con los últimos compiladores compatibles.
+0

Agregado recientemente?Estoy bastante seguro de que ha estado en línea durante al menos una docena de años (desde que comencé a codificar en C) –

+0

en línea ha estado en C++ durante mucho tiempo. En C ha estado en el estándar desde C99 (pero no muchos compiladores soportan completamente C99), pero ha estado en varios compiladores como una extensión por un tiempo. Entonces, para los programas C, usar en línea puede ser un poco dolor de cabeza de portabilidad. –

+0

10 años. Sin embargo, consigue que MS implemente un compilador C decente. De todos modos, eso es reciente en C-time;) Agregado en C99. Ver: http://en.wikipedia.org/wiki/C99 – dirkgently

4

Inline no es efectivo cuando usa el puntero para funcionar.

+0

seguro, ese no es el caso en mi proyecto sin embargo. gracias – elmarco

+0

+1 observación interesante.! –

5

Lo importante de una declaración en línea es que no necesariamente hace nada. Un compilador es libre de decidir, en muchos casos, alinear una función no declarada así y vincular funciones declaradas en línea.

+0

, incluso si la función en línea con la que se vincula es de un objeto .o diferente – elmarco

+0

el modificador en línea es solo una pista. Si el compilador elige, puede decidir que una función no puede estar en línea, pero el enlazador podría decidir reescribir la llamada a la función como en línea de todos modos. Simplemente no hay garantía de comportamiento con en línea. – SingleNegationElimination

2

Correcto. El uso de inline para funciones grandes aumenta el tiempo de compilación y aporta un rendimiento extra pequeño a la aplicación. Las funciones en línea se usan para indicar al compilador que una función debe incluirse sin una llamada, y debe ser un código pequeño repetido muchas veces. En otras palabras: para grandes funciones, el costo de realizar la llamada en comparación con el costo de la implementación de la función propia es insignificante.

+0

Como se ha mencionado anteriormente, inline se usa para _suggest_ al compilador de que la función debe incluirse sin una llamada. No hay garantía de que realmente lo haga. – Peter

2

Inline se puede usar para funciones pequeñas y de uso frecuente, como el método getter o setter. Para grandes funciones no es aconsejable usar en línea ya que aumenta el tamaño del exe. También para funciones recursivas, incluso si hace en línea, el compilador lo ignorará.

2

Utilizo principalmente funciones en línea como macros de tipo seguro. Se ha hablado de agregar soporte para optimizaciones de tiempo de enlace a GCC durante bastante tiempo, especialmente desde que apareció LLVM. Sin embargo, todavía no sé cuánto se ha implementado realmente.

3

En línea es efectivo en un caso: cuando tienes un problema de rendimiento, ejecutaste tu generador de perfiles con datos reales, y descubrí que la sobrecarga de llamadas a funciones para algunas funciones pequeñas era significativa.

Fuera de eso, no puedo imaginar por qué lo usarías.

+0

Dang. Yo engañé tu respuesta. Bueno, ese hecho por sí solo muestra claramente que estabas impartiendo una gran visión, por lo que vota. :-) –

+0

Yo diría que a veces no es necesario ejecutar el generador de perfiles para saber que la sobrecarga es significativa. Por ejemplo, es bastante obvio para mí que una función de incremento atómico debe estar alineada, y también disminuirá el tamaño del código. –

2

Personalmente no creo que debiera vez en línea, a menos que primero haya ejecutado un generador de perfiles en su código y ha demostrado que existe un cuello de botella significativo en esa rutina que se pueden reducirse en parte mediante procesos en línea.

Este es otro caso más de la Optimización prematura de la que advirtió Knuth.

5

Un ejemplo para ilustrar los beneficios de inline. sinCos.h:

int16 sinLUT[ TWO_PI ]; 

static inline int16_t cos_LUT(int16_t x) { 
    return sin_LUT(x + PI_OVER_TWO) 
} 

static inline int16_t sin_LUT(int16_t x) { 
    return sinLUT[(uint16_t)x]; 
} 

Al hacer algún número pesada crujido y que desea evitar el desperdicio de ciclos en el cálculo de seno/coseno que sustituya sen/cos con una LUT.

Cuando se compila sin línea el compilador no optimizará el bucle y la asm salida mostrará algo en la línea de:

;*----------------------------------------------------------------------------* 
;* SOFTWARE PIPELINE INFORMATION 
;*  Disqualified loop: Loop contains a call 
;*----------------------------------------------------------------------------* 

Cuando se compila con el compilador en línea tiene conocimiento de lo que sucede en el circuito y optimizará porque sabe exactamente lo que está sucediendo.

La salida .asm tendrá un bucle optimizado "en derivación" (es decir, intentará utilizar completamente todas las ALU del procesador e intentará mantener la tubería del procesador llena sin NOPS).


En este caso concreto, yo era capaz de aumentar mi rendimiento en alrededor de 2 ó 4, que me consiguió dentro de lo que necesitaba para mi fecha límite en tiempo real.


p.s. Estaba trabajando en un procesador de punto fijo ... y cualquier operación de punto flotante como sin/cos mató mi rendimiento.

5

Otra razón por la que no debe usar en línea para funciones grandes es en el caso de las bibliotecas. Cada vez que cambie las funciones en línea, puede perder compatibilidad con ABI porque la aplicación compilada en un encabezado anterior todavía tiene la versión anterior de la función. Si las funciones en línea se usan como una macro de tipo seguro, es muy probable que la función nunca tenga que cambiarse en el ciclo de vida de la biblioteca. Pero para grandes funciones esto es difícil de garantizar.

Por supuesto, este argumento solo aplica si la función es parte de su API pública.

1

Las funciones en línea deben ser de aproximadamente 10 líneas o menos, más o menos, dependiendo de su compilador de su elección.

Puede decirle a su compilador que quiere algo en línea .. es hasta que el compilador lo haga. No hay ninguna opción de -force-inline que sepa que el compilador no puede ignorar.Es por eso que debe mirar la salida del ensamblador y ver si su compilador en realidad hizo en línea la función, si no, ¿por qué no? Muchos compiladores dicen en silencio '¡te jodan!' en ese sentido.

así que si:

static inline unsigned int foo (const char * bar)

.. no mejora las cosas más foo static int() es el momento de volver a sus optimizaciones (y bucles probable) o discute con tu compilador Tenga especial cuidado para discutir primero con su compilador, no con las personas que lo desarrollan ... o con su tienda de lecturas desagradables cuando abre su bandeja de entrada al día siguiente.

Mientras tanto, al hacer algo (o intentar hacer algo) en línea, ¿justifica realmente la hinchazón? ¿De verdad quieres esa función expandida cada vez se llama? ¿El salto es tan costoso ?, tu compilador suele ser correcto 9/10 veces, verifica la salida intermedia (o los vuelcos de amperios).