2010-09-08 6 views
3

Pouvez-vous imaginer un moyen d'optimiser ce code? Il est destiné à exécuter dans un processeur ARMv7 (Iphone 3GS):Optimisation du code C++ pour la performance

4.0% inline float BoxIntegral(IplImage *img, int row, int col, int rows, int cols) 
     { 
0.7% float *data = (float *) img->imageData; 
1.4% int step = img->widthStep/sizeof(float); 

     // The subtraction by one for row/col is because row/col is inclusive. 
1.1% int r1 = std::min(row,   img->height) - 1; 
1.0% int c1 = std::min(col,   img->width) - 1; 
2.7% int r2 = std::min(row + rows, img->height) - 1; 
3.7% int c2 = std::min(col + cols, img->width) - 1; 

     float A(0.0f), B(0.0f), C(0.0f), D(0.0f); 
8.5% if (r1 >= 0 && c1 >= 0) A = data[r1 * step + c1]; 
11.7% if (r1 >= 0 && c2 >= 0) B = data[r1 * step + c2]; 
7.6% if (r2 >= 0 && c1 >= 0) C = data[r2 * step + c1]; 
9.2% if (r2 >= 0 && c2 >= 0) D = data[r2 * step + c2]; 

21.9% return std::max(0.f, A - B - C + D); 
3.8% } 

Tout ce code est tiré de la bibliothèque OpenSURF. Voici le contexte de la fonction (certaines personnes demandaient le contexte):

//! Calculate DoH responses for supplied layer 
void FastHessian::buildResponseLayer(ResponseLayer *rl) 
{ 
    float *responses = rl->responses;   // response storage 
    unsigned char *laplacian = rl->laplacian; // laplacian sign storage 
    int step = rl->step;      // step size for this filter 
    int b = (rl->filter - 1) * 0.5 + 1;   // border for this filter 
    int l = rl->filter/3;     // lobe for this filter (filter size/3) 
    int w = rl->filter;      // filter size 
    float inverse_area = 1.f/(w*w);   // normalisation factor 
    float Dxx, Dyy, Dxy; 

    for(int r, c, ar = 0, index = 0; ar < rl->height; ++ar) 
    { 
    for(int ac = 0; ac < rl->width; ++ac, index++) 
    { 
     // get the image coordinates 
     r = ar * step; 
     c = ac * step; 

     // Compute response components 
     Dxx = BoxIntegral(img, r - l + 1, c - b, 2*l - 1, w) 
      - BoxIntegral(img, r - l + 1, c - l * 0.5, 2*l - 1, l)*3; 
     Dyy = BoxIntegral(img, r - b, c - l + 1, w, 2*l - 1) 
      - BoxIntegral(img, r - l * 0.5, c - l + 1, l, 2*l - 1)*3; 
     Dxy = + BoxIntegral(img, r - l, c + 1, l, l) 
      + BoxIntegral(img, r + 1, c - l, l, l) 
      - BoxIntegral(img, r - l, c - l, l, l) 
      - BoxIntegral(img, r + 1, c + 1, l, l); 

     // Normalise the filter responses with respect to their size 
     Dxx *= inverse_area; 
     Dyy *= inverse_area; 
     Dxy *= inverse_area; 

     // Get the determinant of hessian response & laplacian sign 
     responses[index] = (Dxx * Dyy - 0.81f * Dxy * Dxy); 
     laplacian[index] = (Dxx + Dyy >= 0 ? 1 : 0); 

#ifdef RL_DEBUG 
     // create list of the image coords for each response 
     rl->coords.push_back(std::make_pair<int,int>(r,c)); 
#endif 
    } 
    } 
} 

Quelques questions: Est-ce
une bonne idée que la fonction est en ligne? L'utilisation de l'assemblage en ligne permettrait-elle une accélération significative?

+5

La seule bonne réponse à vos deux questions est la suivante: Mesurer. – dirkgently

+0

Oui, jetez un oeil aux questions récentes C++ - il y en a une sur la vitesse du vecteur par rapport au tableau - le code montre comment utiliser les minuteurs de boost pour le profilage. Vous pouvez également consulter graphics.stanford.edu/~seander/bithacks.html - beaucoup de petits hacks peuvent fournir des méthodes plus rapides. Assemblage en ligne - peut-être - je ne sais pas ce processeur ne peut donc pas dire. –

+0

Pourquoi l'un des r1, r2, c1, c2 serait-il jamais négatif? Ces tests devraient tous être redondants. – phkahler

Répondre

8

Spécialisez-vous pour les bords afin de ne pas avoir à les vérifier dans chaque ligne et colonne. Je suppose que cet appel est dans une boucle imbriquée et s'appelle beaucoup. Cette fonction deviendrait:

inline float BoxIntegralNonEdge(IplImage *img, int row, int col, int rows, int cols) 
{ 
    float *data = (float *) img->imageData; 
    int step = img->widthStep/sizeof(float); 

    // The subtraction by one for row/col is because row/col is inclusive. 
    int r1 = row - 1; 
    int c1 = col - 1; 
    int r2 = row + rows - 1; 
    int c2 = col + cols - 1; 

    float A(data[r1 * step + c1]), B(data[r1 * step + c2]), C(data[r2 * step + c1]), D(data[r2 * step + c2]); 

    return std::max(0.f, A - B - C + D); 
} 

Vous vous débarrasser d'une condition et pour chaque branche min et deux conditionals et une branche pour chaque cas. Vous ne pouvez appeler cette fonction que si vous remplissez déjà les conditions - vérifiez dans l'appelant pour la ligne entière une fois au lieu de chaque pixel.

J'ai écrit quelques conseils pour optimiser le traitement de l'image lorsque vous avez à faire travailler sur chaque pixel:

http://www.atalasoft.com/cs/blogs/loufranco/archive/2006/04/28/9985.aspx

Autres choses du blog:

  1. Vous Recalcul une position dans les données d'image avec 2 multiplies (l'indexation est la multiplication) - vous devriez incrémenter un pointeur. Au lieu de transmettre img, row, row, col et cols, passez des pointeurs vers les pixels exacts à traiter - ce que vous obtenez en incrémentant des pointeurs, pas en les indexant. Si vous ne faites pas ce qui précède, le pas est le même pour tous les pixels, calculez-le dans l'appelant et transmettez-le. Si vous faites 1 et 2, vous n'aurez pas besoin de pas du tout.

1

Il y a quelques endroits pour réutiliser les variables temporaires, mais si elle améliorerait la performance devrait être mesurée comme dirkgently a déclaré:

changement

if (r1 >= 0 && c1 >= 0) A = data[r1 * step + c1]; 
    if (r1 >= 0 && c2 >= 0) B = data[r1 * step + c2]; 
    if (r2 >= 0 && c1 >= 0) C = data[r2 * step + c1]; 
    if (r2 >= 0 && c2 >= 0) D = data[r2 * step + c2]; 

à

if (r1 >= 0) { 
    int r1Step = r1 * step; 
    if (c1 >= 0) A = data[r1Step + c1]; 
    if (c2 >= 0) B = data[r1Step + c2]; 
    } 
    if (r2 >= 0) { 
    int r2Step = r2 * step; 
    if (c1 >= 0) C = data[r2Step + c1]; 
    if (c2 >= 0) D = data[r2Step + c2]; 
    } 

Vous peut finir par faire les multiplications de temp trop souvent au cas où vos déclarations if fournissent rarement vrai.

+0

Si des indicateurs d'optimisation appropriés sont utilisés, cela sera automatiquement pris en compte –

0

Le compilateur gère probablement l'inling automatiquement là où c'est approprié.

Sans aucune connaissance du contexte. Le contrôle if (r1> = 0 & & c1> = 0) est-il nécessaire?

N'est-il pas nécessaire que les paramètres de ligne et de col soient> 0?

float BoxIntegral(IplImage *img, int row, int col, int rows, int cols) 
{ 
    assert(row > 0 && col > 0); 
    float *data = (float*)img->imageData; // Don't use C-style casts 
    int step = img->widthStep/sizeof(float); 

    // Is the min check rly necessary? 
    int r1 = std::min(row,   img->height) - 1; 
    int c1 = std::min(col,   img->width) - 1; 
    int r2 = std::min(row + rows, img->height) - 1; 
    int c2 = std::min(col + cols, img->width) - 1; 

    int r1_step = r1 * step; 
    int r2_step = r2 * step; 

    float A = data[r1_step + c1]; 
    float B = data[r1_step + c2]; 
    float C = data[r2_step + c1]; 
    float D = data[r2_step + c2]; 

    return std::max(0.0f, A - B - C + D); 
} 
0

Je ne sais pas si votre problème se prête à SIMD mais cela pourrait vous permettre d'effectuer plusieurs opérations sur votre image à la fois et vous donner une bonne amélioration de la performance. Je suppose que vous êtes en train d'intégrer et d'optimiser parce que vous effectuez l'opération plusieurs fois. Jetez un oeil à:

  1. http://blogs.arm.com/software-enablement/coding-for-neon-part-1-load-and-stores/
  2. http://blogs.arm.com/software-enablement/coding-for-neon-part-2-dealing-with-leftovers/
  3. http://blogs.arm.com/software-enablement/coding-for-neon-part-3-matrix-multiplication/
  4. http://blogs.arm.com/software-enablement/coding-for-neon-part-4-shifting-left-and-right/

compilateur ont un certain soutien pour Neon si les drapeaux corrects sont activés, mais vous aurez probablement besoin de rouler en sortir par vos propres moyens.

Modifier Pour obtenir le soutien du compilateur pour le néon, vous devez utiliser le drapeau du compilateur -mfpu=neon

+0

Existe-t-il des indicateurs de compilation pour activer explicitement le support Neon? – Diego

+0

@Diego - voir éditer – doron

1

Vous n'êtes pas intéressé par quatre variables A, B, C, D, mais seulement la combinaison A - B - C + D.

Essayez

float result(0.0f); 
if (r1 >= 0 && c1 >= 0) result += data[r1 * step + c1]; 
if (r1 >= 0 && c2 >= 0) result -= data[r1 * step + c2]; 
if (r2 >= 0 && c1 >= 0) result -= data[r2 * step + c1]; 
if (r2 >= 0 && c2 >= 0) result += data[r2 * step + c2]; 

if (result > 0f) return result; 
return 0f; 
+0

'if (résultat> 0f)'. –

+0

@Steve: Vous avez bien sûr raison ... Je me souvenais que la fonction 'std :: max' est utilisée pour établir un minimum, et à partir de là tout mon raisonnement était en arrière. –

+0

+1. Enregistre les registres, bonne idée. – MSalters

0

Certains des exemples dire pour initialiser A, B, C et D directement et sauter l'initialisation avec 0, mais cela est fonctionnellement différent de votre code d'origine d'une certaine façon. Je ferais ceci cependant:

inline float BoxIntegral(IplImage *img, int row, int col, int rows, int cols) { 

    const float *data = (float *) img->imageData; 
    const int step = img->widthStep/sizeof(float); 

    // The subtraction by one for row/col is because row/col is inclusive. 
    const int r1 = std::min(row,   img->height) - 1; 
    const int r2 = std::min(row + rows, img->height) - 1; 
    const int c1 = std::min(col,   img->width) - 1; 
    const int c2 = std::min(col + cols, img->width) - 1; 

    const float A = (r1 >= 0 && c1 >= 0) ? data[r1 * step + c1] : 0.0f; 
    const float B = (r1 >= 0 && c2 >= 0) ? data[r1 * step + c2] : 0.0f; 
    const float C = (r2 >= 0 && c1 >= 0) ? data[r2 * step + c1] : 0.0f; 
    const float D = (r2 >= 0 && c2 >= 0) ? data[r2 * step + c2] : 0.0f; 

    return std::max(0.f, A - B - C + D); 
} 

comme votre code d'origine, cela fera A, B, C et D ont une valeur soit de data[] si la condition est true ou 0.0f si la condition est fausse. Aussi, je voudrais (comme je l'ai montré) utiliser const partout où c'est approprié. Beaucoup de compilateurs ne sont pas en mesure d'améliorer le code beaucoup basé sur const -ness, mais il ne peut certainement pas blesser pour donner au compilateur plus d'informations sur les données sur lesquelles il opère. Enfin, j'ai réorganisé les variables r1/r2/c1/c2 pour encourager la réutilisation de la largeur et de la hauteur récupérées.

De toute évidence, il vous faudrait établir un profil pour déterminer si cela constitue réellement une amélioration.