Je développe des simulations d'ingénierie. Cela implique la mise en œuvre de longues équations telles que cette équation pour calculer la contrainte dans un matériau de type caoutchouc:
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'utilise Maple pour générer le code C++ pour éviter les erreurs (et gagner du temps avec une algèbre fastidieuse). Comme ce code est exécuté des milliers (sinon des millions) de fois, les performances sont un problème. Malheureusement, les calculs ne se simplifient que jusqu'à présent; les équations longues sont inévitables.
Quelle approche puis-je adopter pour optimiser cette implémentation? Je recherche des stratégies de haut niveau que je devrais appliquer lors de l'implémentation de telles équations, pas nécessairement des optimisations spécifiques pour l'exemple ci-dessus.
Je compile en utilisant g ++ avec --enable-optimize=-O3
.
Mise à jour:
Je sais qu'il y a beaucoup d'expressions répétées, j'utilise l'hypothèse que le compilateur les gérerait; mes tests jusqu'à présent suggèrent que c'est le cas.
l1, l2, l3, mu, a, K
Sont tous des nombres réels positifs (pas zéro).
J'ai remplacé l1*l2*l3
Par une variable équivalente: J
. Cela a permis d'améliorer les performances.
Remplacer pow(x, 0.1e1/0.3e1)
par cbrt(x)
était une bonne suggestion.
Cela sera exécuté sur les processeurs, dans un avenir proche, cela fonctionnerait probablement mieux sur les GPU, mais pour l'instant cette option n'est pas disponible.
pow(x, 0.1e1/0.3e1)
est identique à cbrt(x)
.l1
, l2
Et l3
Sont des nombres réels positifs et si a
est un nombre réel non nul. (Nous n'avons pas encore entendu le PO sur la nature spécifique de ces coefficients. Compte tenu de la nature du problème, ce sont des hypothèses raisonnables.)J'utilise Maple pour générer le code C++ pour éviter les erreurs.
Maple et Mathematica manquent parfois l'évidence. Plus important encore, les utilisateurs de Maple et Mathematica font parfois des erreurs. La substitution de "souvent", ou peut-être même "presque toujours", au lieu de "parfois est probablement plus proche de la marque.
Vous auriez pu aider Maple à simplifier cette expression en lui parlant des paramètres en question. Dans l'exemple présenté, je soupçonne que l1
, l2
Et l3
Sont des nombres réels positifs et que a
est un nombre réel non nul. Si tel est le cas, dites-le lui. Ces programmes mathématiques symboliques supposent généralement que les quantités disponibles sont complexes. Restreindre le domaine permet au programme de faire des hypothèses qui ne sont pas valides dans les nombres complexes.
Les programmes mathématiques symboliques permettent généralement de fournir des informations sur les différents paramètres. Utilisez cette capacité, en particulier si votre problème implique une division ou une exponentiation. Dans l'exemple présenté, vous auriez pu aider Maple à simplifier cette expression en lui disant que l1
, l2
Et l3
Sont des nombres réels positifs et que a
est un nombre réel non nul. Si tel est le cas, dites-le lui. Ces programmes mathématiques symboliques supposent généralement que les quantités disponibles sont complexes. La restriction du domaine permet au programme de faire des hypothèses telles qu'unxbx= (ab)x. C'est seulement si a
et b
sont des nombres réels positifs et si x
est réel. Il n'est pas valable dans les nombres complexes.
En fin de compte, ces programmes mathématiques symboliques suivent des algorithmes. Aidez-le. Essayez de développer, de collecter et de simplifier avant de générer du code. Dans ce cas, vous auriez pu collecter les termes impliquant un facteur mu
et ceux impliquant un facteur K
. Réduire une expression à sa "forme la plus simple" reste un peu un art.
Lorsque vous obtenez un désordre laid de code généré, ne l'acceptez pas comme une vérité que vous ne devez pas toucher. Essayez de le simplifier vous-même. Regardez ce que le programme mathématique symbolique avait avant de générer du code. Regardez comment j'ai réduit votre expression à quelque chose de beaucoup plus simple et beaucoup plus rapide, et comment réponse de Walter a pris le mien plusieurs étapes plus loin. Il n'y a pas de recette magique. S'il y avait une recette magique, Maple l'aurait appliquée et aurait donné la réponse que Walter avait donnée.
Vous faites beaucoup d'addition et de soustraction dans ce calcul. Vous pouvez avoir de gros ennuis si vous avez des conditions qui s'annulent presque. Vous gaspillez beaucoup de CPU si vous avez un terme qui domine sur les autres.
Ensuite, vous gaspillez beaucoup de CPU en effectuant des calculs répétés. À moins que vous n'ayez activé -ffast-math
, Qui permet au compilateur d'enfreindre certaines des règles de la virgule flottante IEEE, le compilateur ne simplifiera pas (en fait, ne doit pas) cette expression pour vous. Il fera à la place exactement ce que vous lui avez dit de faire. Au minimum, vous devez calculer l1 * l2 * l3
Avant de calculer ce désordre.
Enfin, vous passez beaucoup d'appels à 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 à pow
pourraient être effectués avec un seul appel à std::cbrt
:
l123 = l1 * l2 * l3;
l123_pow_1_3 = std::cbrt(l123);
l123_pow_4_3 = l123 * l123_pow_1_3;
Avec ça,
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 a raté l'évidence.
Par exemple, il existe un moyen beaucoup plus simple d'é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 l1
, l2
Et l3
Sont des nombres réels plutôt que complexes, et que la racine réelle du cube (plutôt que la racine complexe principale) doit être extraite, 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)
En utilisant cbrt_l123
Au lieu de l123_pow_1_3
, L'expression désagréable de 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);
Vérifiez toujours deux fois, mais simplifiez toujours aussi.
Voici certaines de mes étapes pour arriver à ce qui précède:
// 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);
Notez que cela est frappé. C'est faux.
Mise à jour
Maple a raté l'évidence. Par exemple, il existe un moyen beaucoup plus simple d'é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 l1
, l2
Et l3
Sont des nombres réels plutôt que complexes, et que la racine réelle du cube (plutôt que la racine complexe principale) doit être extraite, le ci-dessus se réduit à zéro. Ce calcul de zéro est répété plusieurs fois.
Deuxième mise à jour
Si j'ai bien fait les maths (il n'y a pas garantie que j'ai bien fait les maths), l'expression désagréable de 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 que l1
, l2
Et l3
Sont des nombres réels positifs.
La première chose à noter est que pow
est vraiment cher, vous devez donc vous en débarrasser autant que possible. En parcourant l'expression, je vois de nombreuses répétitions de pow(l1 * l2 * l3, -0.1e1 / 0.3e1)
et pow(l1 * l2 * l3, -0.4e1 / 0.3e1)
. Je m'attendrais donc à un gros gain en pré-calculant ceux-ci:
const double c1 = pow(l1 * l2 * l3, -0.1e1 / 0.3e1);
const double c2 = boost::math::pow<4>(c1);
où j'utilise la fonction boost pow .
De plus, vous avez encore pow
avec l'exposant a
. Si a
est Integer et connu au moment du compilateur, vous pouvez également remplacer ceux par boost::math::pow<a>(...)
pour obtenir des performances supplémentaires. Je suggère également de remplacer des termes comme a / l1 / 0.3e1
Par a / (l1 * 0.3e1)
car la multiplication est plus rapide que la division.
Enfin, si vous utilisez g ++, vous pouvez utiliser l'indicateur -ffast-math
Qui permet à l'optimiseur d'être plus agressif dans la transformation des équations. Lisez sur ce que fait réellement cet indicateur , car il a cependant des effets secondaires.
Woah, quel enfer d'expression. La création de l'expression avec Maple était en fait un choix sous-optimal ici. Le résultat est tout simplement illisible.
Théoriquement, le compilateur devrait être capable de faire tout cela pour vous, mais parfois il ne peut pas - par exemple lorsque l'imbrication de boucle se propage sur plusieurs fonctions dans différentes unités de compilation. Quoi qu'il en soit, cela vous donnera un code beaucoup plus lisible, compréhensible et maintenable.
réponse de David Hammen est bon, mais encore loin d'être optimal. Continuons avec sa dernière expression (au moment d'écrire ceci)
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);
qui peut être optimisé davantage. En particulier, nous pouvons éviter l'appel à cbrt()
et l'un des appels à pow()
si nous exploitons certaines identités mathématiques. Faisons-le à 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 également optimisé 2.0*N1
En N1+N1
Etc. Ensuite, nous pouvons faire avec seulement deux appels à 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);
Étant donné que les appels à pow()
sont de loin l'opération la plus coûteuse ici, il vaut la peine de les réduire autant que possible (la prochaine opération coûteuse a été l'appel à cbrt()
, que nous avons éliminée ).
Si par hasard a
est entier, les appels à pow
pourraient être optimisés en appels à cbrt
(plus les puissances entières), ou si athird
est à moitié -entier, nous pouvons utiliser sqrt
(plus les puissances entières). De plus, si par hasard l1==l2
Ou l1==l3
Ou l2==l3
Un ou les deux appels à pow
peuvent être éliminés. Donc, cela vaut la peine de les considérer comme des cas spéciaux si de telles chances existent de manière réaliste.
J'ai tenté une simplification manuelle de cette formule, je voudrais savoir si elle enregistre quelque chose?
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 encore travaillé sur la dernière formule à trois lignes et je suis parvenu à 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)
Cela peut être un peu laconique, mais j'ai en fait trouvé une bonne accélération pour les polynômes (interpolation des fonctions énergétiques) en utilisant Horner Form, qui réécrit essentiellement ax^3 + bx^2 + cx + d
En d + x(c + x(b + x(a)))
. Cela évitera de nombreux appels répétés à pow()
et vous empêchera de faire des choses idiotes comme appeler séparément pow(x,6)
et pow(x,7)
au lieu de simplement faire x*pow(x,6)
.
Ce n'est pas directement applicable à votre problème actuel, mais si vous avez des polynômes d'ordre élevé avec des puissances entières, cela peut aider. Vous devrez peut-être faire attention aux problèmes de stabilité numérique et de débordement car l'ordre des opérations est important pour cela (bien qu'en général, je pense que Horner Form y contribue, car x^20
Et x
sont généralement plusieurs ordres de grandeur à part).
Également comme astuce pratique, si vous ne l'avez pas déjà fait, essayez d'abord de simplifier l'expression en érable. Vous pouvez probablement le faire faire la plupart de l'élimination de la sous-expression commune pour vous. Je ne sais pas dans quelle mesure cela affecte le générateur de code dans ce programme en particulier, mais je sais que dans Mathematica, faire un FullSimplify avant de générer le code peut entraîner une énorme différence.
Il semble que vous ayez beaucoup d'opérations répétées en cours.
pow(l1 * l2 * l3, -0.1e1 / 0.3e1)
pow(l1 * l2 * l3, -0.4e1 / 0.3e1)
Vous pouvez les pré-calculer afin de ne pas appeler à plusieurs reprises la fonction pow
qui peut être coûteuse.
Vous pouvez également pré-caluter
l1 * l2 * l3
que vous utilisez ce terme à plusieurs reprises.
Par hasard, pourriez-vous fournir le calcul symboliquement. S'il y a des opérations vectorielles, vous voudrez peut-être vraiment étudier l'utilisation de blas ou de lapack qui, dans certains cas, peut exécuter des opérations en parallèle.
Il est concevable (au risque d'être hors sujet?) Que vous puissiez utiliser python avec numpy et/ou scipy. Dans la mesure où cela était possible, vos calculs pourraient être plus lisible.
Si vous avez une carte graphique Nvidia CUDA, vous pouvez envisager de décharger les calculs sur la carte graphique, qui est elle-même plus adaptée aux calculs complexes.
https://developer.nvidia.com/how-to-cuda-c-cpp
Sinon, vous voudrez peut-être considérer plusieurs threads pour les calculs.
Comme vous l'avez explicitement demandé à propos des optimisations de haut niveau, il peut être utile d'essayer différents compilateurs C++. De nos jours, les compilateurs sont des bêtes d'optimisation très complexes et les fournisseurs de CPU peuvent implémenter des optimisations très puissantes et spécifiques. Mais veuillez noter que certains d'entre eux ne sont pas gratuits (mais il pourrait y avoir un programme académique gratuit).
J'ai vu des extraits de code différer dans la vitesse d'exécution par le facteur 2, uniquement en changeant le compilateur (avec des optimisations complètes bien sûr). Mais n'oubliez pas de vérifier l'identité de la sortie. Une optimisation agressive peut conduire à des sorties différentes, ce que vous voulez absolument éviter.
Bonne chance!