12 Soru: GCC neden * a * a * a * a * a ila (a * a * a) * (a * a * a) seviyesini optimize etmiyor?

tarafından oluşturulan soru Fri, Feb 3, 2017 12:00 AM

Bilimsel bir uygulamada bazı sayısal optimizasyonlar yapıyorum. Fark ettiğim bir şey, GCC'nin pow(a,2)'u a*a'a derleyerek arama pow(a,6)'u optimize edeceği, ancak pow araması'nın optimize edilmediği ve aslında performansı büyük ölçüde yavaşlatan icc kütüphane işlevini çağıracağı. (Buna karşılık, Intel C ++ Derleyici , çalıştırılabilir pow(a,6), pow(a,6) numaralı kütüphane çağrısını ortadan kaldıracaktır.)

Merak ediyorum, GCC 4.5.1 ve 49110 seçeneklerini kullanarak a*a*a*a*a*a'u -O3 -lm -funroll-loops -msse4 ile değiştirdiğimde, 5 mulsd komutunu kullanması:

 
movapd  %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm14, %xmm13

(a*a*a)*(a*a*a) yazarsam, üretecek

 
movapd  %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm13, %xmm13
çarpma talimatlarının sayısını 3'e indiren

. icc da benzer davranışa sahiptir.

Derleyiciler bu optimizasyon numarasını neden tanımıyor?

    
2054
  1. "pow (a, 6)" tanımak ne anlama geliyor?
    2011-06-21 18: 56: 13Z
  2. Um ... biliyorsunuz ki bir a a a a a ve (a a a) * (a a * a) kayan nokta sayıları ile aynı değildir, değil mi? Bunun için -funsafe-math veya -ffast-math veya başka bir şey kullanmanız gerekecek.
    2011-06-21 18: 57: 50Z
  3. David Goldberg tarafından "Her Bilgisayar Bilim Adamının Kayan Nokta Aritmetiği Hakkında Bilmeniz Gerekenler" bölümünü okumanızı öneririz: indir.oracle.com/docs/cd/E19957-01/806-3568/… Daha önce girdiğiniz katran ocağı hakkında daha kapsamlı bir anlayışa sahip olacağım!
    2011-06-22 09: 20: 54Z
  4. Mükemmel bir soru. 20 yıl önce aynı genel soruyu sordum ve bu tek tıkanıklığı ezerek, Monte Carlo simülasyonunun uygulama süresini 21 saatten 7 saate düşürdüm. İç döngüdeki kod, işlem sırasında 13 trilyon kez gerçekleştirildi, ancak simülasyonu bir gece penceresine getirdi. (aşağıdaki cevaba bakınız)
    2012-12-21 03: 47: 57Z
  5. Belki de (a*a)*(a*a)*(a*a)'u karışıma da atın. Aynı çarpma sayısı, ancak muhtemelen daha doğru.
    2015-08-11 17: 18: 38Z
12 Yanıtlar                              12                         

Çünkü, Kayan Nokta Matematiği İlişkisel Değildir . İşlenenleri kayan nokta çarpımında gruplama şeklinin cevabın sayısal doğruluğu üzerinde etkisi vardır.

Sonuç olarak, çoğu derleyici, yanıtın aynı kalacağından emin olmadıkça veya sayısal doğrulukla ilgilenmediğinizi söylemediğiniz sürece kayan nokta hesaplamalarını yeniden sıralama konusunda çok tutucudur. Örneğin: gcc'nin kayan noktayı yeniden ilişkilendirmesini sağlayan gcc'nin -fassociative-math seçeneği operasyonlar, hatta hıza karşı daha agresif bir doğruluk traversyonu sağlayan -ffast-math opsiyonu.

    
2656
2014-06-26 04: 02: 42Z
  1. Evet. -Ffast-math ile böyle bir optimizasyon yapıyor. İyi bir fikir! Ancak, kodumuz hızdan daha fazla doğrulukla ilgili olduğu için onaylamamak daha iyi olabilir.
    2011-06-21 19: 09: 42Z
  2. IIRC C99, derleyicinin bu tür "güvensiz" FP optimizasyonları yapmasına izin verir, ancak GCC (x87 dışındaki herhangi bir şey) IEEE 754'ü takip etmek için makul bir girişimde bulunur -"hata sınırları" değil; yalnızca bir doğru cevap var .
    2011-06-22 02: 19: 45Z
  3. pow'un uygulama ayrıntıları burada veya burada değil; bu cevap pow'a bile başvurmuyor.
    2013-01-03 02: 19: 48Z
  4. @ nedR: Yeniden birleştirmeye izin vermek için ICC varsayılanları. Standart uyumlu davranış elde etmek istiyorsanız, -fp-model precise’u ICC ile ayarlamanız gerekir. clang ve gcc varsayılan olarak sıkı uygunluk w.r.t. Yeniden ilişkilendirme.
    2014-03-27 18: 19: 26Z
  5. @ xis, bu gerçekten -fassociative-math'un yanlış olacağı anlamına gelmez; Sadece a*a*a*a*a*a ve (a*a*a)*(a*a*a) farklı. Bu doğrulukla ilgili değil; standartlara uygunluk ve kesin olarak tekrarlanabilir sonuçlar ile ilgilidir, örn. Herhangi bir derleyicide aynı sonuçlar. Kayan nokta sayıları zaten kesin değil. -fassociative-math ile derlemek nadiren uygunsuzdur.
    2014-08-24 16: 11: 21Z

Lambdageek , ilişkililiğin kayan nokta sayıları için geçerli olmadığı için doğru bir şekilde işaret eder, a*a*a*a*a*a ile (a*a*a)*(a*a*a) arasındaki "optimizasyon" değeri değişebilir. Bu nedenle C99 tarafından izin verilmez (kullanıcı tarafından özellikle derleyici bayrağı veya pragma tarafından izin verilmedikçe). Genel olarak, varsayım programcının bir sebeple ne yaptığını yazdığı ve derleyicinin buna saygı duyması gerektiğidir. (a*a*a)*(a*a*a) istiyorsanız, bunu yazın.

Yine de yazmak acı verici olabilir; neden derleyici pow(a,6) kullandığınızda neden [ne düşündüğünüzü] doğru şeyi yapamıyor? Çünkü yapılacak yanlış bir şey olurdu. İyi bir matematik kütüphanesine sahip bir platformda, pow(a,6), a*a*a*a*a*a veya (a*a*a)*(a*a*a)'dan önemli ölçüde daha doğrudur. Sadece bazı verileri sağlamak için, Mac Pro'm üzerinde küçük bir deneme yaptım, [1,2] arasındaki tüm tek kesinlikli kayan sayılar için ^ 6'yı değerlendirmedeki en büyük hatayı ölçtüm:

 
worst relative error using    powf(a, 6.f): 5.96e-08
worst relative error using (a*a*a)*(a*a*a): 2.94e-07
worst relative error using     a*a*a*a*a*a: 2.58e-07

Çarpım ağacı yerine pow kullanmak, 4 faktörü ile sınırlandırılmış hatayı azaltır. Derleyiciler, kullanıcı tarafından lisansı verilmedikçe (örneğin -ffast-math aracılığıyla) hatayı artıran "optimizasyonlar" yapmamalı (ve genellikle yapmamalıdır).

GCC'nin __builtin_powi(x,n)'a alternatif olarak pow( ) sağladığını ve bununla birlikte satır içi bir çoğaltma ağacı oluşturduğunu unutmayın. Performans için doğrulukla takas etmek istiyorsanız, ancak hızlı matematiği etkinleştirmek istemiyorsanız bunu kullanın.

    
639
2017-05-23 12: 02: 47Z
  1. Ayrıca, Visual C ++ 'nın pow ()' geliştirilmiş 'bir sürümünü de sunduğunu unutmayın. _set_SSE2_enable(<flag>) ile flag=1'u arayarak, mümkünse SSE2'yi kullanacaktır. Bu, doğruluğu biraz azaltır, ancak hızları artırır (bazı durumlarda). MSDN: _set_SSE2_enable () ve pow ()
    2011-06-22 17: 04: 17Z
  2. @ TkTech: Herhangi bir azaltılmış doğrulukta kullanılan kayıtların boyutu değil Microsoft'un uygulamasından kaynaklanmaktadır. Kütüphane yazarı motive olmuşsa, yalnızca 32 bitlik kayıtları kullanarak bir doğru yuvarlanmış pow teslim etmek mümkündür. Çoğu x87 tabanlı uygulamadan daha fazla doğru olan SSE tabanlı pow uygulamaları vardır ve ayrıca hız için kesin doğruluk kazandıran uygulamalar da vardır.
    2011-06-22 17: 37: 23Z
  3. @ TkTech: Tabii ki, doğruluktaki düşüşün SSE kullanımına özgü olmayan, kütüphane yazarları tarafından yapılan seçimlerden kaynaklandığını açıkça söylemek istedim. .
    2011-06-22 17: 56: 20Z
  4. Göreceli hataları hesaplamak için burada "altın standart" olarak ne kullandığınızı bilmek istiyorum - normalde bunu beklerdima*a*a*a*a*a olurdu, ama görünüşe göre durum böyle değil! :)
    2013-09-24 22: 44: 52Z
  5. @ j_random_hacker: tek duyarlıklı sonuçları karşılaştırdığımdan beri, altın standart için çift duyarlık yeterlidir - a a a a iki katına sahip bir bilgisayar, tek duyarlıklı hesaplamaların hatalarından çok daha küçüktür.
    2013-09-24 22: 47: 30Z

Bir başka benzer durum: çoğu derleyici, a + b + c + d - (a + b) + (c + d) arasında en iyi duruma getirmez (bu, ikinci ifade daha iyi boruya dönüştürülebildiğinden bu bir optimizasyondur) ve verilen şekilde değerlendirir (örneğin, (((a + b) + c) + d) olarak). Bu da köşe davaları yüzünden:

 
float a = 1e35, b = 1e-5, c = -1e35, d = 1e-5;
printf("%e %e\n", a + b + c + d, (a + b) + (c + d));

Bu, çıktı 1.000000e-05 0.000000e+00

    
162
2015-05-21 12: 50: 14Z
  1. Bu tamamen aynı değil. Çarpma /bölme sırasını değiştirmek (0 ile bölme hariç), toplama /çıkarma değişkeni sırasından daha güvenlidir. Benim düşünceme göre, derleyici mults./divs. çünkü bunu yapmak toplam işlem sayısını azaltıyor ve performans kazancının yanında aynı zamanda hassas bir kazanım oluyor.
    2014-07-07 08: 22: 52Z
  2. @ DarioOO: Daha güvenli değil. Çarpma ve bölme, üssün toplanması ve çıkarılması ile aynıdır ve sıranın değiştirilmesi, geçici öğelerin üssün olası aralığını aşmasına neden olabilir. (Tam olarak aynı değildir, çünkü üs, hassasiyet kaybına uğramadığından ... ancak temsil hala oldukça sınırlıdır ve yeniden sıralama, bildirilemeyen değerlere yol açabilir)
    2015-03-04 17: 47: 58Z
  3. Bazı matematik geçmişini özlediğinizi düşünüyorum. 2 sayının çarpılması ve bölünmesi aynı miktarda hatayı getirir. Çıkarma /toplama 2 sayıları, özellikle 2 sayı farklı büyüklük sırasına göre daha büyük bir hataya neden olsa da, son hatada ufak bir değişiklik getirdiğinden, alt /toplama işleminden çok daha fazla hata yapılması daha güvenli olur.
    2015-03-05 08: 49: 32Z
  4. @ DarioOO: risk, mul /div ile farklı: Yeniden düzenlemek, sonuçta göz ardı edilebilir bir değişiklik yapar veya üs bir noktada (taşması gereken yerde) taşar. daha önce sahip değilsiniz) ve sonuç büyük ölçüde farklı (potansiyel olarak + inf veya 0).
    2015-07-30 04: 37: 45Z

Fortran (bilimsel bilgi işlem için tasarlanmıştır) yerleşik bir güç operatörüne sahiptir ve bildiğim kadarıyla Fortran derleyicileri genel olarak tanımladığınıza benzer şekilde tam sayıdaki güçlere yükseltmeyi optimize edecektir. C /C ++ ne yazık ki bir güç operatörüne sahip değil, sadece kütüphane işlevi pow(). Bu, akıllı derleyicilerin pow'a özel davranmasını ve özel durumlar için daha hızlı bir şekilde hesaplamasını engellemez, ancak daha az yaygın olarak yaptıkları anlaşılıyor ...

Birkaç yıl önce, tamsayılı güçleri en uygun şekilde hesaplamayı daha kolay hale getirmeye çalışıyordum ve aşağıdakileri buldum. C ++ olsa da C ++ ve hala derleyicinin işleri en iyi hale getirme /satır içi konusunda biraz akıllı olmasına bağlı. Her neyse, pratikte faydalı bulacağını umuyorum:

 
template<unsigned N> struct power_impl;

template<unsigned N> struct power_impl {
    template<typename T>
    static T calc(const T &x) {
        if (N%2 == 0)
            return power_impl<N/2>::calc(x*x);
        else if (N%3 == 0)
            return power_impl<N/3>::calc(x*x*x);
        return power_impl<N-1>::calc(x)*x;
    }
};

template<> struct power_impl<0> {
    template<typename T>
    static T calc(const T &) { return 1; }
};

template<unsigned N, typename T>
inline T power(const T &x) {
    return power_impl<N>::calc(x);
}

Meraklı için açıklama: bu, güçleri hesaplamanın en uygun yolunu bulamaz, ancak en uygun çözümü bulmak NP tamamlanmış bir sorundur ve bu sadece küçük güçler için yapmaya değer (pow kullanmak yerine), ayrıntılarla uğraşmak için hiçbir sebep yok.

O zaman sadece power<6>(a) olarak kullanın.

Bu, güçlerin yazılmasını kolaylaştırır (parens ile 6 a sn. hecelemeye gerek yoktur) ve telafi toplamı (işlemlerin sırasının esas olduğu bir örnek).

Muhtemelen bunun C ++ olduğunu unutabilir ve yalnızca C programında kullanabilirsiniz (eğer bir C ++ derleyicisiyle derlenirse).

Bu yararlı olabilir umarım.

DÜZENLEME:

Bu, derleyicimden aldığım şey:

a*a*a*a*a*a için

 
    movapd  %xmm1, %xmm0
    mulsd   %xmm1, %xmm0
    mulsd   %xmm1, %xmm0
    mulsd   %xmm1, %xmm0
    mulsd   %xmm1, %xmm0
    mulsd   %xmm1, %xmm0

(a*a*a)*(a*a*a) için

 
    movapd  %xmm1, %xmm0
    mulsd   %xmm1, %xmm0
    mulsd   %xmm1, %xmm0
    mulsd   %xmm0, %xmm0

power<6>(a) için

 
    mulsd   %xmm0, %xmm0
    movapd  %xmm0, %xmm1
    mulsd   %xmm0, %xmm1
    mulsd   %xmm0, %xmm1
    
78
2014-11-25 01: 54: 55Z
  1. En uygun güç ağacını bulmak zor olabilir, ancak yalnızca küçük güçler için ilginç olduğu için, açık cevap bir kez önceden hesaplamaktır (Knuth 100'e kadar bir tablo sağlar) ) ve o kodlanmış tabloyu kullanın (bu, gcc'nin powi için dahili olarak yaptığıdır).
    2013-01-31 19: 11: 49Z
  2. Modern işlemcilerde hız gecikmeyle sınırlıdır. Örneğin, bir çarpmanın sonucu beş döngüden sonra kullanılabilir olabilir. Bu durumda, bir güç yaratmanın en hızlı yolunu bulmak daha zor olabilir.
    2014-03-10 16: 46: 04Z
  3. Ayrıca, göreceli yuvarlama hatası için en düşük üst sınır veren güç ağacını veya en düşük ortalama göreli yuvarlama hatasını da deneyebilirsiniz.
    2014-03-10 16: 52: 38Z
  4. Boost da bunun için destek veriyor; artırmak :: matematik :: POW ve R6, kramp (n); Ortak faktörleri çıkararak çarpma sayısını azaltmaya çalıştığını bile düşünüyorum.
    2017-08-03 12: 43: 35Z
  5. Güzel bir fikir! Bunu zaten faktörel hesaplama için yaptım.
    2017-10-03 13: 23: 34Z

GCC aslında a a a a a'yı (a a a) (a a a) a bir tam sayı olduğunda. Bu komutla denedim:

 
$ echo 'int f(int x) { return x*x*x*x*x*x; }' | gcc -o - -O2 -S -masm=intel -x c -

Çok fazla gcc bayrağı var, ancak süslü bir şey yok. Yani: stdin'den oku; O2 optimizasyon seviyesini kullanın; İkili yerine çıktı derleme dili listesi; giriş, Intel derleme dili sözdizimini kullanmalıdır; giriş C dilinde (genellikle dil giriş dosyası uzantısından çıkar, ancak stdin'den okurken dosya uzantısı yoktur); ve stdout'a yazın.

İşte çıktının önemli kısmı. Assembly dilinde neler olup bittiğini gösteren bazı yorumlar ile açıklamıştım:

 
; x is in edi to begin with.  eax will be used as a temporary register.
mov  eax, edi  ; temp = x
imul eax, edi  ; temp = x * temp
imul eax, edi  ; temp = x * temp
imul eax, eax  ; temp = temp * temp

Bir Ubuntu türevi olan Linux Mint 16 Petra üzerindeki GCC sistemini kullanıyorum. İşte gcc sürümü:

 
$ gcc --version
gcc (Ubuntu/Linaro 4.8.1-10ubuntu9) 4.8.1

Diğer posterlerin belirttiği gibi, kayan nokta için bu seçenek mümkün değildir, çünkü kayan nokta aritmetiği aslında ilişkilendirici değildir.

    
57
2019-02-14 16: 46: 15Z
  1. Bu, tamsayı çarpma işlemi için yasaldır, çünkü ikisinin tamamlayıcı taşması tanımsızdır. Eğer bir taşma olacaksa, işlemleri yeniden düzenlemek ne olursa olsun, bir yerlerde olacak. Bu nedenle, taşma olmayan ifadeler aynı şekilde değerlendirilir, taşma olduğu tanımlanmamış davranışlardır, bu nedenle derleyicinin taşma olduğu noktayı değiştirmesi tamamdır. gcc de bunu unsigned int ile yapıyor.
    2015-07-30 05: 18: 59Z

32-bit kayan nokta sayısı - 1.024 gibi - 1.024 değildir. Bir bilgisayarda, 1.024 bir aralıktır: "e" bir hatayı temsil eder (1.024-e) ila (1.024 + e). Bazı insanlar bunu fark edemez ve ayrıca * a * a'da, bu sayılara herhangi bir hata yapmadan rastgele kesinlikli sayıların çarpımına dayandığına inanır. Bazı insanların bunu gerçekleştirememesinin nedeni belki de yaptıkları matematik hesaplamalarıdır.ilköğretim okullarında: sadece hata yapmadan ideal sayılarla çalışmak ve çarpma yaparken sadece "e" yi görmezden gelmenin doğru olduğuna inanmak. "Float a = 1.2", "a * a * a" ve benzer C kodlarında "e" harfini görmezler.

Programcıların çoğunluğu, C ifadesinin a * a * a * a * a * a ifadesinin aslında ideal sayılarla çalışmadığı fikrini kabul ederse (ve uygulayabiliyorsa), GCC derleyicisi daha sonra optimize etmek için ÜCRETSİZ olacaktır " a * a * a * a * a * a "deyin" t = (a * a); t * t * t "ki bu, daha az sayıda çarpma gerektirir. Ancak ne yazık ki, GCC derleyicisi kodu yazan programcının “a” nın hata olan veya olmayan bir sayı olduğunu düşündüğünü bilmiyor. Ve böylece GCC sadece kaynak kodun neye benzediğini yapar - çünkü GCC'nin "çıplak gözü" ile gördüğü şey budur.

... ne tür bir programcı olduğunuzu bildiğinizde , GCC'ye "Hey, GCC, ne yaptığımı biliyorum!" diyen "-ffast-math" anahtarını kullanabilirsiniz. ". Bu, GCC'nin * a * a * a * a * a * a * a * a'yı farklı bir metin parçasına dönüştürmesini sağlar - a * a * a * a * a * a - den farklı görünüyor ancak yine de hata aralığında bir sayı hesaplıyor a * a * a * a * a * a. Bu sorun değil, çünkü zaten ideal sayılarla değil aralıklarla çalıştığınızı biliyorsunuz.

    
50
2011-06-23 10: 07: 41Z
  1. Kayan nokta sayıları kesin. Sadece tam olarak beklediğiniz gibi değiller. Dahası, epsilonlu teknik, gerçekte olaylarla nasıl başa çıkılacağının bir yaklaşımıdır, çünkü beklenen gerçek hata mantisanın ölçeğine göredir, yani normalde yaklaşık 1 LSB'ye çıkarsınız, ancak Dikkatli değilseniz, yaptığınız her işlem, kayan nokta ile önemsiz şeyler yapmadan önce, sayısal bir analiste danışın. Yapabiliyorsanız uygun bir kütüphane kullanın.
    2011-06-24 13: 35: 46Z
  2. @ DonalFellows: IEEE standardı, kayan nokta hesaplamaları, kaynak işlenenler kesin değerler olsaydı sonucun sonucuyla en iyi şekilde eşleşen sonucu verir, ancak bu tam olarak temsil ettikleri anlamına gelmez. Bir çok durumda, 0.1f'nin (1.677.722 +/- 0.5) /16.777.216 olarak kabul edilmesi, bu belirsizlik tarafından belirtilen ondalık basamak sayısıyla gösterilmesi gerekir (1.677.722 +/- 0.5) /16.777.216 (24 ondalık basamağa kadar gösterilmesi gerekir).
    2012-11-18 15: 15: 51Z
  3. @ supercat: IEEE-754, kayan nokta verilerinin tam değerleri temsil ettiği noktada oldukça açık; Madde 3.2 - 3.4, ilgili bölümlerdir. Elbette, onları başka şekilde yorumlamayı seçebilirsiniz, tıpkı int x = 3'u x'un 3 +/- 0.5 olduğu şeklinde yorumlamayı seçebilirsiniz.
    2013-01-04 13: 35: 41Z
  4. @ supercat: Tamamen katılıyorum ama bu Distance'un sayısal değerine tam olarak eşit olmadığı anlamına gelmez; bu, sayısal değerin yalnızca modellenmekte olan bazı fiziksel niceliklerin yaklaşık olduğu anlamına gelir.
    2013-01-04 16: 22: 01Z
  5. Sayısal analiz için, kayan noktalı sayıları aralıklarla değil, kesin değerler olarak (tam olarak istediğiniz değerler değil) yorumlarsanız beyniniz size teşekkür eder ). Örneğin, x, 4.5'in yuvarlak olduğu ve 0,1'den küçük bir hata olduğu takdirde (x + 1) - x değerini hesaplarsanız, "aralık" yorumlaması sizi 0,8 - 1,2 arasında bir aralık bırakırken, "kesin değer" yorumlaması Sonuç, iki katı hassasiyette en fazla 2 ^ (- 50) hatayla 1 olur.
    2014-03-10 16: 50: 18Z

Henüz hiçbir poster, yüz ifadelerinin daralmasından söz etmedi (ISO C standardı, 6.5p8 ve 7.12.2). FP_CONTRACT pragması ON olarak ayarlanmışsa, derleyicinin tam olarak tek bir yuvarlama ile değerlendirilmiş gibi a*a*a*a*a*a gibi bir ifadeyi tek bir işlem olarak kabul etmesine izin verilir. Örneğin, bir derleyici, her ikisini de hızlı olan bir dahili güç işlevi ile değiştirebilirer ve daha doğru. Bu özellikle davranış, programcı tarafından doğrudan kaynak kodunda kısmen kontrol edilirken, son kullanıcı tarafından sağlanan derleyici seçenekleri bazen yanlış kullanılabildiğinden ilginçtir.

FP_CONTRACT pragmasının varsayılan durumu uygulama tarafından tanımlanır, böylece derleyicinin varsayılan olarak bu tür optimizasyonları yapmasına izin verilir. Bu nedenle, IEEE 754 kurallarını kesinlikle uygulamanız gereken taşınabilir kod açıkça OFF olarak ayarlanmalıdır.

Bir derleyici bu pragmayı desteklemiyorsa, geliştiricinin OFF olarak ayarlamayı seçmesi durumunda, böyle bir optimizasyondan kaçınarak muhafazakar olması gerekir.

GCC bu pragmayı desteklemiyor, ancak varsayılan seçeneklerle ON olduğunu varsayar; bu nedenle, bir donanım FMA'sı olan hedefler için, eğer birisi a*b+c'un fma'ya (a, b, c) dönüşümünü engellemek isterse, -ffp-contract=off (pragmayı açıkça OFF'a ayarlamak için) veya -std=c99 (GCC'ye söylemek için) gibi bir seçenek sağlaması gerekir. Bazı C standart versiyonlarına uymak için, burada C99, yukarıdaki paragrafı izleyin). Geçmişte, ikinci seçenek dönüşümü engellemiyordu; bu, GCC'nin bu noktada uymadığı anlamına geliyordu: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=37845

    
31
2018-01-06 17: 17: 18Z
  1. Uzun süren popüler sorular bazen yaşlarını gösterir. Bu soru, 2011'de GCC'nin en son C99 standardına tam olarak uymamaya mazeret gösterdiği durumlarda sorulmuş ve cevaplandırılmıştır. Tabii ki şimdi 2014 oldu, öyleyse GCC… ahem.
    2014-06-27 21: 19: 43Z
  2. Yine de, bunun yerine kabul edilmiş bir cevap olmadan karşılaştırmalı olarak son noktadaki soruları cevaplamamalı mıydınız? öksürük stackoverflow.com/questions/23703408 öksürük
    2014-06-27 21: 22: 57Z
  3. Onu buluyorum ... gcc'nin C99 kayan nokta pragmalarını uygulamamasını rahatsız edici buluyorum.
    2016-11-19 15: 17: 58Z
  4. @ DavidMonniaux pragmalar tanım gereği uygulamaya göre isteğe bağlıdır.
    2018-09-04 07: 57: 54Z
  5. @ TimSeguine Ancak bir pragma uygulanmadıysa, varsayılan değerinin uygulama için en kısıtlayıcı olması gerekir. Sanırım David'in düşündüğü şey buydu. GCC ile, bu şimdi bir ISO C modu kullanıyorsa, FP_CONTRACT için sabitlenir : Hala pragmayı uygulamıyor, ancak bir ISO C modunda, pragmanın kapalı olduğunu varsayıyor.
    2018-09-04 10: 25: 19Z

Lambdageek'in belirttiği gibi float çarpımı birleştirici değildir ve daha az doğruluk elde edersiniz, ancak daha iyi doğruluk elde ettiğinizde optimizasyona karşı çıkabilirsiniz, çünkü deterministik bir uygulama istiyorsunuz. Örneğin, oyun simülasyonu istemcisinde /sunucusunda, her müşterinin aynı dünyayı simüle etmesi gerektiği yerde kayan nokta hesaplamalarının deterministik olmasını istersiniz.

    
28
2011-06-23 12: 44: 13Z
  1. Kayan nokta her zaman belirleyicidir.
    2014-06-30 13: 29: 30Z
  2. @ Alice Görünüşe göre Bjorn burada farklı sonuçları ve farklı derleyici sürümlerinde aynı sonucu veren kod anlamında 'deterministic' kullanıyor (harici değişkenler programcının kontrolü dışında olabilir) - çalışma sırasındaki gerçek sayısal rastgelelik eksikliğinin aksine. Bunun kelimenin doğru bir şekilde kullanılmadığına işaret ediyorsanız, bununla tartışmayacağım.
    2014-09-08 14: 15: 06Z
  3. @ greggo Söylediklerinin yorumunda bile, bu hala yanlış; platformlardaki çoğu (tümü olmasa da) işlemler için aynı özellikleri sağlamak için IEEE 754'ün esas noktası budur. Şimdi, platformlardan veya derleyici sürümlerinden hiç bahsetmedi; bu, uzaktaki her sunucu /istemcideki her bir işlemin aynı olmasını istiyorsanız, geçerli bir endişe kaynağı olurdu… ancak bu, ifadesinden açıkça anlaşılmıyor. Daha iyi bir kelime "güvenilir şekilde benzer" veya başka bir şey olabilir.
    2014-09-09 18: 44: 40Z
  4. @ Alice, semantiği savunarak, kendi zamanınızı da içeren herkesin zamanını boşa harcıyorsunuz. Onun anlamı açıktı.
    2014-12-03 14: 59: 45Z
  5. @ Lanaru Standartların tamamı IS semantics; onun anlamı kesinlikle belli değildi.
    2014-12-08 02: 54: 01Z

Bu davanın optimize edilmesini beklemiyordum. Bir ifadenin, tüm işlemleri kaldırmak için yeniden toplanabilen alt ifadeler içerdiği yerlerde çok sık olamaz. Derleyici yazarlarının zamanlarını, nadiren karşılaşılan bir son durumu ele almak yerine, dikkate değer gelişmelere yol açması muhtemel olan alanlara yatırmalarını beklerdim.

Diğer ifadelerden, bu ifadenin gerçekten uygun derleyici anahtarları ile optimize edilebileceğini öğrendiğimde şaşırdım. Optimizasyon önemsizdir veya çok daha yaygın bir optimizasyonun son halidir veya derleyici yazarları son derece eksiksizdir.

Burada yaptığınız gibi derleyiciye ipucu vermede yanlış bir şey yoktur. Hangi farklılıkları getireceklerini görmek için ifadeleri ve ifadeleri yeniden düzenlemek, mikro optimizasyon işleminin normal ve beklenen bir parçasıdır.

Derleyici, tutarlı olmayan sonuçlar (doğru anahtarlar olmadan) sunmak için iki ifadeyi göz önünde bulundurarak haklı görünse de, bu kısıtlamaya uymanıza gerek yoktur. Fark inanılmaz derecede küçük olacak - o kadar ki fark sizin için önemliyse, ilk önce standart kayan nokta aritmetik kullanmamanız gerekir.

    
28
2014-06-06 19: 20: 18Z
  1. Başka bir yorumcunun belirttiği gibi, bu saçma olma anlamına gelmez; fark, maliyetin% 10 ila% 10'u kadar olabilir ve sıkı bir döngüde çalıştırılırsa, önemsiz miktarda ek hassasiyetin ne olabileceğini elde etmek için boşa harcanan birçok talimatla sonuçlanır. Monte edilmiş bir araba yaparken standart bir FP kullanmamanız gerektiğini söylemek, her zaman ülkeyi geçmek için bir uçak kullanmanız gerektiğini söylemeye benzer; birçok dışsallığı görmezden geliyor. Son olarak, bu nadir bir optimizasyon DEĞİLDİR; ölü kod analizi ve kod azaltma /reddetme çok yaygındır.
    2014-06-30 13: 29: 05Z

"Pow" gibi kütüphane işlevleri, genellikle asgari hatayı (genel durumda) elde etmek için özenle oluşturulur. Bu genellikle spline'larla yaklaşık fonksiyonlara ulaşır (Pascal'ın yorumuna göre en yaygın uygulama Remez algoritması )

temel olarak aşağıdaki işlem:

 
pow(x,y);

yaklaşık olarak herhangi bir çarpma veya bölmedeki hatadır aynı büyüklükte doğal bir hataya sahiptir.

Aşağıdaki işlem sırasında:

 
float a=someValue;
float b=a*a*a*a*a*a;

, tek bir çarpma işleminin hatasından 5 kat daha fazla veya bölme işleminden daha büyük doğal bir hataya sahiptir (çünkü 5 çarpımı birleştiriyorsunuzdur).

Derleyici, yaptığı optimizasyon türüne çok dikkat etmelidir:

  1. pow(a,6) ila a*a*a*a*a*a’u optimize ediyorsanız, performansı iyileştirebilir, ancak kayan nokta sayılarının doğruluğunu büyük ölçüde azaltır.
  2. a*a*a*a*a*a ila pow(a,6)’u optimize ediyorsanız, bu gerçektenause "a" hatasız çarpma işlemine izin veren bazı özel bir değerdi (2 veya bazı küçük tam sayıların gücü)
  3. pow(a,6) - (a*a*a)*(a*a*a) veya (a*a)*(a*a)*(a*a)’u optimize ediyorsanız, pow işlevine kıyasla hala doğruluk kaybı olabilir.

Genelde, rastgele kayan nokta değerleri için "pow" in sonunda yazabileceğiniz herhangi bir fonksiyondan daha iyi bir doğruluğa sahip olduğunu biliyorsunuzdur, ancak bazı özel durumlarda, çoklu çarpmaların daha iyi doğruluk ve performansa sahip olabileceğini, geliştiricinin ne olduğunu seçeceğine bağlıdır daha uygun, sonunda kodun yorumlanması, böylece başka hiç kimse bu kodu "optimize etmeyecek".

İyileştirmek için mantıklı olan (kişisel görüş ve görünüşe göre GCC'de bir seçim yapmak, belirli bir optimizasyon veya derleyici bayrağını seçmek) tek şey "pow (a, 2)" yerine "a * a" kullanmak olmalıdır. Bir derleyici satıcısının yapması gereken tek mantıklı şey bu olurdu.

    
26
2017-01-04 17: 37: 38Z
  1. indiriciler bu cevabın tamamen iyi olduğunu anlamalıdır. Cevabımı desteklemek için düzinelerce kaynak ve belgeleme teklif edebilirim ve muhtemelen herhangi bir düşürücünün olabileceğinden daha kayan nokta hassasiyetiyle ilgiliyim. StackOverflow'ta diğer cevapların kapsamadığı eksik bilgiler eklemek tamamen mantıklıdır, bu yüzden kibar olun ve nedenlerinizi açıklayın.
    2015-01-03 16: 59: 27Z
  2. Bana öyle geliyor ki Stephen Canon’un cevabında söylediklerinizi yazıyor. Libermlerin spline'larla uygulanmasında ısrar etmiş gibi görünüyorsunuz: daha tipik olarak argüman azaltmayı (uygulanmakta olan fonksiyona bağlı olarak) artı katsayıları Remez algoritmasının az çok karmaşık sofistike varyantları tarafından elde edilmiş tek bir polinom kullanıyor. Kavşak noktalarındaki pürüzsüzlük, libm işlevleri için izlenmesi gereken bir hedef olarak kabul edilmez (yeterince doğru sonuçlanırlarsa, etki alanının kaç parçaya ayrıldığına bakılmaksızın otomatik olarak oldukça pürüzsüz olurlar.)
    2015-01-03 17: 33: 44Z
  3. Cevabınızın ikinci yarısı, derleyicilerin kaynak kodun ne dediğini uygulayan kodu üretmesi gereken süreyi tamamen kaçırıyor. Ayrıca “kesinlik” derken “kesinlik” kelimesini kullanırsınız.
    2015-01-03 17: 35: 53Z
  4. Girişiniz için teşekkürler, cevabı biraz düzelttim, son 2 satırda hala yeni bir şey var ^^
    2015-01-03 22: 35: 03Z

Bu soruya zaten birkaç iyi cevap var, ancak tamamlık uğruna, C standardının uygulanabilir bölümünün 5.1.2.2.3 /15 (bölüm 1.9 /ile aynı olan) olduğunu belirtmek istedim. C ++ 11 standardında 9). Bu bölüm, operatörlerin yalnızca gerçekten birleştirici veya değiştirici olmaları durumunda yeniden toplanabileceğini belirtir.

    
21
2013-10-01 19: 33: 31Z

gcc aslında bu optimizasyonu kayan nokta sayıları için bile yapabilir. Örneğin,

 
double foo(double a) {
  return a*a*a*a*a*a;
}

olur

 
foo(double):
    mulsd   %xmm0, %xmm0
    movapd  %xmm0, %xmm1
    mulsd   %xmm0, %xmm1
    mulsd   %xmm1, %xmm0
    ret

-O -funsafe-math-optimizations ile. Bu yeniden sıralama IEEE-754'ü ihlal ediyor olsa da bayrağını gerektiriyor.

İşaretli tamsayılar, Peter Cordes'in bir yorumda işaret ettiği gibi, -funsafe-math-optimizations olmadan bu optimizasyonu yapabilir çünkü tam olarak taşma olmadığında ve taşma varsa tanımsız davranışa sahip olursunuz. Yani alsın

 
foo(long):
    movq    %rdi, %rax
    imulq   %rdi, %rax
    imulq   %rdi, %rax
    imulq   %rax, %rax
    ret

yalnızca -O ile. İmzasız tamsayılar için, 2 mod gücü kullandıkları için daha da kolaydır ve taşma anında bile serbestçe yeniden sıralanabilir.

    
11
2016-06-16 18: 44: 57Z
  1. > Godbolt bağlantısı , çift, int ve unimzalamıştır. gcc ve clang, ikisi de aynı şekilde optimize eder (-ffast-math ile)
    2016-06-17 00: 09: 53Z
  2. @ PeterCordes Teşekkürler!
    2016-06-17 00: 48: 28Z
kaynak yerleştirildi İşte