2010-04-18 24 views
14
  • ¿Qué es más rápido: punteros de función o interruptor?

La sentencia switch tendría alrededor de 30 case s, que consta de enteros sin signo enumarated de 0 a 30.C++ Punteros de función vs Switch

que podía hacer lo siguiente:

class myType 
{ 
    FunctionEnum func; 
    string argv[123]; 
    int someOtherValue; 
}; 
// In another file: 
myType current; 
// Iterate through a vector containing lots of myTypes 
// ... for (i=0; i < myVecSize; i ++) 
    switch (current.func) 
    { 
      case 1: 
      //... 
      break; 
      // ........ 
      case 30: 
      // blah 
      break; 
    } 

Y Tras pasar por el detector con func cada vez. Lo bueno del cambio también sería que mi código está más organizado que con 30 funciones.

O podría hacer eso (no tan seguro con eso):

class myType 
{ 
    myReturnType (*func)(int all, int of, int my, int args); 
    string argv[123]; 
    int someOtherValue; 
}; 

que tendría 30 funciones diferentes a continuación, al principio un puntero a uno de ellos se le asigna a myType.

  • ¿Qué es probablemente más rápido: cambiar la instrucción o el puntero a la función?

Llamadas por segundo: alrededor de 10 millones. No puedo simplemente probarlo, eso requeriría que lo reescribiera todo. Actualmente usando el interruptor.

Estoy construyendo un intérprete que quiero ser más rápido que Python & Ruby: ¡cada ciclo de reloj es importante!

+0

¿Quieres decir 'myReturnType (* func)();'? – kennytm

+0

@KennyTM: lo olvidé, sí. – Rawr

Respuesta

15

Las instrucciones de cambio se implementan típicamente con una tabla de salto. Creo que la asamblea puede reducirse a una sola instrucción, lo que lo haría bastante rápido.

La única manera de estar seguro es intentarlo de ambas formas. Si no puede modificar su código existente, ¿por qué no simplemente crea una aplicación de prueba y la prueba allí?

+0

A menos que su compilador realice un trabajo realmente malo, el cambio será al menos tan rápido como los indicadores de función.El uso de indicadores de función simplemente selecciona una estrategia para implementar el switch, que probablemente sea la mejor y lo que el compilador elegiría de todos modos, pero con la sobrecarga del paso de parámetros. El compilador podría ver una mejor manera, esp. si alguno de los cuerpos de 'caso' tiene algún código en común. Me sentiría cómodo recomendando el OP, simplemente usaré 'switch', y no perderé el tiempo probando en ambos sentidos. Es seguro asumir que el cambio probablemente se compilará con un buen código. Si se trata de un punto de acceso público en la creación de perfiles, verifique el asm. –

6

Creo que la diferencia es insignificante en la mayoría de los casos - su caso podría ser una excepción. ¿Por qué no juntas un prototipo simple de aplicación para medir explícitamente el rendimiento de cada solución? Supongo que no tomaría más de una hora de trabajo ...

Sin embargo, piense también en la claridad y facilidad de mantenimiento del código. En mi humilde opinión, es obvio que la solución del puntero a la función gana indiscutiblemente a este respecto.

Actualización: Tenga en cuenta también que incluso si una solución es, digamos, dos veces más rápida que la otra, no justifica necesariamente la reescritura de su código. Primero debe perfilar su aplicación para determinar qué parte del tiempo de ejecución realmente se gasta en esos conmutadores. Si es el 50% del tiempo total, hay una razón para optimizarlo. Si solo es un par de porcentajes, optimizarlo sería una pérdida de esfuerzo.

+0

Las cajas del interruptor de solenoide tienen solo 10-20 líneas de longitud. Las funciones realmente no serían más legibles. Tomaría más de una hora escribir esto, probablemente un día; no tengo tanto tiempo. Mi pregunta realmente no depende de la arquitectura de mi aplicación: solo es un puntero de función versus un rendimiento de conmutación en el nivel más bajo. – Rawr

+0

Tristemente, no me dijiste nada nuevo: debería escribir dos versiones de mi aplicación para probarlo, imposible, y un prototipo solo no reflejará la versión final del mismo tampoco. – Rawr

+1

Me puede faltar algo completamente, pero en mi opinión la aplicación de prueba necesitaría alrededor de 3 clases: una implementación de un método de clase único con un interruptor, otra de las mismas usando punteros a funciones y una clase de prueba (o incluso una 'principal' método) que ejecuta cada método 10 millones de veces y mide el tiempo de ejecución. –

0

Las declaraciones de conmutación de ese tamaño en C++ son generalmente sistémicas de una necesidad para una mejor organización de la clase y el uso del polimorfismo. ¿Estás seguro de que no hay forma de hacer tu código para que el compilador maneje la función jumping/enumarations por ti?

Pero para responder a la pregunta en sí, las sentencias de conmutación generalmente se compilan de todos modos en las listas de salto de la tabla de funciones. Puede verificar la salida del ensamblador para confirmar si el suyo es o no. Por lo tanto, probablemente sea 6 de una, media docena de otra para este caso.

2

Péter Török tiene la idea correcta de probar ambos y cronometrarlos. Puede que no te guste, pero desafortunadamente esta es la realidad de la situación. El canto de "optimización prematura" ocurre por una razón.

Siempre estoy a favor de usar las mejores prácticas de rendimiento desde el principio, siempre y cuando no sacrifique la claridad. Pero en este tipo de caso no es una victoria clara para ninguna de las opciones que mencionaste. En la mayoría de los casos, este tipo de cambio menor no tendrá ningún efecto mensurable. Habrá algunos cuellos de botella importantes que rigen por completo la velocidad de todo el sistema. En las computadoras modernas algunas instrucciones aquí y allá serán básicamente invisibles porque el sistema está bloqueado por falta de memoria caché o por puestos de tuberías y eso puede ser difícil de predecir. Por ejemplo, en su caso, una única falla de caché adicional probablemente decidirá qué método sería más lento.

La solución del mundo real es evaluar el rendimiento de todo el sistema con un generador de perfiles. Eso le mostrará dónde están sus "puntos calientes" en el código. El siguiente paso habitual es realizar cambios algorítmicos para reducir la necesidad de tantas llamadas en el código de acceso, ya sea a través de mejores algoritmos o mediante el almacenamiento en caché. Solo si una sección muy pequeña de código está iluminando el generador de perfiles, vale la pena bajar a pequeñas micro-optimizaciones como esta. En ese punto, debes probar diferentes cosas y probar el efecto sobre la velocidad. Sin medir el efecto de los cambios, es probable que empeore aún más, incluso para un experto.

Dicho todo esto, supongo que las llamadas a funciones en su caso pueden ser muy ligeramente más rápidas si tienen muy pocos parámetros, especialmente si el cuerpo de cada caso sería grande. Si la instrucción switch no utiliza una tabla de salto, es probable que sea más lenta. Pero probablemente variaría mucho según el compilador y la arquitectura de la máquina, por lo que no dedicaría mucho tiempo a ello, a menos que tenga pruebas sólidas más tarde de que es un cuello de botella para su sistema. La programación se trata tanto de volver a factorizar como de escribir código nuevo.

+0

Buenos comentarios, Alan, pero el concepto de "punto caliente" es uno que me preocupa. Parece que proviene de la vieja idea de que si se gasta demasiado tiempo, la única forma de que se pueda gastar en exceso es registrando demasiado tiempo en ciertos lugares que deben cambiarse. Es increíblemente fácil engañar a cualquier generador de perfiles basado en ese concepto, y los gráficos de llamadas no son mucho mejores. http://stackoverflow.com/questions/1777556/alternatives-to-gprof/1779343#1779343 –

0

Escribir intérpretes es divertido, ¿no? Puedo adivinar que el puntero a la función puede ser más rápido, pero cuando está optimizando, las conjeturas no le llevarán muy lejos.

Si realmente quieres chupar cada ciclo de esa bestia, te llevará varios pases, y las suposiciones no te dirán qué arreglar. Here's an example of what I mean.