web-dev-qa-db-ja.com

C ++で長い方程式を実装するときに、高レベルのアプローチでパフォーマンスを向上させる方法

私はいくつかのエンジニアリングシミュレーションを開発しています。これには、ゴムのような材料の応力を計算するために、この方程式のようないくつかの長い方程式を実装することが含まれます。

_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;
_

Mapleを使用してC++コードを生成し、ミスを回避します(そして退屈な代数で時間を節約します)。このコードは数千回(数百万ではないにしても)実行されるため、パフォーマンスが懸念されます。残念ながら、これまでのところ数学は単純化されています。長い方程式は避けられません。

この実装を最適化するためにどのようなアプローチをとることができますか?このような方程式を実装するときに適用すべき高レベルの戦略を探しています。必ずしも上記の例の特定の最適化ではありません。

_--enable-optimize=-O3_でg ++を使用してコンパイルしています。

更新:

繰り返し表現が多いことは知っていますが、コンパイラがこれらを処理するという仮定を使用しています。これまでの私のテストでは、そうであることが示唆されています。

_l1, l2, l3, mu, a, K_はすべて正の実数です(ゼロではありません)。

_l1*l2*l3_を同等の変数Jに置き換えました。これはパフォーマンスの改善に役立ちました。

pow(x, 0.1e1/0.3e1)cbrt(x)に置き換えることは良い提案でした。

これはCPUで実行されます。近い将来、GPUで実行される可能性が高くなりますが、現時点ではそのオプションは使用できません。

91
user602095

概要を編集

  • 私の元の答えは、コードに多くの複製された計算が含まれており、力の多くが1/3の係数を含んでいたことを単に指摘しました。たとえば、pow(x, 0.1e1/0.3e1)cbrt(x)と同じです。
  • 私の2回目の編集は間違っていたので、3回目の編集はこの間違いについて推測しました。これが、文字「M」で始まるシンボリック数学プログラムのOracleに似た結果を変更することを人々に恐れさせている理由です。私は(つまり、 攻撃)これらの編集を行い、この回答の現在のリビジョンの一番下にプッシュします。ただし、削除しませんでした。私は人間です。間違いを犯すのは簡単です。
  • 4番目の編集では、質問の複雑な式を正確に表す非常にコンパクトな式を開発しました[〜#〜] if [〜#〜]パラメーター_l1_、_l2_、および_l3_は正の実数であり、aがゼロ以外の実数である場合。 (これらの係数の特定の性質に関して、OPからまだ聞いていません。問題の性質を考えると、これらは合理的な仮定です。)
  • この編集は、これらの式を単純化する方法の一般的な問題に答えようとします。

まず最初に

Mapleを使用してC++コードを生成し、ミスを防ぎます。

MapleとMathematicaは明らかなことを見逃すことがあります。さらに重要なことは、MapleとMathematicaのユーザーが時々ミスをすることです。 「たまにマークに近い」の代わりに、「しばしば」、あるいは「ほぼ常に」を置き換えることもできます。

問題のパラメータについてMapleに伝えることで、Mapleがその式を簡素化するのを助けることができました。手元の例では、_l1_、_l2_、および_l3_は正の実数であり、aは非ゼロの実数であると思われます。その場合は、それを教えてください。これらのシンボリック数学プログラムは通常、手元の量が複雑であると想定しています。ドメインを制限すると、プログラムは複素数では無効な仮定を立てることができます。


記号数学プログラムからこれらの大きな混乱を単純化する方法(この編集)

シンボリック数学プログラムは通常、さまざまなパラメーターに関する情報を提供する機能を提供します。特に問題に除算または累乗が含まれる場合は、その機能を使用してください。手元の例では、_l1_、_l2_、および_l3_は正の実数であり、aは非ゼロの実数。その場合は、それを教えてください。これらのシンボリック数学プログラムは通常、手元の量が複雑であると想定しています。ドメインを制限すると、プログラムは次のような仮定を行うことができます。バツbバツ=(ab)バツ。これは、aおよびbが正の実数であり、xが実数である場合のみです。複素数では無効です。

最終的に、これらのシンボリック数学プログラムはアルゴリズムに従います。一緒に助けてください。コードを生成する前に、拡張、収集、および単純化を試してください。この場合、muの因子を含む用語とKの因子を含む用語を収集できます。表現を「最も単純な形式」に縮小することは、少しの芸術のままです。

生成されたコードのい混乱を得るとき、あなたが触れてはならない真実としてそれを受け入れないでください。自分で単純化してみてください。コードを生成する前のシンボリック数学プログラムの内容を見てください。私があなたの表現をもっとシンプルで高速なものに減らした方法、そして ウォルターの答え がさらにいくつかのステップを踏んだ方法を見てください。魔法のレシピはありません。魔法のレシピがあれば、メイプルはそれを適用し、ウォルターが与えた答えを与えたでしょう。


特定の質問について

その計算で多くの加算と減算を行っています。互いに打ち消しそうな条件があると、深刻なトラブルに巻き込まれる可能性があります。 1つの用語が他の用語よりも優位である場合、多くのCPUを浪費しています。

次に、繰り返し計算を実行することで多くのCPUを無駄にしています。 _-ffast-math_を有効にしていない場合、コンパイラはIEEE浮動小数点の規則の一部を破ることができますが、コンパイラはその式を単純化しません(実際、そうすべきではありません)。代わりに、指示されたとおりに実行します。少なくとも、その混乱を計算する前に_l1 * l2 * l3_を計算する必要があります。

最後に、powを何度も呼び出していますが、これは非常に遅いです。これらの呼び出しのいくつかは(l1 * l2 * l3)の形式であることに注意してください(1/3)powへのこれらの呼び出しの多くは、_std::cbrt_への単一の呼び出しで実行できます。

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

これとともに、

  • X * pow(l1 * l2 * l3, 0.1e1 / 0.3e1)は_X * l123_pow_1_3_になります。
  • X * pow(l1 * l2 * l3, -0.1e1 / 0.3e1)は_X / l123_pow_1_3_になります。
  • X * pow(l1 * l2 * l3, 0.4e1 / 0.3e1)は_X * l123_pow_4_3_になります。
  • X * pow(l1 * l2 * l3, -0.4e1 / 0.3e1)は_X / l123_pow_4_3_になります。


メープルは明らかなことを見逃していました。
たとえば、書く方がはるかに簡単です

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

_l1_、_l2_、および_l3_は複素数ではなく実数であり、実際のキューブルート(原則の複素ルートではなく)が抽出されると仮定すると、上記は

_2.0/(3.0 * pow(l1 * l2 * l3, 1.0/3.0))
_

または

_2.0/(3.0 * l123_pow_1_3)
_

_cbrt_l123_の代わりに_l123_pow_1_3_を使用すると、質問の厄介な表現は

_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);
_

常に二重チェックを行いますが、常に単純化します。


上記に到達するためのいくつかの手順を以下に示します。

_// 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);
_


謙虚さのために意図的に保持された間違った答え

これは打撃を受けることに注意してください。それは間違っています。

更新

メイプルは明らかなことを見逃しました。たとえば、はるかに簡単な記述方法があります

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

_l1_、_l2_、および_l3_は複素数ではなく実数であり、実際のキューブルート(原則の複素ルートではなく)が抽出されると仮定すると、上記はゼロ。このゼロの計算は何度も繰り返されます。

2回目の更新

数学を正しく行った場合(noが正しく数学を行ったことを保証します)、質問の厄介な表現は

_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);
_

上記では、_l1_、_l2_、および_l3_が正の実数であると想定しています。

87
David Hammen

最初に注意することは、powは非常に高価なため、できるだけこれを取り除く必要があるということです。式をスキャンすると、pow(l1 * l2 * l3, -0.1e1 / 0.3e1)pow(l1 * l2 * l3, -0.4e1 / 0.3e1)の繰り返しが多く見られます。したがって、これらを事前に計算することで大きな利益が期待できます。

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

boost pow 関数を使用しています。

さらに、指数powを持つaがいくつかあります。 aが整数であり、コンパイラー時に既知である場合、それらをboost::math::pow<a>(...)に置き換えて、パフォーマンスをさらに向上させることもできます。また、乗算は除算よりも高速なので、_a / l1 / 0.3e1_のような用語をa / (l1 * 0.3e1)に置き換えることをお勧めします。

最後に、g ++を使用する場合、_-ffast-math_フラグを使用して、オプティマイザーが方程式をより積極的に変換できるようにします。 このフラグが実際に行うことについて を読んでください。ただし、副作用があります。

31
mariomulansky

うわー、なんて表現の地獄。 Mapleで式を作成することは、実際には次善の選択でした。結果は単純に判読できません。

  1. 話す変数名を選択しました(l1、l2、l3ではなく、高さ、幅、深さなど)。そうすれば、自分のコードを理解しやすくなります。
  2. 複数回使用するサブタームを事前に計算し、結果を名前を話す変数に保存します。
  3. あなたは、式が非常に何度も評価されることを言及します。一番内側のループで変化するパラメーターはごくわずかです。そのループの前にすべての不変サブタームを計算します。すべての不変条件がループの外側になるまで、2番目の内側のループに対して繰り返します。

理論的には、コンパイラはあなたのためにそれらのすべてを行うことができるはずですが、時にはできないことがあります-例えば。ループのネストが異なるコンパイル単位の複数の関数に広がる場合。とにかく、それははるかに読みやすく、理解しやすく、保守可能なコードを提供します。

20
cdonat

David Hammenの答え は良いですが、まだ最適ではありません。彼の最後の表現を続けましょう(これを書いている時点で)

_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);
_

さらに最適化できます。特に、数学的アイデンティティを利用する場合は、cbrt()への呼び出しとpow()への呼び出しの1つを回避できます。これをステップごとに繰り返してみましょう。

_// 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);
_

また、_2.0*N1_を_N1+N1_などに最適化したことに注意してください。次に、pow()を2回呼び出すだけで実行できます。

_// 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);
_

pow()の呼び出しはここでは最もコストのかかる操作であるため、可能な限りそれらを減らす価値があります(次のコストの高い操作はcbrt()の呼び出しでした。 )。

万が一aが整数の場合、powの呼び出しはcbrt(整数のべき乗)の呼び出しに最適化できます。またはathirdが半分の場合-整数、sqrt(および整数のべき乗)を使用できます。さらに、万が一_l1==l2_または_l1==l3_または_l2==l3_の場合、powの1つまたは両方の呼び出しを排除できます。したがって、このような可能性が現実的に存在する場合は、これらを特別なケースと見なす価値があります。

17
Walter
  1. 「多」はいくつですか?
  2. それはどのくらいかかりますか?
  3. [〜#〜] all [〜#〜]パラメーターは、この式の再計算間で変更されますか?または、事前に計算された値をキャッシュできますか?
  4. 私はその式を手動で簡素化しようとしましたが、それが何かを保存するかどうかを知りたいですか?

    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;
    

[追加]最後の3行の式でさらに作業を行い、この美しさを実現しました:

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

ステップバイステップで私の作品を見せてください:

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)
12
Vlad Feinstein

これは少し簡潔かもしれませんが、基本的にax^3 + bx^2 + cx + dd + x(c + x(b + x(a)))に書き換えるHorner Formを使用して、多項式(エネルギー関数の補間)の高速化を実際に見つけました。これにより、pow()を何度も繰り返し呼び出す必要がなくなり、pow(x,6)を実行する代わりにpow(x,7)x*pow(x,6)を別々に呼び出すなどの愚かなことを実行できなくなります。

これは現在の問題に直接適用することはできませんが、整数のベキを持つ高次多項式がある場合に役立ちます。操作の順序が重要なので、数値の安定性とオーバーフローの問題に注意する必要があるかもしれません(一般に、x^20xは多くの場合、Horner Formがこれに役立つと思いますが桁違いに)。

また、実用的なヒントとして、まだ行っていない場合は、最初にmapleの式を単純化してみてください。おそらく、ほとんどの一般的な部分式の除去を行うことができます。特にそのプログラムのコードジェネレーターにどの程度影響するかはわかりませんが、Mathematicaでコードを生成する前にFullSimplifyを実行すると、大きな違いが生じることがあります。

7
neocpp

多くの繰り返し操作が行われているようです。

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

これらを事前に計算して、高価なpow関数を繰り返し呼び出さないようにすることができます。

事前計算することもできます

l1 * l2 * l3

その用語を繰り返し使用するとき。

3
NathanOliver

万が一、計算を象徴的に提供してもらえますか。ベクトル演算がある場合、blasまたはlapackを使用して調査することをお勧めします。これらは、場合によっては並列に演算を実行できます。

Numpyやscipyでpythonを使用できる可能性があります。可能な限り、計算は次のようになります。より読みやすい。

0
Fred Mitchell

Nvidia CUDAグラフィックカードをお持ちの場合は、計算をグラフィックカードにオフロードすることを検討できます。グラフィックカード自体は、計算が複雑な計算に適しています。

https://developer.nvidia.com/how-to-cuda-c-cpp

そうでない場合は、計算のために複数のスレッドを検討することができます。

0
user3791372

高レベルの最適化について明示的に尋ねたように、さまざまなC++コンパイラを試す価値があるかもしれません。現在、コンパイラは非常に複雑な最適化獣であり、CPUベンダーは非常に強力で特定の最適化を実装する場合があります。ただし、それらの一部は無料ではありません(ただし、無料のアカデミックプログラムがある場合があります)。

  • GNUコンパイラコレクションは無料で柔軟性があり、多くのアーキテクチャで利用可能です
  • Intelコンパイラーは非常に高速で非常に高価であり、AMDアーキテクチャーでも良い結果が得られる場合があります(アカデミックプログラムがあると思います)
  • Clangコンパイラは高速で無料であり、GCCと同様の結果を生成する可能性があります(一部の人々はより高速で優れていると言いますが、これはアプリケーションケースごとに異なる可能性がありますので、自分で体験することをお勧めします)
  • PGI(Portland Group)はIntelコンパイラのように無料ではありません。
  • PathScaleコンパイラーは、AMDアーキテクチャーで良好な結果を実行する場合があります

コードスニペットの実行速度が2倍異なるのは、コンパイラを変更するだけです(もちろん完全に最適化されています)。ただし、出力のIDを確認することに注意してください。積極的な最適化を行うと、出力が異なる可能性がありますが、これは絶対に避けたいものです。

幸運を!

0
math