C ++ 'da uzun denklemleri uygularken üst düzey bir yaklaşımla performansı nasıl artırabilirim


92

Bazı mühendislik simülasyonları geliştiriyorum. Bu, kauçuk benzeri bir malzemedeki gerilimi hesaplamak için bu denklem gibi bazı uzun denklemlerin uygulanmasını içerir:

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;

Hatalardan kaçınmak (ve sıkıcı cebirle zamandan tasarruf etmek) için C ++ kodunu oluşturmak için Maple kullanıyorum. Bu kod binlerce (milyonlarca değilse) kez yürütüldüğünden, performans bir endişe kaynağıdır. Ne yazık ki matematik şimdiye kadar sadece basitleştiriyor; uzun denklemler kaçınılmazdır.

Bu uygulamayı optimize etmek için hangi yaklaşımı benimseyebilirim? Bu tür denklemleri uygularken uygulamam gereken üst düzey stratejiler arıyorum, yukarıda gösterilen örnek için mutlaka belirli optimizasyonlar değil.

G ++ ile --enable-optimize=-O3.

Güncelleme:

Tekrarlanan birçok ifade olduğunu biliyorum, derleyicinin bunları halledeceği varsayımını kullanıyorum; şimdiye kadarki testlerim öyle olduğunu gösteriyor.

l1, l2, l3, mu, a, K hepsi pozitif gerçek sayılardır (sıfır değil).

Ben yerini almıştır l1*l2*l3eşdeğer bir değişken ile: J. Bu, performansı artırmaya yardımcı oldu.

Değiştirme pow(x, 0.1e1/0.3e1)ile cbrt(x)iyi bir öneri oldu.

Bu, CPU'larda çalıştırılacak, yakın gelecekte bu muhtemelen GPU'larda daha iyi çalışacak, ancak şimdilik bu seçenek mevcut değil.


32
Peki akla gelen ilk şey (derleyici kendisini optimize etmedikçe) hepsini pow(l1 * l2 * l3, -0.1e1 / 0.3e1)bir değişkenle değiştirmektir ... Yine de, hızlı mı yoksa yavaş mı çalıştığından emin olmak için kodunuzu kıyaslamanız gerekir.
SingerOfTheFall

6
Ayrıca kodu daha okunaklı hale getirmek için biçimlendirin - iyileştirme olasılıklarının belirlenmesine yardımcı olabilir.
Ed Heal

26
Neden tüm olumsuz oylar ve kapatma oyu veriyor? Sayısal veya bilimsel programlamayı sevmeyenleriniz için diğer sorulara bakın. Bu, bu siteye çok uygun olan iyi bir sorudur. Scicomp sitesi hala beta; orada göç iyi bir seçenek değil. Kod inceleme sitesi yeterince bilim insanı gözü almıyor. OP'nin bilimsel hesaplamada oldukça sık yaptığı şey: Sembolik bir matematik programında bir problem oluşturun, programdan kod üretmesini isteyin ve sonuca dokunmayın çünkü üretilen kod tam bir karmaşa.
David Hammen

6
@DavidHammen Kod İnceleme sitesi yeterince bilim bakış açısına sahip değil - kulağa tavuk ve yumurta problemi gibi geliyor ve CR'nin bu tür gözlerden daha fazlasını elde etmesine yardımcı olmayan bir zihniyet. Aynı şey, scicomp beta sitesini devre dışı bırakma fikri için de geçerlidir çünkü beta sitedir - eğer herkes böyle düşünseydi, büyüyecek tek site Stack Overflow olurdu.
Mathieu Guindon

13
Bu soru meta üzerinde tartışılıyor burada
NathanOliver

Yanıtlar:


88

Özeti düzenleyin

  • İlk cevabım sadece kodun çok sayıda tekrarlanmış hesaplama içerdiğini ve güçlerin çoğunun 1 / 3'lük faktörleri içerdiğini belirtti. Örneğin, pow(x, 0.1e1/0.3e1)ile aynıdır cbrt(x).
  • İkinci düzenlemem tamamen yanlıştı ve üçüncü düzenlemem bu yanlışlık üzerine yorum yaptı. İnsanları 'M' harfiyle başlayan sembolik matematik programlarının kahin benzeri sonuçları değiştirmekten korkmasına neden olan şey budur. Bu düzenlemeleri kaldırdım (yani, grev yaptım ) ve bunları bu cevabın mevcut revizyonunun altına ittim. Ancak onları silmedim. Ben insanım. Hata yapmak bizim için çok kolay.
  • Dördüncü düzenleme doğru, söz konusu dolambaçlı ifadesini temsil ettiği çok kompakt ifadesini geliştirdi EĞER parametreleri l1, l2ve l3pozitif reel sayılardır ve eğer asıfır olmayan gerçek sayıdır. (Bu katsayıların özel doğası hakkında OP'den henüz haber alamadık. Sorunun doğası göz önüne alındığında, bunlar makul varsayımlardır.)
  • Bu düzenleme, bu ifadelerin nasıl basitleştirileceğine ilişkin genel soruna cevap vermeye çalışır.

Her şey sırayla

Hatalardan kaçınmak için C ++ kodunu oluşturmak için Maple kullanıyorum.

Maple ve Mathematica bazen bariz olanı gözden kaçırır. Daha da önemlisi, Maple ve Mathematica kullanıcıları bazen hata yapar. "Çoğu zaman" veya hatta "neredeyse her zaman" yerine bazen "bazen" işarete daha yakındır.

Maple'ın söz konusu parametreleri anlatarak bu ifadeyi basitleştirmesine yardımcı olabilirdiniz. Eldeki örnekte, şüpheli l1, l2ve l3olan pozitif reel sayılar ve asıfır olmayan gerçek sayıdır. Eğer durum buysa, bunu söyle. Bu sembolik matematik programları tipik olarak eldeki miktarların karmaşık olduğunu varsayar. Etki alanını kısıtlamak, programın karmaşık sayılarda geçerli olmayan varsayımlar yapmasına izin verir.


Sembolik matematik programlarındaki bu büyük karışıklıklar nasıl basitleştirilir (bu düzenleme)

Sembolik matematik programları tipik olarak çeşitli parametreler hakkında bilgi sağlama yeteneği sağlar. Bu yeteneği kullanın, özellikle de sorununuz bölme veya üs alma içeriyorsa. Eldeki örnekte, Maple bu kadar anlatarak bu ifadeyi basitleştirmek yardımcı olabilirdi l1, l2ve l3olan pozitif reel sayılar ve asıfır olmayan gerçek sayıdır. Eğer durum buysa, bunu söyle. Bu sembolik matematik programları tipik olarak eldeki miktarların karmaşık olduğunu varsayar. Etki alanını kısıtlamak, programın x b x = (ab) x gibi varsayımlar yapmasına izin verir . Bu yalnızca olduğunu ave bpozitif reel sayılardır ve eğer xgerçek. Karmaşık sayılarda geçerli değildir.

Sonuçta, bu sembolik matematik programları algoritmaları takip eder. Yardım edin. Kodu oluşturmadan önce genişletmeyi, toplamayı ve basitleştirmeyi deneyin. Bu durumda, onlardan bir faktör içeren terimler toplanmış olabilir muve bir faktör içerenler K. Bir ifadeyi "en basit biçimine" indirgemek biraz sanattır.

Üretilen kodun çirkin bir karmaşasını gördüğünüzde, bunu dokunmamanız gereken bir gerçek olarak kabul etmeyin. Kendiniz basitleştirmeye çalışın. Sembolik matematik programının kodu oluşturmadan önce neye sahip olduğuna bakın. İfadenizi nasıl çok daha basit ve daha hızlı bir şeye indirdiğime ve Walter'ın cevabının benim birkaç adım daha ileriye gittiğine bakın . Büyülü tarif yok. Büyülü bir tarif olsaydı, Maple onu uygular ve Walter'ın verdiği cevabı verirdi.


Spesifik soru hakkında

Bu hesaplamada çok fazla toplama ve çıkarma yapıyorsunuz. Neredeyse birbirini iptal eden şartlarınız varsa başınız büyük belaya girebilir. Diğerlerine hakim olan bir teriminiz varsa, çok fazla CPU harcıyorsunuz.

Ardından, tekrarlanan hesaplamalar yaparak çok fazla CPU harcarsınız. -ffast-mathDerleyicinin IEEE kayan noktasının bazı kurallarını ihlal etmesine izin veren etkinleştirmediyseniz , derleyici bu ifadeyi sizin için basitleştirmeyecektir (aslında, yapmamalıdır). Bunun yerine tam olarak ne yapmasını söylediğinizi yapacak. En azından, l1 * l2 * l3bu karışıklığı hesaplamadan önce hesaplamalısınız.

Son olarak, powçok yavaş arama yapıyorsunuz . Bu aramaların birçoğunun (l1 * l2 * l3) (1/3) biçiminde olduğuna dikkat edin . Bu çağrıların çoğu powtek bir çağrı ile gerçekleştirilebilir std::cbrt:

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

Bununla,

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


Maple bariz olanı kaçırdı.
Örneğin, yazmanın çok daha kolay bir yolu var

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

Varsayarak l1, l2ve l3gerçek oldukça karmaşık sayılar daha, ve gerçek küp kök (yerine prensibi kompleks kök dışında) ekstre edilmesi için olan, yukarıda azaltır

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

veya

2.0/(3.0 * l123_pow_1_3)

Kullanılması cbrt_l123yerine l123_pow_1_3, söz konusu pis ifade etmek azaltır

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

Her zaman iki kez kontrol edin, ancak her zaman basitleştirin.


Yukarıdakilere ulaşmak için attığım adımlardan bazıları:

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


Yanlış cevap, kasıtlı olarak alçakgönüllülük için tutuldu

Bunun etkilendiğini unutmayın. Yanlış.

Güncelleme

Maple bariz olanı kaçırdı. Örneğin, yazmanın çok daha kolay bir yolu var

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

Varsayarak l1, l2ve l3karmaşık sayılar yerine gerçek yerine, ve gerçek küp kök (yerine prensibi kompleks kök) vardır ekstre edilmesi, yukarıda sıfıra düşer. Bu sıfır hesaplaması defalarca tekrarlanır.

İkinci güncelleme

Matematiği doğru yaptıysam (matematiği doğru yaptığımın garantisi yoktur ), sorudaki çirkin ifade şu şekilde azalır:

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

Yukarıdaki varsayar l1, l2ve l3pozitif reel sayılardır.


2
Eh, CSE-eleme olmalıdır rahat semantik bağımsız çalışır (ve OP öyle açıklamalarda gösterilmiş). Elbette önemliyse (ölçülürse), incelenmesi gerekir (oluşturulan montaj). Hakim terimlere, cevapsız formül basitleştirmelerine, daha özel işlevlere ve iptal etmenin tehlikelerine ilişkin görüşleriniz çok iyidir.
Deduplicator

3
@Deduplicator - Kayan noktayla değil. Güvenli olmayan matematik optimizasyonları etkinleştirilmedikçe (örneğin, -ffast-mathgcc veya clang ile belirterek ), derleyici pow(x,-1.0/3.0)eşit olduğuna güvenemez x*pow(x,-4.0/3.0). İkincisi alttan akabilirken ilki olmayabilir. Kayan nokta standardıyla uyumlu olmak için, derleyicinin bu hesaplamayı sıfıra optimize etmemesi gerekir.
David Hammen

Bunlar benim kastettiğim her şeyden çok daha iddialı.
Deduplicator

1
@Deduplicator: Başka bir cevaba yorum yaptığım gibi : -fno-math-errnog ++ ile CSE arası aynı powçağrılara ihtiyacınız var. (Güçün errno ayarlamasına gerek olmadığını kanıtlayamazsa?)
Peter Cordes

1
@Lefti - Walter'ın cevabına çok şey katın. İyi bir anlaşma daha hızlı. Tüm bu cevaplarda potansiyel bir sorun var, sayısal iptal. Sizin N1, N2ve N3negatif olmadığını varsayarsak , biri 2*N_i-(N_j+N_k)negatif olacak, biri pozitif olacak ve diğeri ikisinin arasında bir yerde olacaktır. Bu, kolayca sayısal iptal sorunlarına neden olabilir.
David Hammen

32

Unutulmaması gereken ilk şey, bunun powgerçekten pahalı olduğu, bu yüzden bundan mümkün olduğunca kurtulmalısınız. İfade aracılığıyla Tarama ben çok tekrarlar bkz pow(l1 * l2 * l3, -0.1e1 / 0.3e1)ve pow(l1 * l2 * l3, -0.4e1 / 0.3e1). Bu nedenle, bunları önceden hesaplamaktan büyük bir kazanç bekliyorum:

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

boost pow işlevini kullanıyorum.

Dahası, powüslü biraz daha var a. Eğer atamsayı ve derleyici anda bilinen, aynı zamanda olanlar yerini alabilir boost::math::pow<a>(...), daha fazla performans elde etmek. Ben de gibi terimleri yerine öneririm a / l1 / 0.3e1ile a / (l1 * 0.3e1)çarpma daha hızlı daha sonra bölümüdür olarak.

Son olarak, g ++ kullanırsanız -ffast-math, eniyileyicinin denklemleri dönüştürmede daha agresif olmasına izin veren bayrağı kullanabilirsiniz . Yine de yan etkileri olduğu için bu bayrağın gerçekte ne yaptığını okuyun .


5
Kodumuzda kullanmak -ffast-math, kodun kararsız hale gelmesine veya kesin olarak yanlış cevaplar vermesine yol açar. Intel derleyicileriyle benzer bir sorunumuz var ve bu -fp-model preciseseçeneği kullanmak zorundayız , aksi takdirde kod ya patlar ya da yanlış yanıtlar verir. Bu yüzden -ffast-mathhızlandırabilir, ancak bağlantılı sorunuzda listelenen yan etkilere ek olarak bu seçeneğe çok dikkatli bir şekilde devam etmenizi öneririm.
tpg2114

2
@ tpg2114: Testlerime göre-fno-math-errnopow , bir döngüden aynı çağrıları kaldırabilmek için yalnızca g ++ 'ya ihtiyacınız var . Çoğu kod için -fast matematiğin en az "tehlikeli" kısmı budur.
Peter Cordes

1
@PeterCordes Bunlar ilginç sonuçlar! Ayrıca pow son derece yavaş olmakla ilgili sorunlar yaşadık ve dlsymaslında biraz daha az hassasiyetle yapabildiğimizde önemli performans artışları elde etmek için yorumlarda belirtilen hack'i kullanmaya başladık.
tpg2114

GCC, yetkinin saf bir işlev olduğunu anlamaz mı? Muhtemelen yerleşik bilgidir.
usr

6
@usr: Bence asıl mesele bu. powolduğu değil o sette gerekiyordu çünkü standardına göre, saf bir fonksiyon errnobazı durumlarda. Gibi ayarlama bayraklar -fno-math-errnoo kadar sebep değil set errno(böylece standardı ihlal), ama sonra saf fonksiyondur ve bu şekilde optimize edilebilir.
Nate Eldredge

20

Woah, ne cehennem bir ifade. Maple ile ifade yaratmak aslında burada yetersiz bir seçimdi. Sonuç basitçe okunamaz.

  1. konuşulan değişken isimlerini seçti (l1, l2, l3 değil, ama örneğin yükseklik, genişlik, derinlik, eğer kastettikleri buysa). O zaman kendi kodunuzu anlamanız daha kolaydır.
  2. Birden çok kez kullandığınız alt terimleri önceden hesaplayın ve sonuçları konuşan adlarla değişkenler halinde saklayın.
  3. İfadenin birçok kez değerlendirildiğini söylüyorsunuz. Sanırım, en iç döngüde yalnızca birkaç parametre değişir. Bu döngüden önceki tüm değişmez alt terimleri hesaplayın. İkinci iç döngü için tekrarlayın ve tüm değişmezler döngünün dışında olana kadar devam edin.

Teorik olarak derleyici tüm bunları sizin için yapabilmelidir, ancak bazen yapamaz - örneğin, döngü yuvalama farklı derleme birimlerindeki birden çok işlev üzerine yayıldığında. Her neyse, bu size çok daha iyi okunabilir, anlaşılır ve bakımı yapılabilir bir kod verecektir.


8
"derleyici bunu yapmalı, ama bazen yapmıyor", burada anahtar nokta. okunabilirliğin yanı sıra elbette.
Javier

3
Derleyicinin bir şey yapması gerekmiyorsa, bunun neredeyse her zaman yanlış olduğunu varsayarsak.
edmz

4
Konuşan değişken isimlerini yeniden seçin - Matematik yaparken çoğu zaman bu güzel kural uygulanmaz. Bilimsel bir dergide bir algoritma uygulaması gereken koda bakarken, koddaki sembollerin tam olarak dergide kullanılanlar olmasını tercih ederim. Tipik olarak, bu, muhtemelen bir alt simge ile son derece kısa adlar anlamına gelir.
David Hammen

8
"Sonuç okunamıyor" - bu neden bir sorun? Bir sözlükçü veya ayrıştırıcı-oluşturucudan gelen yüksek seviyeli dil çıktısının "okunamaz" (insanlar tarafından) olması umrunda olmazdı. Burada önemli olan, kod oluşturucuya (Maple) girişin okunabilir ve kontrol edilebilir olmasıdır. Hatasız olduğundan emin olmak istiyorsanız, yapılmaması gereken şey , üretilen kodu elle düzenlemektir.
alephzero

3
@DavidHammen: Eh, bu durumda, tek harfli olanlar vardır "konuşan isimleri". Örneğin, bir 2-boyutlu bir kartezyen geometri yaparken koordinat sistemi, xve yvardır değil de bütün olarak, anlamsız tek harfli değişkenleri kelime kesin bir tanım ve iyi ve yaygın olarak anlaşılan anlamla.
Jörg W Mittag

17

David Hammen'in cevabı iyi, ancak yine de optimal olmaktan uzak. Son ifadesine devam edelim (bunu yazarken)

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

daha da optimize edilebilir. Özellikle, bazı matematiksel kimliklerden yararlanılıyorsa cbrt(), çağrıyı ve çağrılardan birini önleyebiliriz pow(). Bunu adım adım tekrar yapalım.

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

Ayrıca 2.0*N1, N1+N1vb. İçin optimize ettiğimi unutmayın . Daha sonra, yalnızca iki çağrı ile yapabiliriz 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);

Çağrılar, pow()buradaki en maliyetli işlem olduğundan, onları olabildiğince azaltmaya değer (bir sonraki maliyetli işlem, cbrt()ortadan kaldırdığımız aramaydı).

Herhangi bir şans eseri atamsayı ise, çağrılar çağrılara powoptimize edilebilir cbrt(artı tamsayı güçleri) veya athirdyarım tamsayı ise sqrt(artı tamsayı güçleri) kullanabiliriz. Bundan başka, bir ihtimal ise l1==l2ya da l1==l3ya da l2==l3bir veya her iki çağrılara powelimine edilebilir. Dolayısıyla, gerçekçi bir şekilde böyle bir şans varsa, bunları özel durumlar olarak düşünmeye değer.


@gnat Düzenlemenizi takdir ediyorum (bunu kendim yapmayı düşündüm), ancak David'in cevabı da bununla bağlantılı olsaydı daha adil bulabilirdim. Neden David'in cevabını da benzer şekilde düzenlemiyorsunuz?
Walter

1
Ben sadece sizin açıkça bahsettiğinizi gördüğüm için düzenledim; David'in cevabını tekrar okudum ve orada cevabınıza referans bulamadım. Eklediğim şeylerin yazarın niyetleriyle eşleştiğinin% 100 net olmadığı durumlarda düzenlemelerden kaçınmaya çalışıyorum
gnat

1
@Walter - Cevabım artık sizinkine bağlanıyor.
David Hammen

1
Kesinlikle ben değildim. Cevabınızı birkaç gün önce onayladım. Ayrıca cevabıma rastgele bir aşağı oylama aldım. Bazen şeyler oluyor.
David Hammen

1
Sen ve ben her birine önemsiz bir olumsuz oy aldık. Sorudaki tüm olumsuz oylara bakın! Şu an itibariyle, soru 16 olumsuz oy aldı. Ayrıca, olumsuz oy kullananların hepsini dengelemekten daha fazlasını yapan 80 olumlu oy aldı.
David Hammen

12
  1. Kaç tane "çok" var?
  2. Ne kadar sürer?
  3. Do TÜM parametreler bu formülün yeniden hesaplama arasında geçiş? Veya önceden hesaplanmış bazı değerleri önbelleğe alabilir misiniz?
  4. Bu formülün manuel olarak basitleştirilmesini denedim, herhangi bir şey kaydedip kaydetmediğini bilmek ister misiniz?

    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;
    

[EKLENDİ] Son üç satırlık formül üzerinde biraz daha çalıştım ve bu güzelliğe indirgedim:

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

Çalışmamı adım adım göstermeme izin verin:

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)

2
Bu farkedilir mi? :) FORTRAN, IIRC, verimli formül hesaplamaları için tasarlanmıştır ("FOR" formül içindir).
Vlad Feinstein

Gördüğüm çoğu F77 kodu buna benziyordu (örneğin, BLAS & NR). Fortran 90-> 2008'in varlığından çok memnunum :)
Kyle Kanos

Evet. Bir formülü tercüme ediyorsanız, FORmulaTRANslation'tan daha iyi ne olabilir?
Brian Drummond

1
'Optimizasyonunuz' yanlış yere saldırıyor. Pahalı bitler, std::pow()gerekenden 6, 3 kat daha fazlasına sahip olduğunuz aramalardır . Başka bir deyişle, kodunuz mümkün olandan 3 kat daha yavaştır.
Walter

7

Bu biraz veciz olabilir, ama aslında temelde yeniden yazar Horner Formu kullanarak polinomlar için iyi hızlanma (enerji fonksiyonlarının enterpolasyon) buldum ax^3 + bx^2 + cx + dolarak d + x(c + x(b + x(a))). Bu kadar sürekli çağrı bir çok önleyecektir pow()ve ayrı ayrı arayarak gibi saçma şeyleri yapmaktan durur pow(x,6)ve pow(x,7)bunun yerine sadece yapmanın x*pow(x,6).

Bu, mevcut sorununuz için doğrudan geçerli değildir, ancak tamsayı güçleri olan yüksek dereceli polinomlara sahipseniz yardımcı olabilir. İşlemlerin sırası bunun için önemli olduğu için sayısal kararlılık ve taşma sorunlarına dikkat etmeniz gerekebilir (ancak genel olarak Horner Form'un bunun için yardımcı olduğunu düşünüyorum, çünkü x^20ve xgenellikle birbirinden çok farklıdır).

Ayrıca pratik bir ipucu olarak, henüz yapmadıysanız, önce akçaağaçtaki ifadeyi basitleştirmeye çalışın. Muhtemelen sizin için yaygın alt ifade eleme işlemlerinin çoğunu yapmasını sağlayabilirsiniz. Özellikle bu programdaki kod oluşturucuyu ne kadar etkilediğini bilmiyorum, ancak Mathematica'da kodu oluşturmadan önce FullSimplify yapmanın çok büyük bir fark yaratabileceğini biliyorum.


Horner formu, polinomları kodlamak için oldukça standarttır ve bunun soruyla hiçbir ilgisi yoktur.
Walter

1
Örneğine bakıldığında bu doğru olabilir, ancak "bu türden denklemler" dediğini fark edeceksiniz. Posterin sisteminde herhangi bir polinom varsa, cevabın yararlı olacağını düşündüm. Mathematica ve Maple gibi CAS programları için kod üreteçlerinin, siz özellikle istemediğiniz sürece size Horner formu VERMEME eğiliminde olduklarını özellikle fark ettim; bir polinomu tipik olarak bir insan olarak yazdığınız şekle bürünürler.
neocpp

3

Görünüşe göre çok sayıda tekrarlanan işleminiz var.

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

Bunları önceden hesaplayabilirsiniz, böylece powpahalı olabilecek işlevi tekrar tekrar çağırmazsınız .

Ayrıca önceden hesaplayabilirsiniz.

l1 * l2 * l3

bu terimi tekrar tekrar kullandığınızda.


6
Bahse girerim optimizer bunu sizin için zaten yapıyor ... yine de en azından kodu daha okunaklı hale getiriyor.
Karoly Horvath

Bunu yaptım ama işleri hiç hızlandırmadı. Bunun, derleyici optimizasyonunun halihazırda bununla ilgilenmesi olduğunu düşündüm.

l1 * l2 * l3'ü depolamak işleri hızlandırır, ancak derleyici optimizasyonuyla neden emin değilsiniz

çünkü derleyici bazen bazı optimizasyonları yapamaz veya bunları diğer seçeneklerle çelişki içinde bulamaz.
Javier

1
Aslında, derleyici -ffast-mathetkinleştirilmedikçe bu optimizasyonları yapmamalıdır ve @ tpg2114 tarafından yapılan bir yorumda belirtildiği gibi, bu optimizasyon son derece istikrarsız sonuçlar yaratabilir.
David Hammen

0

Bir Nvidia CUDA grafik kartınız varsa, hesaplamaları grafik kartına aktarmayı düşünebilirsiniz - bu, hesaplama açısından karmaşık hesaplamalar için daha uygundur.

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

Değilse, hesaplamalar için birden çok iş parçacığı kullanmayı düşünebilirsiniz.


10
Bu cevap, eldeki soruya ortogonaldir. GPU'larda çok sayıda işlemci olsa da, CPU ile gömülü FPU ile karşılaştırıldığında oldukça yavaştırlar. Bir GPU ile tek bir seri hesaplama yapmak büyük bir kayıptır. CPU'nun ardışık düzeni GPU ile doldurması, yavaş GPU'nun bu tek görevi gerçekleştirmesini beklemesi ve ardından sonucu kaldırması gerekir. GPU'lar, eldeki sorun büyük ölçüde paralelleştirilebilir olduğunda kesinlikle harika olsa da, seri görevleri yerine getirme söz konusu olduğunda mutlak acımasızdırlar.
David Hammen

1
Orijinal soruda: "Bu kod birçok kez çalıştırıldığı için performans bir endişe kaynağıdır." Bu "birçok" dan bir fazlası. Operatör, hesaplamaları dişli bir şekilde gönderebilir.
user3791372

0

Şans eseri, hesaplamayı sembolik olarak sağlayabilir misiniz? Vektör işlemleri varsa, bazı durumlarda işlemleri paralel olarak çalıştırabilen blas veya lapack kullanarak gerçekten araştırma yapmak isteyebilirsiniz.

Python'u numpy ve / veya scipy ile kullanabilmeniz düşünülebilir (konu dışı olma riski altında mı?). Mümkün olduğu ölçüde, hesaplamalarınız daha okunaklı olabilir.


0

Yüksek seviyeli optimizasyonları açıkça sorduğunuz gibi, farklı C ++ derleyicileri denemeye değer olabilir. Günümüzde, derleyiciler çok karmaşık optimizasyon canavarlarıdır ve CPU satıcıları çok güçlü ve özel optimizasyonlar uygulayabilir. Ancak bazılarının ücretsiz olmadığını lütfen unutmayın (ancak ücretsiz bir akademik program olabilir).

  • GNU derleyici koleksiyonu ücretsiz, esnektir ve birçok mimaride mevcuttur
  • Intel derleyicileri çok hızlı, çok pahalıdır ve AMD mimarileri için de iyi sonuçlar verebilir (akademik bir program olduğuna inanıyorum)
  • Clang derleyicileri hızlı, ücretsizdir ve GCC'ye benzer sonuçlar üretebilir (bazıları daha hızlı ve daha iyi olduklarını söylüyor, ancak bu her uygulama durumu için farklı olabilir, kendi deneyimlerinizi oluşturmanızı öneririm)
  • PGI (Portland Group), Intel derleyicileri olarak ücretsiz değildir.
  • PathScale derleyicileri, AMD mimarileri üzerinde iyi sonuçlar verebilir

Kod parçacıkları yürütme hızının 2 katına göre farklılık gösterdiğini gördüm, sadece derleyiciyi değiştirerek (tabii ki tam optimizasyonlarla). Ancak çıktının kimliğini kontrol etme konusunda dikkatli olun. Agresif optimizasyon, farklı çıktılara yol açabilir, bu kesinlikle kaçınmak isteyeceğiniz bir şeydir.

İyi şanslar!

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.