2012-06-23 31 views
40

Por un lado, leo u oigo que "las llamadas a funciones son costosas" y que afectan la eficiencia (por ejemplo, on Nicholas Zakas' Google tech talk)."Las llamadas a funciones son costosas" vs. "Mantener funciones pequeñas"

Por otro lado, sin embargo, parece aceptado que las funciones/métodos se mantengan cortas y solo realicen una tarea, como generalmente se acepta en here.

¿Me falta algo aquí, o estos dos consejos no son contrarios entre sí? ¿Hay alguna regla empírica que le permita a uno mantener un equilibrio Zen-like?

+1

No he visto esa charla, pero si hablamos de micro-optimización, simplemente haga lo que le resulte más cómodo. Si te gusta funcional, entonces ve con él. Si eres una biblioteca de clase más grande también está bien. No creo que haga una gran diferencia para realmente tomar uno sobre el otro. De todos modos, pueden surgir otros problemas en diferentes situaciones, por lo que se compensan entre sí. – elclanrs

+4

Lo que estás preguntando es esencialmente "cómo escribir un buen programa", uno que a) es legible, pero b) funciona bien. No hay una receta definitiva, es por eso que los programadores todavía tenemos nuestro pan y mantequilla. – georg

+0

Gracias a todos. – Nick

Respuesta

36

La regla general que se aplica a todos los idiomas es: mantener las funciones (métodos, procedimientos) lo más pequeñas posible. Cuando agrega una denominación adecuada, obtiene un código fácil de leer y legible donde puede enfocar fácilmente la imagen general y desglosar para obtener detalles interesantes. Con un método enorme siempre estás mirando los detalles y el panorama está oculto.

Esta regla se aplica específicamente a los lenguajes inteligentes y compiladores que pueden hacer optimizaciones sofisticadas como inlining o descubrir qué métodos no son realmente virtuales, por lo que no es necesario el envío doble.

Volver a JavaScript: esto depende en gran medida del motor de JavaScript. En algunos casos esperaría un motor decente para la función en línea, evitando el costo de ejecución, especialmente en circuitos cerrados. Sin embargo, a menos que tenga un problema de rendimiento, prefiera funciones más pequeñas. La legibilidad es mucho más importante.

+16

+1 Primero escriba para leer, luego perfile su código si tiene problemas de eficiencia y optimice los cuellos de botella. – Darthfett

11

En un mundo perfecto, donde no hay errores (porque el código simplemente se arregla mágicamente), y los requisitos están congelados desde el primer día, es posible vivir con enormes funciones omnipotentes.

Pero en este mundo resulta ser demasiado caro, y no solo en términos de 'hombre-mes'. Nicholas Zakas escribió a brilliant article describiendo la mayoría de los desafíos que los desarrolladores de software enfrentan actualmente.

La transición puede parecer algo artificial, pero mi punto es que el enfoque 'una función - una tarea' es mucho más fácil de mantener y flexible - en otras palabras, es lo que hace felices tanto a los desarrolladores como a los clientes.

No significa, sin embargo, que no se esfuerce por utilizar la menor cantidad posible de llamadas a funciones: simplemente recuerde que no es una prioridad.

+0

Disfruté mucho el artículo :) – Nick

+0

¡Esto es imprescindible! Con mucho, el artículo más intrigante y profundo que he leído sobre el tema. –

4

Mi regla de oro es que es hora de dividir una función en piezas más pequeñas si es más que una pantalla llena de líneas, aunque muchas de mis funciones simplemente terminan algo más pequeñas sin ser "artificialmente" división. Y generalmente dejo suficiente espacio en blanco que incluso una pantalla completa no es realmente una gran cantidad de código.

Intento que cada función haga solo una tarea, pero luego una tarea podría ser "volver a pintar la pantalla", lo que implicaría una serie de subtareas implementadas en funciones separadas que a su vez podrían tener sus propias subtareas en funciones separadas

Comenzando con lo que parece natural (para mí) para la legibilidad (y por lo tanto la facilidad de mantenimiento) No me preocupo porque las llamadas a las funciones sean caras a menos que un código en particular tenga un mal rendimiento cuando se prueban - entonces miro hacer que las cosas vuelvan a estar en línea (particularmente en bucles, comenzando con bucles anidados). A pesar de haber dicho eso, a veces uno simplemente sabe que un código en particular no va a funcionar bien y lo reescribe antes de llegar al punto de prueba ...

Evitaría la "optimización prematura", particularmente con los lenguajes que usan compiladores inteligentes que pueden hacer esas mismas optimizaciones detrás de escena. Cuando comencé con C#, me dijeron que dividir código en funciones más pequeñas puede ser menos costoso en tiempo de ejecución debido a la forma en que funciona el compilador JIT.

Volviendo a la regla de una pantalla completa, en JavaScript es común tener funciones anidadas (debido a la forma en que funcionan los cierres JS), y esto puede hacer que la función contenedora sea más larga de lo que me gustaría si estuviera usando otro idioma, por lo que a veces el resultado final es un compromiso.

0

llamadas a funciones son siempre caro (especialmente en los ciclos) y procesos en línea no sucede tan a menudo como usted puede pensar

El motor V8 que se incluye con Node.js (cualquier versión) se supone que deben hacer inlining ampliamente pero en términos prácticos, esta capacidad está muy restringida.

La siguiente (trivial) fragmento de código prueba mi punto (Nodo 4.2.1 en Win10x64)

"use strict"; 

var a = function(val) { 
    return val+1; 
} 

var b = function(val) { 
    return val-1; 
} 

var c = function(val) { 
    return val*2 
} 

var time = process.hrtime(); 

for(var i = 0; i < 100000000; i++) { 
    a(b(c(100))); 
} 

console.log("Elapsed time function calls: %j",process.hrtime(time)[1]/1e6); 

time = process.hrtime(); 
var tmp; 
for(var i = 0; i < 100000000; i++) { 
    tmp = 100*2 + 1 - 1; 
} 

console.log("Elapsed time NO function calls: %j",process.hrtime(time)[1]/1e6); 

Resultados

Elapsed time function calls: 127.332373 
Elapsed time NO function calls: 104.917725 

+/- 20% caída de rendimiento

Uno habría esperado que el compilador V8 JIT alinee esas funciones, pero en realidad a, b o c podrían llamarse a otro lugar en la c ode y no son buenos candidatos para el enfoque de alineación de fruta colgante baja que obtiene con V8

He visto un montón de código (Java, Php, Node.js) con bajo rendimiento en la producción debido a un método o llamada de función abuso: si escribe su código de estilo Matryoshka, el rendimiento del tiempo de ejecución se se degradará linealmente con el tamaño de la pila de invocación, a pesar de que se ve conceptualmente limpio.

1

Para todos: Esto tiene más la sensación de un "comentario". Admitido. Elegí usar el espacio de una "respuesta". Por favor tolera.

@StefanoFratini: Por favor tome mi nota como basándose en su trabajo. Quiero evitar ser crítico.

Aquí hay dos maneras de mejorar aún más el código en tu post:

  • Use las dos mitades de la tupla que viene de process.hrtime(). Devuelve una matriz [segundos, nanosegundos]. Su código usa la parte nanosegundos de la tupla (elemento 1) y no puedo encontrar que use la parte de segundos (elemento 0).
  • Sea explícito acerca de las unidades.

¿Puedo encontrar mi brazalete? No sé. Aquí hay un desarrollo del código de Stephano. Tiene defectos; No me sorprendería si alguien me lo cuenta. Y eso estaría bien.

"use strict"; 

var a = function(val) { return val+1; } 

var b = function(val) { return val-1; } 

var c = function(val) { return val*2 } 

var time = process.hrtime(); 

var reps = 100000000 

for(var i = 0; i < reps; i++) { a(b(c(100))); } 

time = process.hrtime(time) 
let timeWith = time[0] + time[1]/1000000000 
console.log(`Elapsed time with function calls: ${ timeWith } seconds`); 

time = process.hrtime(); 
var tmp; 
for(var i = 0; i < reps; i++) { tmp = 100*2 - 1 + 1; } 

time = process.hrtime(time) 
let timeWithout = time[0] + time[1]/1000000000 
console.log(`Elapsed time without function calls: ${ timeWithout } seconds`); 

let percentWith = 100 * timeWith/timeWithout 
console.log(`\nThe time with function calls is ${ percentWith } percent\n` + 
    `of time without function calls.`) 

console.log(`\nEach repetition with a function call used roughly ` + 
     `${ timeWith/reps } seconds.` + 
    `\nEach repetition without a function call used roughly ` + 
     `${ timeWithout/reps } seconds.`) 

Es claramente un descendiente del código de Stephano. Los resultados son bastante diferentes.

Elapsed time with function calls: 4.671479346 seconds 
Elapsed time without function calls: 0.503176535 seconds 

The time with function calls is 928.397693664312 percent 
of time without function calls. 

Each repetition with a function call used roughly 4.671479346e-8 seconds. 
Each repetition without a function call used roughly 5.0317653500000005e-9 seconds. 

Al igual que Stephano, utilicé Win10 and Node (v6.2.0 para mí).

que reconocen los argumentos que

  • "Para ponerlo en perspectiva, en un nanosegundo (una mil millonésima, 1e-9), la luz viaja aproximadamente 12 pulgadas."
  • "Estamos hablando solo de pequeños números de nanosegundos (47 a 5), ​​entonces ¿a quién le importan los porcentajes?"
  • "Algún algoritmo genera millones de llamadas a función cada segundo, por lo que se suma a ellas".
  • "La mayoría de nosotros, los desarrolladores, no trabajamos con esos algoritmos, por lo que preocuparse por la cantidad de llamadas a funciones es contraproducente para la mayoría de nosotros".

Voy a colgar mi sombrero en el argumento económico: Mi computadora y la anterior cada uno cuestan menos de $ 400 (EE.UU.). Si un ingeniero de software gana entre $ 90 y $ 130 por hora, el valor de su tiempo para sus jefes es en una proporción de una computadora como la mía a tres o cuatro horas de trabajo. En ese entorno:

¿Cómo se compara eso con los dólares por hora que una empresa pierde cuando el software que necesita deja de funcionar?

¿Cómo se compara eso con la pérdida de la buena voluntad y el prestigio cuando un cliente que paga temporalmente no puede usar el software empaquetado producido por un socio comercial?

Hay muchas otras preguntas similares. Los omitiré.

Como interpreto las respuestas: La legibilidad y la capacidad de mantenimiento reinan sobre el rendimiento de la computadora. ¿Mi consejo? Escriba la primera versión de su código en consecuencia. Muchas personas a las que respeto dicen que las funciones cortas ayudan.

Una vez que termine su código y no le gusta el rendimiento, encuentre los puntos de estrangulación. Muchas personas que respeto dicen que esos puntos son nunca donde los habría esperado. Trabaja cuando los conozcas.

Por lo tanto, ambos lados tienen razón. Algunos.

Yo? Supongo que me iré a alguna parte. Dos centavos.

Cuestiones relacionadas