2010-05-26 20 views
5

Tengo este ciclo escrito en C++, que compila con MSVC2010 tarda mucho tiempo en ejecutarse. (300ms)rendimiento extraño en C++ (VC 2010)

for (int i=0; i<h; i++) { 
    for (int j=0; j<w; j++) { 
     if (buf[i*w+j] > 0) { 
      const int sy = max(0, i - hr); 
      const int ey = min(h, i + hr + 1); 
      const int sx = max(0, j - hr); 
      const int ex = min(w, j + hr + 1); 
      float val = 0; 
      for (int k=sy; k < ey; k++) { 
       for (int m=sx; m < ex; m++) { 
        val += original[k*w + m] * ds[k - i + hr][m - j + hr]; 
       } 
      } 
      heat_map[i*w + j] = val; 
     } 
    } 
} 

que parecía un poco extraño para mí, así que hice algunas pruebas y luego cambió unos pocos bits de inline montaje: (en concreto, el código que resume "val")

for (int i=0; i<h; i++) { 
    for (int j=0; j<w; j++) { 
     if (buf[i*w+j] > 0) { 
      const int sy = max(0, i - hr); 
      const int ey = min(h, i + hr + 1); 
      const int sx = max(0, j - hr); 
      const int ex = min(w, j + hr + 1); 
      __asm { 
       fldz 
      } 
      for (int k=sy; k < ey; k++) { 
       for (int m=sx; m < ex; m++) { 
        float val = original[k*w + m] * ds[k - i + hr][m - j + hr]; 
        __asm { 
         fld val 
         fadd 
        } 
       } 
      } 
      float val1; 
      __asm { 
       fstp val1 
      } 
      heat_map[i*w + j] = val1; 
     } 
    } 
} 

Ahora funciona en la mitad del tiempo, 150 ms. Hace exactamente lo mismo, pero ¿por qué es dos veces más rápido? En ambos casos, se ejecutó en modo de lanzamiento con optimizaciones activadas. ¿Estoy haciendo algo mal en mi código C++ original?

+3

¿Has intentado comparar el código ensamblador generado en ambos casos ... –

Respuesta

5

Le sugiero que pruebe diferentes modelos de cálculo de punto flotante apoyados por el compilador - precise, strict o fast (véase /fp opción) - con el código original antes de hacer ninguna conclusión. Sospecho que su código original fue compilado con un modelo de coma flotante demasiado restrictivo (no seguido por su ensamblado en la segunda versión del código), por lo que el original es mucho más lento.

En otras palabras, si el modelo original era de hecho demasiado restrictivo, entonces simplemente estaba comparando manzanas con naranjas. Las dos versiones en realidad no hicieron lo mismo, aunque a primera vista parezca así.

Tenga en cuenta, por ejemplo, que en la primera versión del código, la suma intermedia se acumula en un valor de float. Si se compiló con el modelo precise, los resultados intermedios se deberían redondear a la precisión del tipo float, incluso si la variable val se optimizó y en su lugar se utilizó el registro FPU interno. En su código de ensamblado, no se molesta en redondear el resultado acumulado, que es lo que podría haber contribuido a su mejor rendimiento.

Sugiero que recopile ambas versiones del código en el modo /fp:fast y vea cómo se comparan sus rendimientos en ese caso.

+0

Gracias!He ejecutado mi código original en modo rápido y ahora se ejecuta en 80 ms, mientras que la segunda versión todavía se ejecuta en 150 ms en modo rápido, así que supongo que el compilador aún sabe mejor :) He encontrado estos # pragma para MSVC para alternar la precisión de flotación por función (no funciona dentro de las funciones): float_control #pragma (precisa, apagado, empuje) ... código aquí ... #pragma float_control (pop) Pero más específicamente: http://msdn.microsoft.com/en-us/library/45ec64h6(VS.80).aspx – raicuandi

3

Algunas cosas para visitar:

  • Es necesario comprobar que en realidad es es el mismo código. Como en, ¿sus instrucciones de ensamblaje en línea son exactamente las mismas que las generadas por el compilador? Puedo ver tres posibles diferencias (posibles porque pueden estar optimizadas). La primera es la configuración inicial de val a cero, la segunda es la variable adicional val1 (improbable ya que lo más probable es que cambie la constante resta del puntero de pila), la tercera es que su versión de ensamblado en línea no puede poner los resultados provisionales de nuevo en val.

  • Debe asegurarse de que el espacio de muestra sea grande. No mencionó si había realizado solo una ejecución de cada versión o cien ejecuciones, pero, mientras más ejecuciones, mejor, para eliminar el efecto de "ruido" en sus estadísticas.

  • Una medida aún mejor sería el tiempo de CPU en lugar del tiempo transcurrido. El tiempo transcurrido está sujeto a cambios ambientales (como su comprobador de virus o uno de sus servicios que decida hacer algo en el momento de la prueba). El espacio de muestra grande lo aliviará, pero no necesariamente lo resolverá.

Cuestiones relacionadas