Extraer la funcionalidad en métodos o funciones es imprescindible para la modularidad, legibilidad e interoperabilidad del código, especialmente en OOP.
Pero esto significa que se realizarán más llamadas a funciones.
¿Cómo la división de nuestro código en métodos o funciones realmente impacta el rendimiento en los idiomas modernos * ?
* Los más populares: C, Java, C ++, C #, Python, JavaScript, Ruby ...
Respuestas:
Tal vez. El compilador podría decidir "oye, esta función solo se llama unas pocas veces, y se supone que debo optimizar la velocidad, así que simplemente alinearé esta función". Esencialmente, el compilador reemplazará la llamada a la función con el cuerpo de la función. Por ejemplo, el código fuente se vería así.
El compilador decide en línea
DoSomethingElse
, y el código se convierte enCuando las funciones no están en línea, sí, hay un golpe de rendimiento para realizar una llamada a la función. Sin embargo, es un golpe tan minúsculo que solo el código de rendimiento extremadamente alto se preocupará por las llamadas a funciones. Y en ese tipo de proyectos, el código generalmente se escribe en ensamblador.
Las llamadas a funciones (dependiendo de la plataforma) generalmente implican unos 10 segundos de instrucciones, y eso incluye guardar / restaurar la pila. Algunas llamadas a funciones consisten en instrucciones de salto y retorno.
Pero hay otras cosas que podrían afectar el rendimiento de las llamadas de función. Es posible que la función que se llama no se cargue en la memoria caché del procesador, lo que provoca una pérdida de memoria caché y obliga al controlador de memoria a tomar la función de la RAM principal. Esto puede causar un gran éxito para el rendimiento.
En pocas palabras: las llamadas a funciones pueden o no afectar el rendimiento. La única forma de saberlo es perfilando su código. No intente adivinar dónde están los puntos de código lentos, porque el compilador y el hardware tienen algunos trucos increíbles bajo la manga. Perfile el código para obtener la ubicación de los puntos lentos.
fuente
Esta es una cuestión de implementación del compilador o del tiempo de ejecución (y sus opciones) y no se puede decir con certeza.
Dentro de C y C ++, algunos compiladores alinearán las llamadas en función de la configuración de optimización; esto se puede ver trivialmente al examinar el ensamblaje generado cuando se miran herramientas como https://gcc.godbolt.org/
Otros lenguajes, como Java, tienen esto como parte del tiempo de ejecución. Esto es parte del JIT y se detalla en esta pregunta SO . En particular, mire las opciones de JVM para HotSpot
Entonces sí, el compilador HotSpot JIT incorporará métodos que cumplan ciertos criterios.
El impacto de esto es difícil de determinar ya que cada JVM (o compilador) puede hacer las cosas de manera diferente y tratar de responder con el trazo amplio de un lenguaje es casi seguro que está mal. El impacto solo se puede determinar de manera adecuada perfilando el código en el entorno de ejecución apropiado y examinando el resultado compilado.
Esto puede verse como un enfoque equivocado con CPython no alineado, pero Jython (Python ejecutándose en la JVM) tiene algunas llamadas en línea. Del mismo modo, MRI Ruby no se alinea mientras que JRuby sí, y ruby2c, que es un transpilador para ruby en C ... que luego podría estar en línea o no, dependiendo de las opciones del compilador C que se compiló.
Los idiomas no están en línea. Las implementaciones pueden .
fuente
Estás buscando rendimiento en el lugar equivocado. El problema con las llamadas a funciones no es que cuesten mucho. Hay otro problema Las llamadas a funciones podrían ser absolutamente gratuitas, y aún tendría este otro problema.
Es que una función es como una tarjeta de crédito. Como puede usarlo fácilmente, tiende a usarlo más de lo que debería. Supongamos que lo llamas 20% más de lo que necesitas. Entonces, el software grande típico contiene varias capas, cada función de llamada en la capa a continuación, por lo que el factor de 1.2 puede ser compuesto por el número de capas. (Por ejemplo, si hay cinco capas y cada capa tiene un factor de desaceleración de 1.2, el factor de desaceleración compuesto es 1.2 ^ 5 o 2.5.) Esta es solo una forma de pensarlo.
Esto no significa que deba evitar las llamadas a funciones. Lo que significa es que, cuando el código está en funcionamiento, debe saber cómo encontrar y eliminar el desperdicio. Hay muchos consejos excelentes sobre cómo hacer esto en los sitios de stackexchange. Esto le da una de mis contribuciones.
AGREGADO: Pequeño ejemplo. Una vez trabajé en un equipo en software de fábrica que rastreaba una serie de órdenes de trabajo o "trabajos". Había una función
JobDone(idJob)
que podría decir si se realizó un trabajo. Se realizó un trabajo cuando se realizaron todas sus subtareas, y cada una de ellas se realizó cuando se realizaron todas sus suboperaciones. Todas estas cosas fueron rastreadas en una base de datos relacional. Una sola llamada a otra función podría extraer toda esa información,JobDone
llamada esa otra función, ver si el trabajo estaba hecho y tirar el resto. Entonces la gente podría escribir fácilmente código como este:o
¿Ve el punto? La función era tan "poderosa" y fácil de llamar que se llamaba demasiado. Entonces, el problema de rendimiento no eran las instrucciones que entraban y salían de la función. Era que tenía que haber una forma más directa de saber si se hacían trabajos. Nuevamente, este código podría haberse incrustado en miles de líneas de código inocente. Intentar arreglarlo con anticipación es lo que todos intentan hacer, pero es como tratar de lanzar dardos en una habitación oscura. En cambio, lo que necesita es ponerlo en funcionamiento y luego dejar que el "código lento" le diga qué es, simplemente tomándose el tiempo. Para eso uso pausas aleatorias .
fuente
Creo que realmente depende del idioma y de la función. Si bien los compiladores c y c ++ pueden incorporar muchas funciones, este no es el caso de Python o Java.
Si bien no conozco los detalles específicos de Java (excepto que todos los métodos son virtuales, pero le sugiero que verifique mejor la documentación), en Python estoy seguro de que no hay en línea, no hay optimización de recursión de cola y las llamadas a funciones son bastante caras.
Las funciones de Python son básicamente objetos ejecutables (y de hecho también puede definir el método call () para hacer que una instancia de objeto sea una función). Esto significa que hay bastante sobrecarga al llamarlos ...
PERO
cuando define variables dentro de funciones, el intérprete usa LOADFAST en lugar de la instrucción LOAD normal en el código de bytes, haciendo que su código sea más rápido ...
Otra cosa es que cuando define un objeto invocable, son posibles patrones como la memorización y pueden acelerar mucho su cálculo (a costa de usar más memoria). Básicamente siempre es una compensación. El costo de las llamadas de función también depende de los parámetros, porque determinan la cantidad de material que realmente tiene que copiar en la pila (por lo tanto, en c / c ++ es una práctica común pasar parámetros grandes como estructuras por punteros / referencia en lugar de por valor).
Creo que su pregunta es en la práctica demasiado amplia para ser respondida completamente en stackexchange.
Lo que le sugiero que haga es comenzar con un idioma y estudiar la documentación avanzada para comprender cómo las llamadas de función son implementadas por ese lenguaje específico.
Te sorprenderá la cantidad de cosas que aprenderás en este proceso.
Si tiene un problema específico, realice mediciones / perfiles y decida el clima, es mejor crear una función o copiar / pegar el código equivalente.
Si hace una pregunta más específica, creo que sería más fácil obtener una respuesta más específica.
fuente
Medí la sobrecarga de las llamadas directas y virtuales a la función C ++ en el Xenon PowerPC hace algún tiempo .
Las funciones en cuestión tenían un único parámetro y un único retorno, por lo que el paso de parámetros se produjo en los registros.
En pocas palabras, la sobrecarga de una llamada de función directa (no virtual) fue de aproximadamente 5,5 nanosegundos, o 18 ciclos de reloj, en comparación con una llamada de función en línea. La sobrecarga de una llamada de función virtual fue de 13,2 nanosegundos, o 42 ciclos de reloj, en comparación con en línea.
Es probable que estos tiempos sean diferentes en diferentes familias de procesadores. Mi código de prueba está aquí ; puedes ejecutar el mismo experimento en tu hardware. Use un temporizador de alta precisión como rdtsc para su implementación de CFastTimer; system time () no es lo suficientemente preciso.
fuente