Wie könnte ich diese Berechnung optimieren? (x^a + y^a +z^a)^(1/a)

Lesezeit: 12 Minuten

Benutzer-Avatar
Schwarzkugel

Wie der Titel zeigt. Ich muss viele Berechnungen wie folgt durchführen:

re = (x^a + y^a + z^a)^(1/a).

wo {x, j, z} >= 0. genauer gesagt, a eine positive Fließkommakonstante ist, und x, j, z sind Gleitkommazahlen. Das ^ ist ein Potenzierungsoperator.

Derzeit würde ich es vorziehen, SIMD nicht zu verwenden, hoffe aber auf einen anderen Trick, um es zu beschleunigen.

static void heavy_load(void) {
  static struct xyz_t {
    float x,y,z;
  };
  struct xyz_t xyzs[10000];
  float re[10000] = {.0f};
  const float a = 0.2;

  /* here fill xyzs using some random positive floating point values */

  for (i = 0; i < 10000; ++ i) {
    /* here is what we need to optimize */
    re[i] = pow((pow(xyzs[i].x, a) + pow(xyzs[i].y, a) + pow(xyzs[i].z, a)), 1.0/a);
  }
}

  • Muss man das eigentlich wissen re? Oft können Sie durch Computer erhalten re^a, was Ihnen eine Potenzierung erspart. Können Sie mehr Kontext liefern?

    – Vorlagentypdef

    26. September 2013 um 2:52 Uhr

  • Ich stimme zu, dass Ihre Frage schlecht gestellt ist. Aber es ist wahrscheinlich, dass die einzig mögliche Optimierung if ist a zufällig eine ganze Zahl, wenn Sie die inneren Power Ops durch wiederholte Faktoren und die Wurzel mit optimieren können sqrt Wenn a == 2. Im allgemeinen Fall verwenden Sie die pow Bibliotheksfunktion 4 mal.

    – Gen

    26. September 2013 um 3:00 Uhr

  • Wenn Ihr a immer 0,1 oder 0,2 ist, können Sie zumindest einen Anruf auf elide pow() indem die letzte Potenzierung als Multiplikation codiert wird, da diese letzte Potenzierung einen Exponenten von 10 bzw. 5 haben würde. Das sollte Ihre Zeit um etwa 20 % verkürzen.

    – Cmaster – Wiedereinsetzung von Monica

    26. September 2013 um 4:10 Uhr

  • @cmaster: Wie können Sie (x ^ 10) oder (x ^ 5) durch Multiplikation ersetzen?

    – Kuba hat Monica nicht vergessen

    26. September 2013 um 4:47 Uhr

  • @KubaOber: Wenn Sie die Option -ffast-math aktivieren, wird gcc sie automatisch für Sie optimieren. Sehen Sie sich die Antwort hier an

    – phuklv

    26. September 2013 um 5:06 Uhr

Benutzer-Avatar
David Hammen

Das erste, was Sie tun müssen, ist, eine dieser Potenzierungen loszuwerden, indem Sie einen der Terme ausklammern. Der folgende Code verwendet

(x^a + y^a + z^a)^(1/a) = x * ((1 + (y/x)^a + (z/x)^a)^(1/a))

Das Herausrechnen des größten der drei wäre etwas sicherer und vielleicht genauer.

Eine weitere Optimierung besteht darin, die Tatsache auszunutzen, dass a entweder 0,1 oder 0,2 ist. Verwenden Sie eine Chebychev-Polynom-Approximation, um x^a zu approximieren. Der folgende Code hat nur eine Annäherung für x^0.1; x^0.2 ist einfach so quadratisch. Da schließlich 1/a eine kleine ganze Zahl (5 oder 10) ist, kann die endgültige Potenzierung durch eine kleine Anzahl von Multiplikationen ersetzt werden.

Um zu sehen, was in den Funktionen vor sich geht powtenth und powtenthnorm siehe diese Antwort: Optimierungen für pow() mit konstantem nicht ganzzahligem Exponenten? .

#include <stdlib.h>
#include <math.h>


float powfive (float x);
float powtenth (float x);
float powtenthnorm (float x);

// Returns (x^0.2 + y^0.2 + z^0.2)^5
float pnormfifth (float x, float y, float z) {
   float u = powtenth(y/x);
   float v = powtenth(z/x);
   return x * powfive (1.0f + u*u + v*v);
}

// Returns (x^0.1 + y^0.1 + z^0.1)^10
float pnormtenth (float x, float y, float z) {
   float u = powtenth(y/x);
   float v = powtenth(z/x);
   float w = powfive (1.0f + u + v);
   return x * w * w;
}

// Returns x^5
float powfive (float x) {
   float xsq = x*x;
   return xsq*xsq*x;
}

// Returns x^0.1.
float powtenth (float x) {
   static const float pow2_tenth[10] = {
      1.0,
      pow(2.0, 0.1),
      pow(4.0, 0.1),
      pow(8.0, 0.1),
      pow(16.0, 0.1),
      pow(32.0, 0.1),
      pow(64.0, 0.1),
      pow(128.0, 0.1),
      pow(256.0, 0.1),
      pow(512.0, 0.1)
   };

   float s;
   int iexp;

   s = frexpf (x, &iexp);
   s *= 2.0;
   iexp -= 1;

   div_t qr = div (iexp, 10);
   if (qr.rem < 0) {
      qr.quot -= 1;
      qr.rem += 10;
   }

   return ldexpf (powtenthnorm(s)*pow2_tenth[qr.rem], qr.quot);
}

// Returns x^0.1 for x in [1,2), to within 1.2e-7 (relative error).
// Want more precision? Add more Chebychev polynomial coefs.
float powtenthnorm (float x) {
   static const int N = 8;

   // Chebychev polynomial terms.
   // Non-zero terms calculated via
   //   integrate (2/pi)*ChebyshevT[n,u]/sqrt(1-u^2)*((u+3)/2)^0.1
   //   from -1 to 1
   // Zeroth term is similar except it uses 1/pi rather than 2/pi.
   static const float Cn[N] = {
       1.0386703502389972,
       3.55833786872637476e-2,
      -2.7458105122604368629e-3,
       2.9828558990819401155e-4,
      -3.70977182883591745389e-5,
       4.96412322412154169407e-6,
      -6.9550743747592849e-7,
       1.00572368333558264698e-7};

   float Tn[N];

   float u = 2.0*x - 3.0;


   Tn[0] = 1.0;
   Tn[1] = u;
   for (int ii = 2; ii < N; ++ii) {
      Tn[ii] = 2*u*Tn[ii-1] - Tn[ii-2];
   }

   float y = 0.0;
   for (int ii = N-1; ii > 0; --ii) {
      y += Cn[ii]*Tn[ii];
   }

   return y + Cn[0];
}

  • Deine Antwort ist wirklich inspirierend. Aber ich denke in eine andere Richtung: Let lg(x) = (log_2)^(x)Dann x^a = 2^(a * lg(x)) = 2^a * 2^(lg(x)). Ich frage mich, ob es einfacher ist, eine Optimierung durchzuführen lg und 2^x.

    – Schwarzball

    27. September 2013 um 8:38 Uhr


  • Da haben Sie recht x^a=2^(alog2(x))*. Der nächste Schritt ist falsch. 2^a * 2^(log2(x)) ist 2^(a+log2(x))was nicht gleich ist x^a.

    – David Hamm

    27. September 2013 um 12:01 Uhr


Benutzer-Avatar
Kuba hat Monica nicht vergessen

Eine generische Optimierung würde darin bestehen, die Cache-Lokalität zu verbessern. Halten Sie das Ergebnis zusammen mit den Argumenten fest. Verwenden powf Anstatt von pow vermeidet Zeitverschwendung durch eine Float-Double-Rundreise. Jeder vernünftige Compiler wird hochziehen 1/a aus der Schleife, das ist also kein Problem.

Sie sollten profilieren, ob eine möglicherweise vom Compiler durchgeführte automatische Vektorisierung getäuscht würde, indem Sie keine explizite Indizierung für den Zeiger auf das Array verwenden.

typedef struct {
  float x,y,z, re;
} element_t;

void heavy_load(element_t * const elements, const int num_elts, const float a) {
  element_t * elts = elements;
  for (i = 0; i < num_elts; ++ i) {
    elts->re = 
      powf((powf(elts->x, a) + powf(elts->y, a) + powf(elts->z, a)), 1.0/a);
    elts ++;
  }
}

Unter Verwendung von SSE2 SIMD würde es wie folgt aussehen. Ich verwende und füge einen Auszug aus Julien Pommiers ein sse_mathfun.h für die Potenzierung; die Lizenz ist unten angehängt.

#include <emmintrin.h>
#include <math.h>
#include <assert.h>

#ifdef _MSC_VER /* visual c++ */
# define ALIGN16_BEG __declspec(align(16))
# define ALIGN16_END
#else /* gcc or icc */
# define ALIGN16_BEG
# define ALIGN16_END __attribute__((aligned(16)))
#endif

typedef __m128 v4sf;  // vector of 4 float (sse1)
typedef __m128i v4si; // vector of 4 int (sse2)

#define _PS_CONST(Name, Val)                                            \
    static const ALIGN16_BEG float _ps_##Name[4] ALIGN16_END = { Val, Val, Val, Val }
#define _PI32_CONST(Name, Val)                                            \
    static const ALIGN16_BEG int _pi32_##Name[4] ALIGN16_END = { Val, Val, Val, Val }

_PS_CONST(1  , 1.0f);
_PS_CONST(0p5, 0.5f);
_PI32_CONST(0x7f, 0x7f);
_PS_CONST(exp_hi,   88.3762626647949f);
_PS_CONST(exp_lo,   -88.3762626647949f);
_PS_CONST(cephes_LOG2EF, 1.44269504088896341);
_PS_CONST(cephes_exp_C1, 0.693359375);
_PS_CONST(cephes_exp_C2, -2.12194440e-4);
_PS_CONST(cephes_exp_p0, 1.9875691500E-4);
_PS_CONST(cephes_exp_p1, 1.3981999507E-3);
_PS_CONST(cephes_exp_p2, 8.3334519073E-3);
_PS_CONST(cephes_exp_p3, 4.1665795894E-2);
_PS_CONST(cephes_exp_p4, 1.6666665459E-1);
_PS_CONST(cephes_exp_p5, 5.0000001201E-1);

v4sf exp_ps(v4sf x) {
    v4sf tmp = _mm_setzero_ps(), fx;
    v4si emm0;
    v4sf one = *(v4sf*)_ps_1;

    x = _mm_min_ps(x, *(v4sf*)_ps_exp_hi);
    x = _mm_max_ps(x, *(v4sf*)_ps_exp_lo);

    fx = _mm_mul_ps(x, *(v4sf*)_ps_cephes_LOG2EF);
    fx = _mm_add_ps(fx, *(v4sf*)_ps_0p5);

    emm0 = _mm_cvttps_epi32(fx);
    tmp  = _mm_cvtepi32_ps(emm0);

    v4sf mask = _mm_cmpgt_ps(tmp, fx);
    mask = _mm_and_ps(mask, one);
    fx = _mm_sub_ps(tmp, mask);

    tmp = _mm_mul_ps(fx, *(v4sf*)_ps_cephes_exp_C1);
    v4sf z = _mm_mul_ps(fx, *(v4sf*)_ps_cephes_exp_C2);
    x = _mm_sub_ps(x, tmp);
    x = _mm_sub_ps(x, z);

    z = _mm_mul_ps(x,x);

    v4sf y = *(v4sf*)_ps_cephes_exp_p0;
    y = _mm_mul_ps(y, x);
    y = _mm_add_ps(y, *(v4sf*)_ps_cephes_exp_p1);
    y = _mm_mul_ps(y, x);
    y = _mm_add_ps(y, *(v4sf*)_ps_cephes_exp_p2);
    y = _mm_mul_ps(y, x);
    y = _mm_add_ps(y, *(v4sf*)_ps_cephes_exp_p3);
    y = _mm_mul_ps(y, x);
    y = _mm_add_ps(y, *(v4sf*)_ps_cephes_exp_p4);
    y = _mm_mul_ps(y, x);
    y = _mm_add_ps(y, *(v4sf*)_ps_cephes_exp_p5);
    y = _mm_mul_ps(y, z);
    y = _mm_add_ps(y, x);
    y = _mm_add_ps(y, one);

    emm0 = _mm_cvttps_epi32(fx);
    emm0 = _mm_add_epi32(emm0, *(v4si*)_pi32_0x7f);
    emm0 = _mm_slli_epi32(emm0, 23);
    v4sf pow2n = _mm_castsi128_ps(emm0);
    y = _mm_mul_ps(y, pow2n);
    return y;
}

typedef struct {
    float x,y,z, re;
} element_t;   

void fast(element_t * const elements, const int num_elts, const float a) {
    element_t * elts = elements;
    float logf_a = logf(a);
    float logf_1_a = logf(1.0/a);
    v4sf log_a = _mm_load1_ps(&logf_a);
    v4sf log_1_a = _mm_load1_ps(&logf_1_a);
    assert(num_elts % 3 == 0); // operates on 3 elements at a time

    // elts->re = powf((powf(elts->x, a) + powf(elts->y, a) + powf(elts->z, a)), 1.0/a);
    for (int i = 0; i < num_elts; i += 3) {
        // transpose
        // we save one operation over _MM_TRANSPOSE4_PS by skipping the last row of output
        v4sf r0 = _mm_load_ps(&elts[0].x); // x1,y1,z1,0
        v4sf r1 = _mm_load_ps(&elts[1].x); // x2,y2,z2,0
        v4sf r2 = _mm_load_ps(&elts[2].x); // x3,y3,z3,0
        v4sf r3 = _mm_setzero_ps();        // 0, 0, 0, 0
        v4sf t0 = _mm_unpacklo_ps(r0, r1); //  x1,x2,y1,y2
        v4sf t1 = _mm_unpacklo_ps(r2, r3); //  x3,0, y3,0
        v4sf t2 = _mm_unpackhi_ps(r0, r1); //  z1,z2,0, 0
        v4sf t3 = _mm_unpackhi_ps(r2, r3); //  z3,0, 0, 0
        r0 = _mm_movelh_ps(t0, t1);        // x1,x2,x3,0
        r1 = _mm_movehl_ps(t1, t0);        // y1,y2,y3,0
        r2 = _mm_movelh_ps(t2, t3);        // z1,z2,z3,0
        // perform pow(x,a),.. using the fact that pow(x,a) = exp(x * log(a))
        v4sf r0a = _mm_mul_ps(r0, log_a); // x1*log(a), x2*log(a), x3*log(a), 0
        v4sf r1a = _mm_mul_ps(r1, log_a); // y1*log(a), y2*log(a), y3*log(a), 0
        v4sf r2a = _mm_mul_ps(r2, log_a); // z1*log(a), z2*log(a), z3*log(a), 0
        v4sf ex0 = exp_ps(r0a); // pow(x1, a), ..., 0
        v4sf ex1 = exp_ps(r1a); // pow(y1, a), ..., 0
        v4sf ex2 = exp_ps(r2a); // pow(z1, a), ..., 0
        // sum
        v4sf s1 = _mm_add_ps(ex0, ex1);
        v4sf s2 = _mm_add_ps(sum1, ex2);
        // pow(sum, 1/a) = exp(sum * log(1/a))
        v4sf ps = _mm_mul_ps(s2, log_1_a);
        v4sf es = exp_ps(ps);
        ALIGN16_BEG float re[4] ALIGN16_END;
        _mm_store_ps(re, es);
        elts[0].re = re[0];
        elts[1].re = re[1];
        elts[2].re = re[2];
        elts += 3;
    }
}


/* Copyright (C) 2007  Julien Pommier
  This software is provided 'as-is', without any express or implied
  warranty.  In no event will the authors be held liable for any damages
  arising from the use of this software.
  Permission is granted to anyone to use this software for any purpose,
  including commercial applications, and to alter it and redistribute it
  freely, subject to the following restrictions:
  1. The origin of this software must not be misrepresented; you must not
     claim that you wrote the original software. If you use this software
     in a product, an acknowledgment in the product documentation would be
     appreciated but is not required.
  2. Altered source versions must be plainly marked as such, and must not be
     misrepresented as being the original software.
  3. This notice may not be removed or altered from any source distribution. */

  • Wann immer funktioniert wie pow() beteiligt sind, ist die Cache-Kohärenz ein untergeordnetes Problem. Außerdem kaufe ich Ihre Behauptung, dass Sie Argumente und Ergebnisse zusammenhalten sollten, wirklich nicht ab – obwohl es möglich ist, dass es ein wenig hilft, denke ich, dass es genauso gut kontraproduktiv sein könnte. Können Sie hinter dieser Behauptung Zahlen nennen?

    – Cmaster – Wiedereinsetzung von Monica

    26. September 2013 um 6:06 Uhr

  • Ich stimme grundsätzlich zu; Dies ist wahrscheinlich stark rechenabhängig, sodass Probleme mit der Speichereffizienz im Vergleich wahrscheinlich relativ gering sind.

    – Oliver Charlesworth

    26. September 2013 um 10:13 Uhr

Benutzer-Avatar
Chux – Wiedereinsetzung von Monica

Einige C-Optimierungen – nicht viel, aber etwas.

[Edit]

Sorry – zurück zum ärgern: Wenn die FP-Werte stark schwanken dann doch recht häufig re = a (oder b oder c). Wenn Sie einen Test auf große Magnitudenunterschiede durchführen, wird die Notwendigkeit eines Anrufs zunichte gemacht pow() auf einigen von x, y oder z. Dies hilft der durchschnittlichen, aber nicht der ungünstigsten Zeit.

Ersetzen 1.0/a mit a_inverse die vor die Schleife gesetzt wird.

Ersetzen pow() mit powf()andernfalls rufen Sie die auf double Version von pow().

Minor: Initialisierung in float re[10000] = { 0.0f} wird nicht benötigt, außer um einen Speicher-Cache aufzufrischen.

Minor: Die Verwendung eines Zeigers anstelle der Indizierung von Arrays kann etwas sparen.

Minor: Verwenden Sie für ausgewählte Plattformen den Typ double kann schneller laufen. – Umkehrung meiner oben pow()/powf() Kommentar.

Minor: Versuchen Sie 3 separate Arrays von x,y,z. Jeder float *.

Natürlich hilft Profiling, aber nehmen wir an, das ist hier gut bekannt.

  • Trennung der Arrays ist Schlecht Rat. Es unterbricht die Cache-Kohärenz.

    – Kuba hat Monica nicht vergessen

    26. September 2013 um 4:26 Uhr

  • @Kuba Ober Stimme dem Problem der Cache-Kohärenz im Allgemeinen zu, aber ich weiß nicht einmal, dass die Systeme von OP einen Cache haben (vielleicht ist es irgendwo angegeben und ich habe es verpasst). Ich behaupte, es ist kein schlechter Rat, es zu versuchen – nur vielleicht hilft es nicht. Profiling wird der Richter sein.

    – chux – Wiedereinsetzung von Monica

    26. September 2013 um 4:36 Uhr

  • Ich glaube nicht, dass es da draußen vernünftige Hardware mit einer Gleitkommaeinheit gibt, die C ausführt und keinen Cache hat. Cache-Kohärenz ist so etwas wie die grundlegendste Sache, nach der man jemals suchen sollte, also ist es eine voreilige Pessimierung, etwas anderes vorzuschlagen. Du hättest es nicht vorschlagen sollen. Es wird noch nie Hilfe. Auf jeder derzeit verkauften Hardware wird dies der Fall sein stets alles noch schlimmer machen. Ich meine es so.

    – Kuba hat Monica nicht vergessen

    26. September 2013 um 4:37 Uhr


  • @Kuba Ober, der sich auf eingebettete Prozessoren als mickrigen 8-Bit-Mikrocontroller bezieht, ist mit derzeit gängigen 16- und 32-Bit-Prozessoren nicht auf dem neuesten Stand. Die Frage von OP behauptet keine Plattform. Fragen zur Leistung von FP-Mathematik treten häufig sowohl in PICs als auch in Vektorprozessoren auf. Das Ganze im Assembler zu codieren, ist nur eine Option für einen schrumpfenden Teil des wachsenden Embedded-Marktsegments.

    – chux – Wiedereinsetzung von Monica

    26. September 2013 um 5:09 Uhr

  • @Kuba Ober Wieder falsch, da ich die Referenz nicht verpasst habe. OP “… ziehen es vor, SIMD nicht zu verwenden”. Ich habe dies so verstanden, dass OP aus irgendeinem Grund die Verwendung dieser Funktion vermeiden wollte, die in dieser Klasse von Parallelcomputern verfügbar ist. Vielleicht, um Code auf Maschinen schnell zu machen, die diese Fähigkeit nicht hatten.

    – chux – Wiedereinsetzung von Monica

    26. September 2013 um 5:26 Uhr

1199410cookie-checkWie könnte ich diese Berechnung optimieren? (x^a + y^a +z^a)^(1/a)

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy