Wie kann ich die Leistung über einen allgemeinen Ansatz verbessern, wenn ich lange Gleichungen in C ++ implementiere?

92

Ich entwickle einige technische Simulationen. Dies beinhaltet die Implementierung einiger langer Gleichungen wie dieser Gleichung, um die Spannung in einem gummiartigen Material zu berechnen:

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;

Ich verwende Maple, um den C ++ - Code zu generieren, um Fehler zu vermeiden (und Zeit mit langwieriger Algebra zu sparen). Da dieser Code tausende (wenn nicht millionenfach) ausgeführt wird, ist die Leistung ein Problem. Leider vereinfacht sich die Mathematik bisher nur; Die langen Gleichungen sind unvermeidlich.

Welchen Ansatz kann ich wählen, um diese Implementierung zu optimieren? Ich suche nach Strategien auf hoher Ebene, die ich bei der Implementierung solcher Gleichungen anwenden sollte, nicht unbedingt nach spezifischen Optimierungen für das oben gezeigte Beispiel.

Ich kompiliere mit g ++ mit --enable-optimize=-O3.

Aktualisieren:

Ich weiß, dass es viele wiederholte Ausdrücke gibt. Ich gehe davon aus, dass der Compiler damit umgehen würde. Meine bisherigen Tests legen nahe, dass dies der Fall ist.

l1, l2, l3, mu, a, K sind alle positiven reellen Zahlen (nicht Null).

Ich habe durch l1*l2*l3eine äquivalente Variable ersetzt : J. Dies hat zur Verbesserung der Leistung beigetragen.

Ersetzen pow(x, 0.1e1/0.3e1)durch cbrt(x)war ein guter Vorschlag.

Dies wird auf CPUs ausgeführt. In naher Zukunft wird dies wahrscheinlich besser auf GPUs ausgeführt, aber diese Option ist derzeit nicht verfügbar.

TylerH
quelle
32
Nun, das erste, was mir in den Sinn kommt (es sei denn, der Compiler optimiert es selbst), ist, alle pow(l1 * l2 * l3, -0.1e1 / 0.3e1)durch eine Variable zu ersetzen ... Sie müssen Ihren Code vergleichen, um sicherzugehen, ob er schnell oder langsam läuft.
SingerOfTheFall
6
Formatieren Sie den Code auch, um ihn besser lesbar zu machen. Dies kann dazu beitragen, Verbesserungsmöglichkeiten zu identifizieren.
Ed Heal
26
Warum alle Abstimmungen und Abstimmungen zu schließen? Für diejenigen unter Ihnen, die keine numerische oder wissenschaftliche Programmierung mögen, schauen Sie sich andere Fragen an. Dies ist eine gute Frage, die für diese Site gut geeignet ist. Die Scicomp-Site ist noch Beta; Migration gibt es keine gute Option. Die Codeüberprüfungsseite bekommt nicht genug Sciomp-Augen. Was das OP im wissenschaftlichen Rechnen oft getan hat: Konstruieren Sie ein Problem in einem symbolischen Mathematikprogramm, bitten Sie das Programm, Code zu generieren, und berühren Sie das Ergebnis nicht, da der generierte Code so durcheinander ist.
David Hammen
6
@DavidHammen Die Code Review-Site bekommt nicht genug Sciomp-Augen - klingt nach einem Henne -Ei-Problem und einer Denkweise, die CR nicht hilft, mehr von solchen Augen zu bekommen. Gleiches gilt für die Idee, die Beta-Site von scicomp abzulehnen, da es sich um eine Beta-Site handelt. Wenn alle so dachten, wäre Stack Overflow die einzige Site, die wächst.
Mathieu Guindon
13
Diese Frage wird hier
NathanOliver

Antworten:

88

Zusammenfassung bearbeiten

  • In meiner ursprünglichen Antwort wurde lediglich festgestellt, dass der Code viele replizierte Berechnungen enthielt und dass viele der Potenzen Faktoren von 1/3 beinhalteten. Zum Beispiel pow(x, 0.1e1/0.3e1)ist das gleiche wie cbrt(x).
  • Meine zweite Bearbeitung war einfach falsch, und meine dritte wurde auf diese Unrichtigkeit hochgerechnet. Dies ist es, was Menschen Angst macht, die orakelartigen Ergebnisse von symbolischen Mathematikprogrammen zu ändern, die mit dem Buchstaben 'M' beginnen. Ich habe diese Änderungen gestrichen (dh gestrichen ) und sie an den Grund der aktuellen Überarbeitung dieser Antwort verschoben. Ich habe sie jedoch nicht gelöscht. Ich bin menschlich. Es fällt uns leicht, einen Fehler zu machen.
  • Mein vierte bearbeiten entwickelte einen sehr kompakten Ausdruck, korrekt gefalteten Ausdruck in der Frage stellt IF die Parameter l1, l2und l3positive reelle Zahlen sind und wenn aeine Nicht-Null reelle Zahl. (Wir haben vom OP noch nichts über die Spezifität dieser Koeffizienten gehört. Angesichts der Art des Problems sind dies vernünftige Annahmen.)
  • Diese Bearbeitung versucht, das allgemeine Problem der Vereinfachung dieser Ausdrücke zu beantworten.

Das wichtigste zuerst

Ich verwende Maple, um den C ++ - Code zu generieren, um Fehler zu vermeiden.

Maple und Mathematica vermissen manchmal das Offensichtliche. Noch wichtiger ist, dass die Benutzer von Maple und Mathematica manchmal Fehler machen. Das Ersetzen von "oft" oder vielleicht sogar "fast immer" anstelle von "manchmal" ist wahrscheinlich näher an der Marke.

Sie hätten Maple helfen können, diesen Ausdruck zu vereinfachen, indem Sie ihm die fraglichen Parameter mitteilen. Im Beispiel auf der Hand, vermute ich , dass l1, l2und l3positive reelle Zahlen sind und das aist eine von Null verschiedene reelle Zahl. Wenn das der Fall ist, sagen Sie es das. Diese symbolischen Mathematikprogramme gehen normalerweise davon aus, dass die vorliegenden Größen komplex sind. Durch die Einschränkung der Domäne kann das Programm Annahmen treffen, die in den komplexen Zahlen nicht gültig sind.


Wie man diese großen Probleme mit symbolischen Mathematikprogrammen vereinfacht (diese Bearbeitung)

Symbolische Mathematikprogramme bieten normalerweise die Möglichkeit, Informationen über die verschiedenen Parameter bereitzustellen. Verwenden Sie diese Fähigkeit, insbesondere wenn Ihr Problem Teilung oder Potenzierung beinhaltet. Im Beispiel auf der Hand, hätte Ihnen geholfen Maple diesen Ausdruck zu vereinfachen , indem sie das sagen l1, l2und l3positive reelle Zahlen sind und das aist eine von Null verschiedene reelle Zahl. Wenn das der Fall ist, sagen Sie es das. Diese symbolischen Mathematikprogramme gehen normalerweise davon aus, dass die vorliegenden Größen komplex sind. Durch Einschränken der Domäne kann das Programm Annahmen wie a x b x = (ab) x treffen . Dies ist nur dann, wenn aund bpositive reelle Zahlen sind und wenn xes real ist. Es ist in den komplexen Zahlen nicht gültig.

Letztendlich folgen diese symbolischen Mathematikprogramme Algorithmen. Helfen Sie mit. Versuchen Sie, mit dem Erweitern, Sammeln und Vereinfachen zu spielen, bevor Sie Code generieren. In diesem Fall hätten Sie die Begriffe mit einem Faktor von muund die mit einem Faktor von sammeln können K. Das Reduzieren eines Ausdrucks auf seine "einfachste Form" bleibt eine Kunst.

Wenn Sie ein hässliches Durcheinander von generiertem Code erhalten, akzeptieren Sie ihn nicht als eine Wahrheit, die Sie nicht berühren dürfen. Versuchen Sie es selbst zu vereinfachen. Schauen Sie sich an, was das symbolische Mathematikprogramm hatte, bevor es Code generierte. Schau dir an, wie ich deinen Ausdruck auf etwas viel Einfacheres und viel Schnelleres reduziert habe und wie Walters Antwort meine einige Schritte weiter gebracht hat. Es gibt kein Zauberrezept. Wenn es ein magisches Rezept gegeben hätte, hätte Maple es angewendet und die Antwort gegeben, die Walter gegeben hat.


Über die spezifische Frage

Sie addieren und subtrahieren viel in dieser Berechnung. Sie können in große Schwierigkeiten geraten, wenn Sie Begriffe haben, die sich fast gegenseitig aufheben. Sie verschwenden viel CPU, wenn Sie einen Begriff haben, der die anderen dominiert.

Als nächstes verschwenden Sie viel CPU, indem Sie wiederholte Berechnungen durchführen. Sofern Sie nicht aktiviert haben -ffast-math, wodurch der Compiler einige der Regeln des IEEE-Gleitkommas brechen kann, wird der Compiler diesen Ausdruck für Sie nicht (in der Tat nicht) vereinfachen. Es wird stattdessen genau das tun, was Sie ihm gesagt haben. Sie sollten mindestens rechnen, l1 * l2 * l3bevor Sie dieses Durcheinander berechnen .

Schließlich telefonieren Sie viel pow, was extrem langsam ist. Beachten Sie, dass einige dieser Aufrufe die Form (l1 * l2 * l3) (1/3) haben . Viele dieser Anrufe an powkönnten mit einem einzigen Anruf an ausgeführt werden std::cbrt:

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

Mit diesem,

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


Maple vermisste das Offensichtliche.
Zum Beispiel gibt es eine viel einfachere Möglichkeit zu schreiben

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

Vorausgesetzt, das l1,l2 und l3sind real und nicht komplexe Zahlen, und dass der reale Kubikwurzel ( und nicht das Prinzip komplexe root) extrahiert werden sollen, verringert sich die oben

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

oder

2.0/(3.0 * l123_pow_1_3)

Verwenden cbrt_l123stattl123_pow_1_3 reduziert sich der böse Ausdruck in der Frage auf

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

Immer überprüfen, aber auch immer vereinfachen.


Hier sind einige meiner Schritte, um zu den oben genannten Ergebnissen zu gelangen:

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


Falsche Antwort, absichtlich aus Demut gehalten

Beachten Sie, dass dies betroffen ist. Es ist falsch.

Aktualisieren

Maple vermisste das Offensichtliche. Zum Beispiel gibt es eine viel einfachere Möglichkeit zu schreiben

(pow (l1 * l2 * l3, -0,1e1 / 0,3e1) - l1 * l2 * 13 * pow (l1 * l2 * l3, -0,4e1 / 0,3e1) / 0,3e1)

Vorausgesetzt, das l1,l2 undl3 sind real und nicht komplexe Zahlen, und dass der reale Kubikwurzel ( und nicht das Prinzip komplexe root) extrahiert werden sollen, verringert sich die oben auf Null. Diese Berechnung von Null wird um ein Vielfaches wiederholt.

Zweites Update

Wenn ich die Mathematik richtig gemacht habe (es gibt keine Garantie dafür, dass ich die Mathematik richtig gemacht habe), reduziert sich der böse Ausdruck in der Frage auf

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

Die oben geht davon aus, dass l1, l2und l3positive reelle Zahlen sind .

David Hammen
quelle
2
Nun, die CSE-Eliminierung sollte funktionieren, unabhängig von der entspannten Semantik (und dem OP, die in den Kommentaren angegeben sind). Wenn es darauf ankommt (gemessen), sollte dies natürlich überprüft werden (erzeugte Baugruppe). Ihre Punkte zu dominierenden Begriffen, fehlenden Formelvereinfachungen, besser spezialisierten Funktionen und den Gefahren einer Stornierung sind sehr gut.
Deduplikator
3
@Deduplicator - Nicht mit Gleitkomma. Wenn man keine unsicheren mathematischen Optimierungen aktiviert (z. B. durch Angabe -ffast-mathmit gcc oder clang), kann sich der Compiler nicht darauf verlassen, pow(x,-1.0/3.0)dass er gleich ist x*pow(x,-4.0/3.0). Letzteres könnte unterlaufen, während das erste nicht. Um dem Gleitkomma-Standard zu entsprechen, darf der Compiler diese Berechnung nicht auf Null optimieren.
David Hammen
Nun, die sind viel ehrgeiziger als alles, was ich meinte.
Deduplikator
1
@Deduplicator: Als ich eine andere Antwort kommentierte : Sie benötigen -fno-math-errnofür g ++ zu CSE identische powAufrufe. (Es sei denn, es kann vielleicht beweisen, dass pow nicht errno setzen muss?)
Peter Cordes
1
@Lefti - Nehmen Sie viel an Walters Antwort. Es ist viel schneller. Bei all diesen Antworten gibt es ein potenzielles Problem, nämlich die numerische Löschung. Angenommen, Sie N1sind N2und N3nicht negativ, einer der 2*N_i-(N_j+N_k)ist negativ, einer ist positiv und der andere liegt irgendwo dazwischen. Dies kann leicht zu numerischen Löschproblemen führen.
David Hammen
32

Als erstes ist zu beachten, dass dies powsehr teuer ist. Sie sollten dies also so weit wie möglich beseitigen. Beim Durchsuchen des Ausdrucks sehe ich viele Wiederholungen von pow(l1 * l2 * l3, -0.1e1 / 0.3e1)und pow(l1 * l2 * l3, -0.4e1 / 0.3e1). Ich würde also einen großen Gewinn erwarten, wenn ich Folgendes vorberechnete:

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

wo ich die Boost Pow Funktion benutze .

Außerdem haben Sie noch mehr powmit Exponent a. Wenn aInteger ist und zur Compilerzeit bekannt ist, können Sie diese auch durch ersetzen boost::math::pow<a>(...), um weitere Leistung zu erzielen. Ich würde auch vorschlagen, Begriffe wie a / l1 / 0.3e1durch zu ersetzen, a / (l1 * 0.3e1)da die Multiplikation schneller ist als die Division.

Wenn Sie g ++ verwenden, können Sie schließlich das -ffast-mathFlag verwenden, mit dem der Optimierer bei der Transformation von Gleichungen aggressiver vorgehen kann. Lesen Sie, was diese Flagge tatsächlich tut , da sie jedoch Nebenwirkungen hat.

mariomulansky
quelle
5
In unserem Code -ffast-mathführt die Verwendung dazu, dass der Code instabil wird oder falsche Antworten gibt. Wir haben ein ähnliches Problem mit Intel-Compilern und müssen die -fp-model preciseOption verwenden, andernfalls explodiert der Code oder gibt die falschen Antworten. Könnte -ffast-mathes also beschleunigen, aber ich würde empfehlen, zusätzlich zu den in Ihrer verknüpften Frage aufgeführten Nebenwirkungen sehr vorsichtig mit dieser Option umzugehen.
tpg2114
2
@ tpg2114: Nach meinen Tests muss-fno-math-errno g ++ nur identische Aufrufe powaus einer Schleife herausheben können . Das ist für den meisten Code der am wenigsten "gefährliche" Teil von -ffast-math.
Peter Cordes
1
@PeterCordes Das sind interessante Ergebnisse! Wir hatten auch Probleme damit pow , extrem langsam zu sein, und haben schließlich den dlsymin den Kommentaren erwähnten Hack verwendet, um erhebliche Leistungssteigerungen zu erzielen, wenn wir dies tatsächlich mit etwas weniger Präzision tun könnten.
tpg2114
Würde GCC nicht verstehen, dass pow eine reine Funktion ist? Das ist wahrscheinlich eingebautes Wissen.
usr
6
@usr: Das ist genau der Punkt, denke ich. powist nach dem Standard keine reine Funktion, da sie unter errnobestimmten Umständen eingestellt werden soll. Das Setzen von Flags wie z. B. -fno-math-errnobewirkt, dass es nicht gesetzt wird errno(was gegen den Standard verstößt), aber dann ist es eine reine Funktion und kann als solche optimiert werden.
Nate Eldredge
20

Woah, was für ein verdammter Ausdruck. Das Erstellen des Ausdrucks mit Maple war hier tatsächlich eine suboptimale Wahl. Das Ergebnis ist einfach unlesbar.

  1. wählte sprechende Variablennamen (nicht l1, l2, l3, sondern zB Höhe, Breite, Tiefe, wenn sie das bedeuten). Dann fällt es Ihnen leichter, Ihren eigenen Code zu verstehen.
  2. Berechnen Sie Subterms, die Sie mehrmals verwenden, im Voraus und speichern Sie die Ergebnisse in Variablen mit sprechenden Namen.
  3. Sie erwähnen, dass der Ausdruck sehr oft ausgewertet wird. Ich denke, nur wenige Parameter variieren in der innersten Schleife. Berechnen Sie alle invarianten Subterme vor dieser Schleife. Wiederholen Sie diesen Vorgang für die zweite innere Schleife usw., bis sich alle Invarianten außerhalb der Schleife befinden.

Theoretisch sollte der Compiler in der Lage sein, all das für Sie zu tun, aber manchmal kann er dies nicht - z. B. wenn sich die Schleifenverschachtelung über mehrere Funktionen in verschiedenen Kompilierungseinheiten erstreckt. Auf diese Weise erhalten Sie einen viel besser lesbaren, verständlichen und wartbaren Code.

cdonat
quelle
8
"Der Compiler sollte es tun, aber manchmal nicht", ist hier der Schlüssel. Neben der Lesbarkeit natürlich.
Javier
3
Wenn der Compiler nichts tun muss, ist dies fast immer falsch.
Edmz
4
Re Wählen Sie sprechende Variablennamen - Oft gilt diese nette Regel nicht, wenn Sie rechnen. Wenn ich mir Code anschaue, der einen Algorithmus in einer wissenschaftlichen Zeitschrift implementieren soll, würde ich viel lieber sehen, dass die Symbole im Code genau die sind, die in der Zeitschrift verwendet werden. In der Regel bedeutet dies extrem kurze Namen, möglicherweise mit einem Index.
David Hammen
8
"Das Ergebnis ist einfach unlesbar" - warum ist das ein Problem? Es ist Ihnen egal, dass die Hochsprachenausgabe eines Lexer- oder Parser-Generators (von Menschen) "unlesbar" war. Was hier zählt, ist, dass die Eingabe in den Codegenerator (Maple) lesbar und überprüfbar ist. Die Sache nicht zu tun ist , bearbeiten der generierte Code von Hand, wenn Sie sicher sein wollen , dass es fehlerfrei ist.
Alephzero
3
@DavidHammen: Nun, in diesem Fall, die aus einem Buchstaben diejenigen sind die „sprechenden Namen“. Zum Beispiel, wenn sie in einem 2-dimensionalen kartesischen Geometrie tut Koordinatensystem, xund yist nicht sinnlos aus einem Buchstaben Variablen, sie heil sind Worte mit einer genauen Definition und eine gut und weit verstandenen Bedeutung.
Jörg W Mittag
17

Die Antwort von David Hammen ist gut, aber noch lange nicht optimal. Fahren wir mit seinem letzten Ausdruck fort (zum Zeitpunkt des Schreibens)

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

was weiter optimiert werden kann. Insbesondere können wir den Aufruf von cbrt()und einen der Aufrufe von vermeiden , pow()wenn wir einige mathematische Identitäten ausnutzen. Lassen Sie uns dies Schritt für Schritt wiederholen.

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

Beachten Sie, dass ich auch 2.0*N1auf N1+N1usw. optimiert habe . Als nächstes können wir mit nur zwei Aufrufen auf tun 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);

Da die Anrufe pow()hier bei weitem die teuerste Operation sind, lohnt es sich, sie so weit wie möglich zu reduzieren (die nächste kostspielige Operation war der Anruf bei cbrt(), den wir eliminiert haben).

Wenn zufällig aeine Ganzzahl ist, können die Aufrufe an powfür Aufrufe an cbrt(plus ganzzahlige Potenzen) optimiert werden , oder wenn athirdes sich um eine halbe Ganzzahl handelt, können wir sqrt(plus ganzzahlige Potenzen) verwenden. Darüber hinaus kann, wenn durch Zufall l1==l2oder l1==l3oder l2==l3ein oder beide Anrufe powzu eliminiert werden. Es lohnt sich also, diese als Sonderfälle zu betrachten, wenn solche Chancen realistisch sind.

Walter
quelle
@gnat Ich schätze deine Bearbeitung (ich dachte daran, das selbst zu tun), hätte es aber fairer gefunden, wenn Davids Antwort auch auf diese verlinkt wäre. Warum bearbeitest du nicht auch Davids Antwort auf ähnliche Weise?
Walter
1
Ich habe nur bearbeitet, weil ich gesehen habe, dass Sie es ausdrücklich erwähnt haben. Ich habe Davids Antwort noch einmal gelesen und konnte dort keinen Hinweis auf Ihre Antwort finden. Ich versuche, Änderungen zu vermeiden, bei denen nicht 100% klar ist, dass das, was ich hinzufüge, den Absichten des Autors entspricht
Mücke
1
@Walter - Meine Antwort verlinkt jetzt auf deine.
David Hammen
1
Ich war es bestimmt nicht. Ich habe Ihre Antwort vor ein paar Tagen positiv bewertet. Ich habe auch eine zufällige Abstimmung über meine Antwort erhalten. Sachen passieren einfach manchmal.
David Hammen
1
Sie und ich haben jeweils eine dürftige Ablehnung erhalten. Schauen Sie sich alle Abstimmungen zu dieser Frage an! Bis jetzt hat die Frage 16 Abstimmungen erhalten. Es hat auch 80 Upvotes erhalten, die all diese Downvoter mehr als ausgleichen.
David Hammen
12
  1. Wie viele sind "viele viele"?
  2. Wie lange dauert es?
  3. Do ALL Parameter ändern dieser Formel zwischen Neuberechnung? Oder können Sie einige vorberechnete Werte zwischenspeichern?
  4. Ich habe versucht, diese Formel manuell zu vereinfachen. Möchten Sie wissen, ob sie etwas spart?

    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;

[HINZUGEFÜGT] Ich habe noch etwas an der letzten dreizeiligen Formel gearbeitet und es auf diese Schönheit zurückgeführt:

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

Lassen Sie mich Schritt für Schritt meine Arbeit zeigen:

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)
Vlad Feinstein
quelle
2
Das fällt auf, oder? :) FORTRAN, IIRC, wurde für effiziente Formelberechnungen entwickelt ("FOR" steht für Formel).
Vlad Feinstein
Die meisten F77-Codes, die ich gesehen habe, sahen so aus (z. B. BLAS & NR). Sehr froh, dass Fortran 90-> 2008 existiert :)
Kyle Kanos
Ja. Wenn Sie eine Formel übersetzen, gibt es einen besseren Weg als FORmulaTRANslation?
Brian Drummond
1
Ihre "Optimierung" greift den falschen Ort an. Die kostspieligen Bits sind die Anrufe std::pow(), von denen Sie noch 6, 3 mal mehr als nötig haben. Mit anderen Worten, Ihr Code ist dreimal langsamer als möglich.
Walter
7

Dies mag etwas knapp sein, aber ich habe tatsächlich eine gute Beschleunigung für Polynome (Interpolation von Energiefunktionen) gefunden, indem ich Horner Form verwendet habe, das im Grunde genommen ax^3 + bx^2 + cx + dals umschreibt d + x(c + x(b + x(a))). Dies vermeidet viele wiederholte Anrufe bei pow()und hindert Sie daran, dumme Dinge wie separates Anrufen pow(x,6)und pow(x,7)nicht nur zu tun x*pow(x,6).

Dies gilt nicht direkt für Ihr aktuelles Problem. Wenn Sie jedoch Polynome höherer Ordnung mit ganzzahligen Potenzen haben, kann dies hilfreich sein. Möglicherweise müssen Sie auf numerische Stabilitäts- und Überlaufprobleme achten, da die Reihenfolge der Operationen dafür wichtig ist (obwohl ich im Allgemeinen tatsächlich denke, dass Horner Form dabei hilft, da x^20und xnormalerweise viele Größenordnungen voneinander entfernt sind).

Versuchen Sie auch als praktischen Tipp, wenn Sie dies noch nicht getan haben, zuerst den Ausdruck in Ahorn zu vereinfachen. Sie können es wahrscheinlich dazu bringen, den größten Teil der üblichen Eliminierung von Unterausdrücken für Sie durchzuführen. Ich weiß nicht, wie sehr sich dies insbesondere auf den Codegenerator in diesem Programm auswirkt, aber ich weiß, dass in Mathematica eine vollständige Vereinfachung vor dem Generieren des Codes zu einem großen Unterschied führen kann.

neocpp
quelle
Die Horner-Form ist ein ziemlich normaler Standard für die Codierung von Polynomen, und dies hat für die Frage überhaupt keine Relevanz.
Walter
1
Dies mag angesichts seines Beispiels zutreffen, aber Sie werden feststellen, dass er "Gleichungen dieses Typs" sagte. Ich dachte, die Antwort wäre nützlich, wenn das Poster Polynome in seinem System hätte. Mir ist besonders aufgefallen, dass Codegeneratoren für CAS-Programme wie Mathematica und Maple dazu neigen, Ihnen KEIN Horner-Formular zu geben, es sei denn, Sie fragen ausdrücklich danach. Sie entsprechen standardmäßig der Art und Weise, wie Sie als Mensch normalerweise ein Polynom schreiben.
Neocpp
3

Es sieht so aus, als würden viele wiederholte Operationen durchgeführt.

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

Sie können diese vorberechnen, damit Sie die powFunktion nicht wiederholt aufrufen , was teuer sein kann.

Sie können auch vorkalutieren

l1 * l2 * l3

wie Sie diesen Begriff wiederholt verwenden.

NathanOliver
quelle
6
Ich wette, der Optimierer erledigt dies bereits für Sie ... obwohl dadurch zumindest der Code besser lesbar wird.
Karoly Horvath
Ich habe das getan, aber es hat die Dinge überhaupt nicht beschleunigt. Ich dachte, das lag daran, dass sich die Compiler-Optimierung bereits darum kümmerte.
Das Speichern von l1 * l2 * l3 beschleunigt die Dinge, nicht sicher, warum mit der Compiler-Optimierung
weil der Compiler manchmal einfach keine Optimierungen vornehmen kann oder sie im Konflikt mit anderen Optionen findet.
Javier
1
Tatsächlich darf der Compiler diese Optimierungen nur durchführen, wenn sie -ffast-mathaktiviert sind. Wie in einem Kommentar von @ tpg2114 erwähnt, kann diese Optimierung zu äußerst instabilen Ergebnissen führen.
David Hammen
0

Wenn Sie eine Nvidia CUDA-Grafikkarte besitzen, können Sie die Berechnungen auf die Grafikkarte verlagern, die sich selbst besser für rechenintensive Berechnungen eignet.

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

Wenn nicht, können Sie mehrere Threads für Berechnungen berücksichtigen.

user3791372
quelle
10
Diese Antwort ist orthogonal zur vorliegenden Frage. Während GPUs sehr viele Prozessoren haben, sind sie im Vergleich zu der in die CPU eingebetteten FPU ziemlich langsam. Das Durchführen einer einzelnen seriellen Berechnung mit einer GPU ist ein großer Verlust. Die CPU muss die Pipeline zur GPU füllen, warten, bis die langsame GPU diese einzelne Aufgabe ausführt, und dann das Ergebnis entladen. Während GPUs absolut fantastisch sind, wenn das vorliegende Problem massiv parallelisierbar ist, sind sie absolut grausam, wenn es um die Ausführung serieller Aufgaben geht.
David Hammen
1
In der ursprünglichen Frage: "Da dieser Code viele Male ausgeführt wird, ist die Leistung ein Problem." Das ist eins mehr als "viele". Die Operation kann die Berechnungen in einem Thread senden.
user3791372
0

Könnten Sie die Berechnung zufällig symbolisch angeben? Wenn es Vektoroperationen gibt, möchten Sie möglicherweise wirklich die Verwendung von Blas oder Lapack untersuchen, die in einigen Fällen Operationen parallel ausführen können.

Es ist denkbar (auf die Gefahr hin, nicht zum Thema zu gehören?), Dass Sie Python mit Numpy und / oder Scipy verwenden können. Soweit dies möglich war, sind Ihre Berechnungen möglicherweise besser lesbar.

Fred Mitchell
quelle
0

Da Sie explizit nach Optimierungen auf hoher Ebene gefragt haben, lohnt es sich möglicherweise, verschiedene C ++ - Compiler auszuprobieren. Heutzutage sind Compiler sehr komplexe Optimierungstiere, und CPU-Anbieter implementieren möglicherweise sehr leistungsfähige und spezifische Optimierungen. Bitte beachten Sie, dass einige von ihnen nicht kostenlos sind (es kann jedoch ein kostenloses akademisches Programm geben).

  • Die GNU-Compilersammlung ist kostenlos, flexibel und auf vielen Architekturen verfügbar
  • Intel-Compiler sind sehr schnell, sehr teuer und können auch für AMD-Architekturen gute Ergebnisse liefern (ich glaube, es gibt ein akademisches Programm).
  • Clang-Compiler sind schnell, kostenlos und liefern möglicherweise ähnliche Ergebnisse wie GCC (einige Leute sagen, sie sind schneller und besser, aber dies kann für jeden Anwendungsfall unterschiedlich sein. Ich schlage vor, Ihre eigenen Erfahrungen zu machen).
  • PGI (Portland Group) ist als Intel-Compiler nicht kostenlos.
  • PathScale-Compiler erzielen möglicherweise gute Ergebnisse auf AMD-Architekturen

Ich habe gesehen, dass sich Code-Snippets in der Ausführungsgeschwindigkeit um den Faktor 2 unterscheiden, nur durch Ändern des Compilers (natürlich mit vollständigen Optimierungen). Beachten Sie jedoch die Identität der Ausgabe. Eine aggressive Optimierung kann zu unterschiedlichen Ergebnissen führen, was Sie unbedingt vermeiden möchten.

Viel Glück!

Mathematik
quelle