Sonuç ne olursa olsun sıfıra bölmeyi destekleyen en hızlı tamsayı bölümü nedir?


109

Özet:

Hesaplamanın en hızlı yolunu arıyorum

(int) x / (int) y

için bir istisna olmaksızın y==0. Bunun yerine sadece keyfi bir sonuç istiyorum.


Arka fon:

Görüntü işleme algoritmalarını kodlarken genellikle bir (birikmiş) alfa değerine bölmem gerekir. En basit varyant, tamsayı aritmetiğine sahip düz C kodudur. Benim sorunum, ile sonuç pikselleri için genellikle sıfıra bölünme hatası almamdır alpha==0. Ancak bu, sonucun hiç önemli olmadığı tam olarak piksellerdir: ile piksellerin renk değerleri umrumda değil alpha==0.


Detaylar:

Şunun gibi bir şey arıyorum:

result = (y==0)? 0 : x/y;

veya

result = x / MAX( y, 1 );

x ve y pozitif tam sayılardır. Kod, iç içe geçmiş bir döngüde çok sayıda çalıştırılır, bu yüzden koşullu dallanmadan kurtulmanın bir yolunu arıyorum.

Y bayt aralığını aşmadığında, çözümden memnunum

unsigned char kill_zero_table[256] = { 1, 1, 2, 3, 4, 5, 6, 7, [...] 255 };
[...]
result = x / kill_zero_table[y];

Ancak bu açıkça daha büyük aralıklar için pek işe yaramıyor.

Sanırım son soru şudur: 0'ı başka bir tamsayı değerine değiştirirken diğer tüm değerleri değiştirmeden bırakan en hızlı bit twiddling hack nedir?


Açıklamalar

Dallanmanın çok pahalı olduğundan% 100 emin değilim. Bununla birlikte, farklı derleyiciler kullanılıyor, bu yüzden küçük optimizasyonlarla kıyaslamayı tercih ediyorum (ki bu gerçekten sorgulanabilir).

Elbette, derleyiciler biraz karıştırmaya gelince harikadır, ancak C ile "umurumda değil" sonucunu ifade edemem, bu nedenle derleyici hiçbir zaman tüm optimizasyon yelpazesini kullanamayacaktır.

Kod tamamen C uyumlu olmalıdır, ana platformlar gcc & clang ve MacOS ile Linux 64 Bit'tir.


22
İf-şubesinin çok pahalı olduğunu nasıl belirlediniz?
djechlin

7
Orada nasıl olduğunu tespit ettik olduğunu bir dal?
leemes

13
Profil oluşturma için +1, modern şube tahminiyle buna ihtiyacınız olmayabilir. Ayrıca, neden kendi görüntü işleme algoritmalarınızı kodluyorsunuz?
TC1

8
"En hızlı biraz oynaşan hack nedir ..." Belki y += !y? Bunu hesaplamak için şubeye gerek yok. Sen karşılaştırabilirsiniz x / (y + !y)karşı x / max(y, 1)belki de ve y ? (x/y) : 0. En azından optimizasyonlar açıkken ikisinde de dal olmayacağını tahmin ediyorum.
leemes

6
Modern dal tahminini düşünen biri, bunu yapmak zorunda olmadığınız anlamına gelir, piksel başına seviyede çalışan yeterli dal eleme kodu profili oluşturmamıştır. Modern dal tahmini, alfa 0bölümleri büyük ve bitişikse kabul edilebilir . Mikro optimizasyonlarla uğraşmak için bir yer var ve piksel başına işlemler tam olarak bu yer.
Yakk - Adam Nevraumont

Yanıtlar:


107

Pentium ve gccderleyicimdeki daldan kurtardığım bazı yorumlardan esinlenildi.

int f (int x, int y)
{
        y += y == 0;
        return x/y;
}

Derleyici temelde, ek olarak testin bir koşul bayrağını kullanabileceğini kabul eder.

Talep üzerine montaj:

.globl f
    .type   f, @function
f:
    pushl   %ebp
    xorl    %eax, %eax
    movl    %esp, %ebp
    movl    12(%ebp), %edx
    testl   %edx, %edx
    sete    %al
    addl    %edx, %eax
    movl    8(%ebp), %edx
    movl    %eax, %ecx
    popl    %ebp
    movl    %edx, %eax
    sarl    $31, %edx
    idivl   %ecx
    ret

Bunun çok popüler bir soru ve cevap olduğu ortaya çıktığı için, biraz daha detaylandıracağım. Yukarıdaki örnek, bir derleyicinin tanıdığı programlama deyimine dayanmaktadır. Yukarıdaki durumda, integral aritmetikte bir boole ifadesi kullanılır ve bu amaçla donanımda koşul bayraklarının kullanımı icat edilir. Genelde durum bayraklarına yalnızca C deyimi kullanılarak erişilebilir. Bu nedenle, (satır içi) montaja başvurmadan C'de taşınabilir çoklu hassas tamsayı kitaplığı yapmak çok zor. Tahminimce en iyi derleyiciler yukarıdaki deyimi anlayacaktır.

Yukarıdaki yorumların bazılarında da belirtildiği gibi, dallardan kaçınmanın bir başka yolu, önceden belirlenmiş yürütmedir. Bu nedenle, philipp'in ilk kodunu ve kodumu aldım ve bunu, önceden belirlenmiş yürütme özelliğine sahip ARM mimarisi için ARM ve GCC derleyicisinden derleyici aracılığıyla çalıştırdım. Her iki derleyici, her iki kod örneğinde de daldan kaçınır:

Philipp'in ARM derleyicili versiyonu:

f PROC
        CMP      r1,#0
        BNE      __aeabi_idivmod
        MOVEQ    r0,#0
        BX       lr

Philipp'in GCC'li versiyonu:

f:
        subs    r3, r1, #0
        str     lr, [sp, #-4]!
        moveq   r0, r3
        ldreq   pc, [sp], #4
        bl      __divsi3
        ldr     pc, [sp], #4

ARM derleyicisindeki kodum:

f PROC
        RSBS     r2,r1,#1
        MOVCC    r2,#0
        ADD      r1,r1,r2
        B        __aeabi_idivmod

GCC ile kodum:

f:
        str     lr, [sp, #-4]!
        cmp     r1, #0
        addeq   r1, r1, #1
        bl      __divsi3
        ldr     pc, [sp], #4

ARM'nin bu sürümünde bir bölüm için donanım bulunmadığından, tüm sürümler bölüm yordamına yönelik bir şubeye ihtiyaç duymaktadır, ancak testi y == 0tamamen önceden belirlenmiş yürütme yoluyla gerçekleştirilir.


Elde edilen assembler kodunu bize gösterebilir misiniz? Ya da şube olmadığını nasıl belirlediniz?
Haatschii

1
Muhteşem. constexprBöyle gereksiz template<typename T, typename U> constexpr auto fdiv( T t, U u ) -> decltype(t/(u+!u)) { return t/(u+!u); }255(lhs)/(rhs+!rhs) & -!rhs
yazılardan

1
@leemes ama demek |istemedim &. Ooops - ( (lhs)/(rhs+!rhs) ) | -!rhsiçin bir değer belirlemeniz gerekir 0xFFFFFFFeğer rhsolduğunu 0ve lhs/rhseğer rhs!=0.
Yakk - Adam Nevraumont

1
Bu çok akıllıcaydı.
Theodoros Chatzigiannakis

1
Mükemmel cevap! Genelde bu tür şeyler için toplanmaya başvururum, ancak bunun bakımı her zaman korkunçtur (daha az taşınabilir olduğundan bahsetmiyorum bile;)).
Aslan

20

GCC 4.7.2 kullanan Windows'ta bazı somut sayılar şunlardır:

#include <stdio.h>
#include <stdlib.h>

int main()
{
  unsigned int result = 0;
  for (int n = -500000000; n != 500000000; n++)
  {
    int d = -1;
    for (int i = 0; i != ITERATIONS; i++)
      d &= rand();

#if CHECK == 0
    if (d == 0) result++;
#elif CHECK == 1
    result += n / d;
#elif CHECK == 2
    result += n / (d + !d);
#elif CHECK == 3
    result += d == 0 ? 0 : n / d;
#elif CHECK == 4
    result += d == 0 ? 1 : n / d;
#elif CHECK == 5
    if (d != 0) result += n / d;
#endif
  }
  printf("%u\n", result);
}

Kasıtlı olarak aramadığımı unutmayın srand(), böylece rand()her zaman tam olarak aynı sonuçları verir. Ayrıca şunu da unutmayın:-DCHECK=0 yalnızca sıfırları saydığına dikkat edin, böylece ne sıklıkta göründüğü açıktır.

Şimdi, çeşitli şekillerde derleyip zamanlama:

$ for it in 0 1 2 3 4 5; do for ch in 0 1 2 3 4 5; do gcc test.cc -o test -O -DITERATIONS=$it -DCHECK=$ch && { time=`time ./test`; echo "Iterations $it, check $ch: exit status $?, output $time"; }; done; done

bir tabloda özetlenebilecek çıktıyı gösterir:

Iterations  | 0        | 1        | 2        | 3         | 4         | 5
-------------+-------------------------------------------------------------------
Zeroes       | 0        | 1        | 133173   | 1593376   | 135245875 | 373728555
Check 1      | 0m0.612s | -        | -        | -         | -         | -
Check 2      | 0m0.612s | 0m6.527s | 0m9.718s | 0m13.464s | 0m18.422s | 0m22.871s
Check 3      | 0m0.616s | 0m5.601s | 0m8.954s | 0m13.211s | 0m19.579s | 0m25.389s
Check 4      | 0m0.611s | 0m5.570s | 0m9.030s | 0m13.544s | 0m19.393s | 0m25.081s
Check 5      | 0m0.612s | 0m5.627s | 0m9.322s | 0m14.218s | 0m19.576s | 0m25.443s

Sıfırlar nadirse, -DCHECK=2sürüm kötü performans gösterir. Sıfırlar daha fazla görünmeye başladığında,-DCHECK=2 başladıkça vaka önemli ölçüde daha iyi performans göstermeye başlar. Diğer seçeneklerden pek bir farkı yok.

Zira -O3bu farklı bir hikaye:

Iterations  | 0        | 1        | 2        | 3         | 4         | 5
-------------+-------------------------------------------------------------------
Zeroes       | 0        | 1        | 133173   | 1593376   | 135245875 | 373728555
Check 1      | 0m0.646s | -        | -        | -         | -         | -
Check 2      | 0m0.654s | 0m5.670s | 0m9.905s | 0m14.238s | 0m17.520s | 0m22.101s
Check 3      | 0m0.647s | 0m5.611s | 0m9.085s | 0m13.626s | 0m18.679s | 0m25.513s
Check 4      | 0m0.649s | 0m5.381s | 0m9.117s | 0m13.692s | 0m18.878s | 0m25.354s
Check 5      | 0m0.649s | 0m6.178s | 0m9.032s | 0m13.783s | 0m18.593s | 0m25.377s

Orada, kontrol 2'nin diğer kontrollere kıyasla bir dezavantajı yoktur ve sıfırlar daha yaygın hale geldikçe faydaları korur.

Yine de derleyiciniz ve temsilci örnek verilerinizle ne olduğunu görmek için gerçekten ölçmelisiniz.


4
Girişlerin% 50'sini d=0neredeyse her zaman yapmak yerine rastgele yapın d!=0ve daha fazla şube tahmin hatası göreceksiniz. Şube tahmini, bir şubenin neredeyse her zaman takip edilmesi veya bir veya diğerinin takibi gerçekten topaksa harika ...
Yakk - Adam Nevraumont

@Yakk dYineleme iç döngüdür, bu nedenle d == 0vakalar eşit olarak dağıtılır. Ve vakaların% 50'sini d == 0gerçekçi kılıyor mu?

2
yapıyor 0.002%olguların d==0gerçekçi? Davanıza ulaştığınız her 65000 yinelemede dağıtılırlar d==0. İken 50%kudreti sık olmaz, 10%ya 1%kolayca gerçekleşmesi, hatta verebilir 90%veya 99%. Test sadece gerçekten test ediyor "Eğer temelde asla bir daldan aşağı inmezseniz, dal tahmini dalı kaldırmayı anlamsız hale getirir mi?" Cevabı "evet, ama bu ilginç değil".
Yakk - Adam Nevraumont

1
Hayır, çünkü farklılıklar gürültü nedeniyle etkili bir şekilde görünmez olacaktır.
Joe

3
Sıfırların dağılımı, soruyu soranın durumunda bulunan dağılımla ilgili değildir. 0 alfa ve diğerlerinin karışımını içeren görüntülerde delikler veya düzensiz şekil vardır, ancak (genellikle) bu gürültü değildir. Veriler hakkında hiçbir şey bilmediğinizi varsaymak (ve bunun gürültü olduğunu düşünmek) bir hatadır. Bu, 0 alfa değerine sahip olabilen gerçek görüntülere sahip gerçek bir dünya uygulamasıdır. Ve bir piksel dizisi büyük olasılıkla a = 0 veya tümü a> 0'a sahip olacağından, dal tahmininden yararlanmak çok iyi olabilir, özellikle a = 0 çok ve (yavaş) bölünmeler (15+ döngü) meydana geldiğinde !) kaçınılır.
DDS

13

Platformu bilmeden en verimli yöntemi tam olarak bilmenin bir yolu yoktur, ancak genel bir sistemde bu optimuma yakın olabilir (Intel assembler sözdizimi kullanılarak):

(bölenin içinde olduğunu ecxve temettü payınıneax )

mov ebx, ecx
neg ebx
sbb ebx, ebx
add ecx, ebx
div eax, ecx

Dört dallanmamış, tek döngülü talimat artı bölme. Bölüm içeride olacak eaxve geri kalanı edxsonunda olacak. (Bu tür bir erkek işi için neden bir derleyici göndermek istemediğinizi gösterir).


bölüm nerede?
Yakk - Adam Nevraumont

1
bu bölme yapmaz, sadece böleni kirletir, böylece sıfıra bölme imkansızdır
Tyler Durden,

@Jens Timmerman Üzgünüm, bunu div ifadesini eklemeden önce yazmıştım. Metni güncelledim.
Tyler Durden

1

Bu bağlantıya göre , SIGFPE sinyalini ile engelleyebilirsiniz sigaction()(kendim denemedim, ancak çalışması gerektiğine inanıyorum).

Sıfıra bölme hatası çok nadir ise bu, mümkün olan en hızlı yaklaşımdır: yalnızca sıfıra bölme için ödeme yaparsınız, geçerli bölümler için değil, normal yürütme yolu hiç değişmez.

Bununla birlikte, işletim sistemi, göz ardı edilen ve pahalı olan her istisnaya dahil olacaktır. Bence, sıfıra göre bölme başına en az bin iyi bölüme sahip olmalısın. İstisnalar bundan daha sıksa, bölümden önceki her değeri kontrol etmektense istisnaları göz ardı ederek muhtemelen daha fazla ödeme yapacaksınız.

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.