91 votes

Comment puis-je améliorer les performances grâce à une approche de haut niveau lors de l’implémentation de longues équations en C++

Je suis en train d'élaborer certains d'ingénierie des simulations. Cela implique la mise en œuvre de certaines équations comme cette équation pour calculer le stress dans un caoutchouc comme matériel:

T = (
    mu * (
            pow(l1 * pow(l1 * l2 * l3, -0.1e1 / 0.3e1), a) * a
            * (
                pow(l1 * l2 * l3, -0.1e1 / 0.3e1)
                - l1 * l2 * l3 * pow(l1 * l2 * l3, -0.4e1 / 0.3e1) / 0.3e1
            ) * pow(l1 * l2 * l3, 0.1e1 / 0.3e1) / l1
            - pow(l2 * pow(l1 * l2 * l3, -0.1e1 / 0.3e1), a) * a / l1 / 0.3e1
            - pow(l3 * pow(l1 * l2 * l3, -0.1e1 / 0.3e1), a) * a / l1 / 0.3e1
        ) / a
    + K * (l1 * l2 * l3 - 0.1e1) * l2 * l3
) * N1 / l2 / l3

+ (
    mu * (
        - pow(l1 * pow(l1 * l2 * l3, -0.1e1 / 0.3e1), a) * a / l2 / 0.3e1
        + pow(l2 * pow(l1 * l2 * l3, -0.1e1 / 0.3e1), a) * a
        * (
            pow(l1 * l2 * l3, -0.1e1 / 0.3e1)
            - l1 * l2 * l3 * pow(l1 * l2 * l3, -0.4e1 / 0.3e1) / 0.3e1
        ) * pow(l1 * l2 * l3, 0.1e1 / 0.3e1) / l2
        - pow(l3 * pow(l1 * l2 * l3, -0.1e1 / 0.3e1), a) * a / l2 / 0.3e1
    ) / a
    + K * (l1 * l2 * l3 - 0.1e1) * l1 * l3
) * N2 / l1 / l3

+ (
    mu * (
        - pow(l1 * pow(l1 * l2 * l3, -0.1e1 / 0.3e1), a) * a / l3 / 0.3e1
        - pow(l2 * pow(l1 * l2 * l3, -0.1e1 / 0.3e1), a) * a / l3 / 0.3e1
        + pow(l3 * pow(l1 * l2 * l3, -0.1e1 / 0.3e1), a) * a
        * (
            pow(l1 * l2 * l3, -0.1e1 / 0.3e1)
            - l1 * l2 * l3 * pow(l1 * l2 * l3, -0.4e1 / 0.3e1) / 0.3e1
        ) * pow(l1 * l2 * l3, 0.1e1 / 0.3e1) / l3
    ) / a
+ K * (l1 * l2 * l3 - 0.1e1) * l1 * l2
) * N3 / l1 / l2;

J'ai utiliser Maple pour générer le code C++ pour éviter les erreurs et de gagner du temps fastidieux de l'algèbre). Comme ce code est exécuté des milliers (voire des millions) de fois, la performance est un sujet de préoccupation. Malheureusement, le calcul simplifie jusqu'à présent; les équations sont inévitables.

Quelle démarche dois-je prendre pour optimiser cette mise en œuvre? Je suis à la recherche de stratégies de haut niveau que je devrais être en vigueur lors de la mise en œuvre de ces équations, pas nécessairement des optimisations spécifiques pour l'exemple montré ci-dessus.

Je suis à la compilation à l'aide de g++ avec --enable-optimize=-O3.

Mise à jour:

Je sais qu'il y a beaucoup d'expressions répétées, je suis en utilisant l'hypothèse que le compilateur serait en mesure de gérer ces; mes tests jusqu'à présent suggèrent qu'il fait.

l1, l2, l3, mu, a, K sont tous positifs nombres réels (pas zéro).

J'ai remplacé l1*l2*l3 avec un équivalent de la variable: J. Cela ne aider à améliorer les performances.

Remplacement d' pow(x, 0.1e1/0.3e1) avec cbrt(x) était une bonne suggestion.

Ce sera de fonctionner sur les Processeurs, Dans un avenir proche, ce serait sans doute mieux fonctionner sur les Gpu, mais pour l'instant, cette option n'est pas disponible.

87voto

David Hammen Points 17912

Résumé de la modification

  • Ma réponse originale à cette question simplement noté que le code contenait beaucoup de réplication des calculs et que la plupart des puissances impliquées facteurs de 1/3. Par exemple, pow(x, 0.1e1/0.3e1) est le même que cbrt(x).
  • Mon deuxième édition a été tout simplement faux, et et mon troisième extrapoler sur ce qu'est le mal. C'est ce qui fait peur aux gens de changer l'oracle comme les résultats de symbolique programmes de mathématiques qui commencent par la lettre 'M'. J'ai radié (c'est à dire, grève) ces modifications et les ont poussés vers le bas de la révision actuelle de cette réponse. Cependant, je n'ai pas les supprimer. Je suis humain. Il est facile pour nous de faire une erreur.
  • Mon quatrième édition développé une très compact expression qui représente correctement le tube d'expression dans la question SI les paramètres l1, l2, et l3 sont des nombres réels positifs et si a est un non-zéro nombre réel. (Nous n'avons pas encore entendre parler de l'OP au sujet de la nature spécifique de ces coefficients. Compte tenu de la nature du problème, ce sont des hypothèses raisonnables.)
  • Cette modification vise à répondre à la générique problème de la façon de simplifier ces expressions.

Tout d'abord

J'ai utiliser Maple pour générer le code C++ pour éviter les erreurs.

Maple et Mathematica manquent parfois de l'évidence. Plus important encore, les utilisateurs de Maple et Mathematica parfois faire des erreurs. En substituant "souvent", ou peut-être même "presque toujours", en lieu et place de "parfois, est probablement plus proche de la marque.

Vous pourriez avoir aidé Érable simplifier cette expression en disant cela sur les paramètres en question. Dans l'exemple à portée de main, je soupçonne qu' l1, l2, et l3 sont des nombres réels positifs et qu' a est un non-zéro nombre réel. Si c'est le cas, dites-lui que. Ceux symbolique programmes de mathématiques supposent généralement les quantités à traiter sont complexes. En restreignant le domaine permet au programme de faire des hypothèses qui ne sont pas valides dans les nombres complexes.


Comment simplifier ces grandes messes de symbolique programmes de mathématiques (cette édition)

Symbolique programmes de mathématiques fournissent généralement la capacité à fournir des informations sur les différents paramètres. L'utilisation de cette capacité, en particulier si votre problème implique la division ou de l'exponentiation. Dans l'exemple à portée de main, vous pourriez avoir aidé Érable simplifier cette expression en disant que l1, l2, et l3 sont des nombres réels positifs et qu' a est un non-zéro nombre réel. Si c'est le cas, dites-lui que. Ceux symbolique programmes de mathématiques supposent généralement les quantités à traiter sont complexes. En restreignant le domaine permet au programme de faire des hypothèses telles que axbx=(ab)x. Ce n'est que si a et b sont des nombres réels positifs et si x est réel. Il n'est pas valide dans les nombres complexes.

En fin de compte, ceux symbolique programmes de mathématiques de suivre les algorithmes. Aider le long. Essayez de jouer avec de l'expansion, de la collecte, et de simplifier avant de générer le code. Dans ce cas, vous pourriez avoir rassemblé ces conditions impliquant un facteur de mu et ceux impliquant un facteur d' K. Réduire une expression à sa "forme la plus simple" reste un peu d'un art.

Lorsque vous obtenez une horrible gâchis de code généré, ne pas l'accepter comme une vérité que vous ne devez pas toucher. Essayez de simplifier vous-même. Regardez ce que la symbolique programme de mathématiques était saisie du code généré. Regardez comment j'ai réduit votre expression de quelque chose de beaucoup plus simple et beaucoup plus rapide, et la façon de Walter réponse a pris la mienne en plusieurs étapes supplémentaires. Il n'y a pas de recette magique. Si il y avait une recette magique, Érable serait appliqué et que, compte tenu de la réponse que Walter a donné.


Sur la question spécifique

Vous faites beaucoup de l'addition et de la soustraction dans ce calcul. Vous pouvez obtenir dans le pétrin si vous avez des conditions que près de s'annuler l'un l'autre. Vous perdez beaucoup de CPU si vous avez un terme qui domine sur les autres.

Ensuite, vous perdez beaucoup de CPU en effectuant les calculs qui se répètent. Sauf si vous avez activé l' -ffast-math, ce qui permet au compilateur de briser les règles de la virgule flottante IEEE, le compilateur ne va pas (en fait, ne doit pas) simplifier cette expression pour vous. Il va plutôt faire exactement ce que vous lui avez dit de faire. Au minimum, vous devez calculer l1 * l2 * l3 avant le calcul de ce désordre.

Enfin, vous faites beaucoup d'appels à l' pow, ce qui est extrêmement lent. Notez que plusieurs de ces appels sont de la forme (l1*l2*l3)(1/3). Beaucoup de ces appels à l' pow a pu être effectuées avec un seul appel à l' std::cbrt:

l123 = l1 * l2 * l3;
l123_pow_1_3 = std::cbrt(l123);
l123_pow_4_3 = l123 * l123_pow_1_3;

Avec cela,

  • X * pow(l1 * l2 * l3, 0.1e1 / 0.3e1) devient X * l123_pow_1_3.
  • X * pow(l1 * l2 * l3, -0.1e1 / 0.3e1) devient X / l123_pow_1_3.
  • X * pow(l1 * l2 * l3, 0.4e1 / 0.3e1) devient X * l123_pow_4_3.
  • X * pow(l1 * l2 * l3, -0.4e1 / 0.3e1) devient X / l123_pow_4_3.


Maple ne manquez évidence.
Par exemple, il y a un moyen beaucoup plus facile à écrire

(pow(l1 * l2 * l3, -0.1e1 / 0.3e1) - l1 * l2 * l3 * pow(l1 * l2 * l3, -0.4e1 / 0.3e1) / 0.3e1)

En supposant que l' l1, l2, et l3 sont réels plutôt que des nombres complexes, et que la véritable racine cubique (plutôt que sur le principe racines complexes) sont susceptibles d'être extraites, le ci-dessus se réduit à

2.0/(3.0 * pow(l1 * l2 * l3, 1.0/3.0))

ou

2.0/(3.0 * l123_pow_1_3)

À l'aide de cbrt_l123 au lieu de l123_pow_1_3, le méchant de l'expression dans la question se réduit à

l123 = l1 * l2 * l3; 
cbrt_l123 = cbrt(l123);
T = 
  mu/(3.0*l123)*(  pow(l1/cbrt_l123,a)*(2.0*N1-N2-N3)
                 + pow(l2/cbrt_l123,a)*(2.0*N2-N3-N1)
                 + pow(l3/cbrt_l123,a)*(2.0*N3-N1-N2))
 +K*(l123-1.0)*(N1+N2+N3);

Toujours vérifier deux fois, mais toujours de simplifier ainsi.


Voici quelques unes de mes étapes pour en arriver à la ci-dessus:

// Step 0: Trim all whitespace.
T=(mu*(pow(l1*pow(l1*l2*l3,-0.1e1/0.3e1),a)*a*(pow(l1*l2*l3,-0.1e1/0.3e1)-l1*l2*l3*pow(l1*l2*l3,-0.4e1/0.3e1)/0.3e1)*pow(l1*l2*l3,0.1e1/0.3e1)/l1-pow(l2*pow(l1*l2*l3,-0.1e1/0.3e1),a)*a/l1/0.3e1-pow(l3*pow(l1*l2*l3,-0.1e1/0.3e1),a)*a/l1/0.3e1)/a+K*(l1*l2*l3-0.1e1)*l2*l3)*N1/l2/l3+(mu*(-pow(l1*pow(l1*l2*l3,-0.1e1/0.3e1),a)*a/l2/0.3e1+pow(l2*pow(l1*l2*l3,-0.1e1/0.3e1),a)*a*(pow(l1*l2*l3,-0.1e1/0.3e1)-l1*l2*l3*pow(l1*l2*l3,-0.4e1/0.3e1)/0.3e1)*pow(l1*l2*l3,0.1e1/0.3e1)/l2-pow(l3*pow(l1*l2*l3,-0.1e1/0.3e1),a)*a/l2/0.3e1)/a+K*(l1*l2*l3-0.1e1)*l1*l3)*N2/l1/l3+(mu*(-pow(l1*pow(l1*l2*l3,-0.1e1/0.3e1),a)*a/l3/0.3e1-pow(l2*pow(l1*l2*l3,-0.1e1/0.3e1),a)*a/l3/0.3e1+pow(l3*pow(l1*l2*l3,-0.1e1/0.3e1),a)*a*(pow(l1*l2*l3,-0.1e1/0.3e1)-l1*l2*l3*pow(l1*l2*l3,-0.4e1/0.3e1)/0.3e1)*pow(l1*l2*l3,0.1e1/0.3e1)/l3)/a+K*(l1*l2*l3-0.1e1)*l1*l2)*N3/l1/l2;

// Step 1:
//   l1*l2*l3 -> l123
//   0.1e1 -> 1.0
//   0.4e1 -> 4.0
//   0.3e1 -> 3
l123 = l1 * l2 * l3;
T=(mu*(pow(l1*pow(l123,-1.0/3),a)*a*(pow(l123,-1.0/3)-l123*pow(l123,-4.0/3)/3)*pow(l123,1.0/3)/l1-pow(l2*pow(l123,-1.0/3),a)*a/l1/3-pow(l3*pow(l123,-1.0/3),a)*a/l1/3)/a+K*(l123-1.0)*l2*l3)*N1/l2/l3+(mu*(-pow(l1*pow(l123,-1.0/3),a)*a/l2/3+pow(l2*pow(l123,-1.0/3),a)*a*(pow(l123,-1.0/3)-l123*pow(l123,-4.0/3)/3)*pow(l123,1.0/3)/l2-pow(l3*pow(l123,-1.0/3),a)*a/l2/3)/a+K*(l123-1.0)*l1*l3)*N2/l1/l3+(mu*(-pow(l1*pow(l123,-1.0/3),a)*a/l3/3-pow(l2*pow(l123,-1.0/3),a)*a/l3/3+pow(l3*pow(l123,-1.0/3),a)*a*(pow(l123,-1.0/3)-l123*pow(l123,-4.0/3)/3)*pow(l123,1.0/3)/l3)/a+K*(l123-1.0)*l1*l2)*N3/l1/l2;

// Step 2:
//   pow(l123,1.0/3) -> cbrt_l123
//   l123*pow(l123,-4.0/3) -> pow(l123,-1.0/3)
//   (pow(l123,-1.0/3)-pow(l123,-1.0/3)/3) -> 2.0/(3.0*cbrt_l123)
//   *pow(l123,-1.0/3) -> /cbrt_l123
l123 = l1 * l2 * l3;
cbrt_l123 = cbrt(l123);
T=(mu*(pow(l1/cbrt_l123,a)*a*2.0/(3.0*cbrt_l123)*cbrt_l123/l1-pow(l2/cbrt_l123,a)*a/l1/3-pow(l3/cbrt_l123,a)*a/l1/3)/a+K*(l123-1.0)*l2*l3)*N1/l2/l3+(mu*(-pow(l1/cbrt_l123,a)*a/l2/3+pow(l2/cbrt_l123,a)*a*2.0/(3.0*cbrt_l123)*cbrt_l123/l2-pow(l3/cbrt_l123,a)*a/l2/3)/a+K*(l123-1.0)*l1*l3)*N2/l1/l3+(mu*(-pow(l1/cbrt_l123,a)*a/l3/3-pow(l2/cbrt_l123,a)*a/l3/3+pow(l3/cbrt_l123,a)*a*2.0/(3.0*cbrt_l123)*cbrt_l123/l3)/a+K*(l123-1.0)*l1*l2)*N3/l1/l2;

// Step 3:
//   Whitespace is nice.
l123 = l1 * l2 * l3;
cbrt_l123 = cbrt(l123);
T =
  (mu*( pow(l1/cbrt_l123,a)*a*2.0/(3.0*cbrt_l123)*cbrt_l123/l1
       -pow(l2/cbrt_l123,a)*a/l1/3
       -pow(l3/cbrt_l123,a)*a/l1/3)/a
   +K*(l123-1.0)*l2*l3)*N1/l2/l3
 +(mu*(-pow(l1/cbrt_l123,a)*a/l2/3
       +pow(l2/cbrt_l123,a)*a*2.0/(3.0*cbrt_l123)*cbrt_l123/l2
       -pow(l3/cbrt_l123,a)*a/l2/3)/a
   +K*(l123-1.0)*l1*l3)*N2/l1/l3
 +(mu*(-pow(l1/cbrt_l123,a)*a/l3/3
       -pow(l2/cbrt_l123,a)*a/l3/3
       +pow(l3/cbrt_l123,a)*a*2.0/(3.0*cbrt_l123)*cbrt_l123/l3)/a
   +K*(l123-1.0)*l1*l2)*N3/l1/l2;

// Step 4:
//   Eliminate the 'a' in (term1*a + term2*a + term3*a)/a
//   Expand (mu_term + K_term)*something to mu_term*something + K_term*something
l123 = l1 * l2 * l3;
cbrt_l123 = cbrt(l123);
T =
  (mu*( pow(l1/cbrt_l123,a)*2.0/(3.0*cbrt_l123)*cbrt_l123/l1
       -pow(l2/cbrt_l123,a)/l1/3
       -pow(l3/cbrt_l123,a)/l1/3))*N1/l2/l3
 +K*(l123-1.0)*l2*l3*N1/l2/l3
 +(mu*(-pow(l1/cbrt_l123,a)/l2/3
       +pow(l2/cbrt_l123,a)*2.0/(3.0*cbrt_l123)*cbrt_l123/l2
       -pow(l3/cbrt_l123,a)/l2/3))*N2/l1/l3
 +K*(l123-1.0)*l1*l3*N2/l1/l3
 +(mu*(-pow(l1/cbrt_l123,a)/l3/3
       -pow(l2/cbrt_l123,a)/l3/3
       +pow(l3/cbrt_l123,a)*2.0/(3.0*cbrt_l123)*cbrt_l123/l3))*N3/l1/l2
 +K*(l123-1.0)*l1*l2*N3/l1/l2;

// Step 5:
//   Rearrange
//   Reduce l2*l3*N1/l2/l3 to N1 (and similar)
//   Reduce 2.0/(3.0*cbrt_l123)*cbrt_l123/l1 to 2.0/3.0/l1 (and similar)
l123 = l1 * l2 * l3;
cbrt_l123 = cbrt(l123);
T =
  (mu*( pow(l1/cbrt_l123,a)*2.0/3.0/l1
       -pow(l2/cbrt_l123,a)/l1/3
       -pow(l3/cbrt_l123,a)/l1/3))*N1/l2/l3
 +(mu*(-pow(l1/cbrt_l123,a)/l2/3
       +pow(l2/cbrt_l123,a)*2.0/3.0/l2
       -pow(l3/cbrt_l123,a)/l2/3))*N2/l1/l3
 +(mu*(-pow(l1/cbrt_l123,a)/l3/3
       -pow(l2/cbrt_l123,a)/l3/3
       +pow(l3/cbrt_l123,a)*2.0/3.0/l3))*N3/l1/l2
 +K*(l123-1.0)*N1
 +K*(l123-1.0)*N2
 +K*(l123-1.0)*N3;

// Step 6:
//   Factor out mu and K*(l123-1.0)
l123 = l1 * l2 * l3;
cbrt_l123 = cbrt(l123);
T =
  mu*(  ( pow(l1/cbrt_l123,a)*2.0/3.0/l1
         -pow(l2/cbrt_l123,a)/l1/3
         -pow(l3/cbrt_l123,a)/l1/3)*N1/l2/l3
      + (-pow(l1/cbrt_l123,a)/l2/3
         +pow(l2/cbrt_l123,a)*2.0/3.0/l2
         -pow(l3/cbrt_l123,a)/l2/3)*N2/l1/l3
      + (-pow(l1/cbrt_l123,a)/l3/3
         -pow(l2/cbrt_l123,a)/l3/3
         +pow(l3/cbrt_l123,a)*2.0/3.0/l3)*N3/l1/l2)
 +K*(l123-1.0)*(N1+N2+N3);

// Step 7:
//   Expand
l123 = l1 * l2 * l3;
cbrt_l123 = cbrt(l123);
T =
  mu*( pow(l1/cbrt_l123,a)*2.0/3.0/l1*N1/l2/l3
      -pow(l2/cbrt_l123,a)/l1/3*N1/l2/l3
      -pow(l3/cbrt_l123,a)/l1/3*N1/l2/l3
      -pow(l1/cbrt_l123,a)/l2/3*N2/l1/l3
      +pow(l2/cbrt_l123,a)*2.0/3.0/l2*N2/l1/l3
      -pow(l3/cbrt_l123,a)/l2/3*N2/l1/l3
      -pow(l1/cbrt_l123,a)/l3/3*N3/l1/l2
      -pow(l2/cbrt_l123,a)/l3/3*N3/l1/l2
      +pow(l3/cbrt_l123,a)*2.0/3.0/l3*N3/l1/l2)
 +K*(l123-1.0)*(N1+N2+N3);

// Step 8:
//   Simplify.
l123 = l1 * l2 * l3;
cbrt_l123 = cbrt(l123);
T =
  mu/(3.0*l123)*(  pow(l1/cbrt_l123,a)*(2.0*N1-N2-N3)
                 + pow(l2/cbrt_l123,a)*(2.0*N2-N3-N1)
                 + pow(l3/cbrt_l123,a)*(2.0*N3-N1-N2))
 +K*(l123-1.0)*(N1+N2+N3);


Mauvaise réponse, intentionnellement pour l'humilité

Notez que c'est frappée. C'est faux.

Mise à jour

Maple ne manquez évidence. Par exemple, il y a un moyen beaucoup plus facile à écrire

(pow(l1 * l2 * l3, -0.1e1 / 0.3e1) - l1 * l2 * l3 * pow(l1 * l2 * l3, -0.4e1 / 0.3e1) / 0.3e1)

En supposant que l' l1, l2, et l3 sont réels plutôt que des nombres complexes, et que la véritable racine cubique (plutôt que sur le principe racines complexes) sont susceptibles d'être extraites, le ci-dessus se réduit à zéro. Ce calcul de zéro est répété de nombreuses fois.

Deuxième mise à jour

Si j'ai bien fait le droit de mathématiques (il n'y a pas de garantie que j'ai déjà fait le droit de mathématiques), le méchant de l'expression dans la question se réduit à

l123 = l1 * l2 * l3; 
cbrt_l123_inv = 1.0 / cbrt(l123);
nasty_expression =
    K * (l123 - 1.0) * (N1 + N2 + N3) 
    - (  pow(l1 * cbrt_l123_inv, a) * (N2 + N3) 
       + pow(l2 * cbrt_l123_inv, a) * (N1 + N3) 
       + pow(l3 * cbrt_l123_inv, a) * (N1 + N2)) * mu / (3.0*l123);

Ce qui précède suppose qu' l1, l2, et l3 sont des nombres réels positifs.

31voto

mariomulansky Points 655

Première chose à noter est que l' pow est vraiment cher, alors vous devriez vous débarrasser de ce autant que possible. Numérisation par le biais de l'expression, je vois beaucoup de répétitions de l' pow(l1 * l2 * l3, -0.1e1 / 0.3e1) et pow(l1 * l2 * l3, -0.4e1 / 0.3e1). Je voudrais donc s'attendre à un gros gain de pré-calcul de ces:

 const double c1 = pow(l1 * l2 * l3, -0.1e1 / 0.3e1);
const double c2 = boost::math::pow<4>(c1);

où je suis en utilisant le boost pow fonction.

En outre, vous avez quelques plus pow avec l'exposant a. Si a est Entier et connu au compilateur de temps, vous pouvez également les remplacer par celles de boost::math::pow<a>(...) de gain de plus de performance. Je vous suggère aussi de remplacer des termes comme a / l1 / 0.3e1 avec a / (l1 * 0.3e1) comme la multiplication est plus rapide, puis de la division.

Enfin, si vous utiliser g++, vous pouvez utiliser l' -ffast-math indicateur qui permet à l'optimiseur d'être plus agressif dans la transformation des équations. Lire à propos de ce que ce drapeau ne fait, comme il a des effets secondaires.

20voto

cdonat Points 253

Woah, quel enfer d'une expression. La création de l'expression d'Érable était en réalité un sous-optimale de choix ici. Le résultat est tout simplement illisible.

  1. a choisi de parler des noms de variables (pas de l1, l2, l3, mais par exemple, la hauteur, la largeur, la profondeur, si c'est ce qu'ils veulent dire). Il est alors plus facile pour vous de comprendre votre propre code.
  2. calculer subterms, que vous utilisez à plusieurs reprises, d'avance et de stocker les résultats dans les variables parlant avec des noms.
  3. Vous l'avez mentionné, que l'expression est évaluée de très nombreuses fois. Je suppose que, seulement quelques paramètres varient dans le plus profond de la boucle. Calculer tous les invariants subterms avant que la boucle. Répétez l'opération pour la deuxième boucle intérieure et ainsi de suite jusqu'à ce que tous les invariants sont en dehors de la boucle.

Théoriquement, le compilateur doit être en mesure de faire tout cela pour vous, mais parfois il ne peut pas - par exemple, lorsque la boucle de nidification s'étend sur de multiples fonctions dans les différentes unités de compilation. De toute façon, qui va vous donner beaucoup mieux lisible, compréhensible et maintenable code.

17voto

Walter Points 7554

David Hammen la réponse est bonne, mais encore loin d'être optimale. Continuons avec sa dernière expression (au moment de l'écriture de cette page)

auto l123 = l1 * l2 * l3;
auto cbrt_l123 = cbrt(l123);
T = mu/(3.0*l123)*(  pow(l1/cbrt_l123,a)*(2.0*N1-N2-N3)
                   + pow(l2/cbrt_l123,a)*(2.0*N2-N3-N1)
                   + pow(l3/cbrt_l123,a)*(2.0*N3-N1-N2))
  + K*(l123-1.0)*(N1+N2+N3);

ce qui peut être optimisé en outre. En particulier, nous pouvons éviter l'appel à cbrt() et celle des appels d' pow() si l'exploitation de certaines mathématique identités. Nous allons le faire de nouveau, étape par étape.

// step 1 eliminate cbrt() by taking the exponent into pow()
auto l123 = l1 * l2 * l3;
auto athird = 0.33333333333333333 * a; // avoid division
T = mu/(3.0*l123)*(  (N1+N1-N2-N3)*pow(l1*l1/(l2*l3),athird)
                   + (N2+N2-N3-N1)*pow(l2*l2/(l1*l3),athird)
                   + (N3+N3-N1-N2)*pow(l3*l3/(l1*l2),athird))
  + K*(l123-1.0)*(N1+N2+N3);

Notez que j'ai aussi optimisé 2.0*N1 de N1+N1 etc. Ensuite, l'on peut faire avec seulement deux appels d' pow().

// step 2  eliminate one call to pow
auto l123 = l1 * l2 * l3;
auto athird = 0.33333333333333333 * a;
auto pow_l1l2_athird = pow(l1/l2,athird);
auto pow_l1l3_athird = pow(l1/l3,athird);
auto pow_l2l3_athird = pow_l1l3_athird/pow_l1l2_athird;
T = mu/(3.0*l123)*(  (N1+N1-N2-N3)* pow_l1l2_athird*pow_l1l3_athird
                   + (N2+N2-N3-N1)* pow_l2l3_athird/pow_l1l2_athird
                   + (N3+N3-N1-N2)/(pow_l1l3_athird*pow_l2l3_athird))
  + K*(l123-1.0)*(N1+N2+N3);

Depuis les appels d' pow() sont de loin le plus cher, ici, il vaut la peine de les réduire autant que possible (le côté cher, a été l'appel à cbrt(), ce qui nous éliminé).

Si, par hasard, a est entier, les appels à l' pow pourrait être optimisée pour les appels d' cbrt (plus les puissances entières), ou si athird est un demi-entier, on peut utiliser sqrt (plus les puissances entières). En outre, si, par hasard, l1==l2 ou l1==l3 ou l2==l3 d'un ou deux appels d' pow peut être éliminé. Alors, il vaut la peine de les considérer comme des cas particuliers si de telles chances existent de façon réaliste.

12voto

Vlad Feinstein Points 479
  1. Combien est "beaucoup beaucoup"?
  2. Combien de temps faut-il prendre?
  3. Faire TOUS les paramètres de changement entre le calcul de cette formule? Ou pouvez-vous mettre en cache certaines valeurs pré calculées?
  4. J'ai tenté un manuel de simplification de la formule, voudrais savoir si il enregistre quoi que ce soit?

    C1 = -0.1e1 / 0.3e1;
    C2 =  0.1e1 / 0.3e1;
    C3 = -0.4e1 / 0.3e1;
    
    X0 = l1 * l2 * l3;
    X1 = pow(X0, C1);
    X2 = pow(X0, C2);
    X3 = pow(X0, C3);
    X4 = pow(l1 * X1, a);
    X5 = pow(l2 * X1, a);
    X6 = pow(l3 * X1, a);
    X7 = a / 0.3e1;
    X8 = X3 / 0.3e1;
    X9 = mu / a;
    XA = X0 - 0.1e1;
    XB = K * XA;
    XC = X1 - X0 * X8;
    XD = a * XC * X2;
    
    XE = X4 * X7;
    XF = X5 * X7;
    XG = X6 * X7;
    
    T = (X9 * ( X4 * XD - XF - XG) / l1 + XB * l2 * l3) * N1 / l2 / l3 
      + (X9 * (-XE + X5 * XD - XG) / l2 + XB * l1 * l3) * N2 / l1 / l3 
      + (X9 * (-XE - XF + X6 * XD) / l3 + XB * l1 * l2) * N3 / l1 / l2;
    

[AJOUTÉ] j'ai travaillé un peu plus sur les trois dernières lignes de la formule et eu cette beauté:

T = X9 / X0 * (
      (X4 * XD - XF - XG) * N1 + 
      (X5 * XD - XE - XG) * N2 + 
      (X5 * XD - XE - XF) * N3)
  + XB * (N1 + N2 + N3)

Permettez-moi de montrer mon travail, étape par étape:

T = (X9 * (X4 * XD - XF - XG) / l1 + XB * l2 * l3) * N1 / l2 / l3 
  + (X9 * (X5 * XD - XE - XG) / l2 + XB * l1 * l3) * N2 / l1 / l3 
  + (X9 * (X5 * XD - XE - XF) / l3 + XB * l1 * l2) * N3 / l1 / l2;


T = (X9 * (X4 * XD - XF - XG) / l1 + XB * l2 * l3) * N1 / (l2 * l3) 
  + (X9 * (X5 * XD - XE - XG) / l2 + XB * l1 * l3) * N2 / (l1 * l3) 
  + (X9 * (X5 * XD - XE - XF) / l3 + XB * l1 * l2) * N3 / (l1 * l2);

T = (X9 * (X4 * XD - XF - XG) + XB * l1 * l2 * l3) * N1 / (l1 * l2 * l3) 
  + (X9 * (X5 * XD - XE - XG) + XB * l1 * l2 * l3) * N2 / (l1 * l2 * l3) 
  + (X9 * (X5 * XD - XE - XF) + XB * l1 * l2 * l3) * N3 / (l1 * l2 * l3);

T = (X9 * (X4 * XD - XF - XG) + XB * X0) * N1 / X0 
  + (X9 * (X5 * XD - XE - XG) + XB * X0) * N2 / X0 
  + (X9 * (X5 * XD - XE - XF) + XB * X0) * N3 / X0;

T = X9 * (X4 * XD - XF - XG) * N1 / X0 + XB * N1 
  + X9 * (X5 * XD - XE - XG) * N2 / X0 + XB * N2
  + X9 * (X5 * XD - XE - XF) * N3 / X0 + XB * N3;


T = X9 * (X4 * XD - XF - XG) * N1 / X0 
  + X9 * (X5 * XD - XE - XG) * N2 / X0
  + X9 * (X5 * XD - XE - XF) * N3 / X0
  + XB * (N1 + N2 + N3)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X