Bir tam sayının bilinen değer kümeleriyle iki tam sayı (dahil) arasında olup olmadığını belirlemenin en hızlı yolu


389

x >= start && x <= endBir tamsayının iki tamsayı arasında olup olmadığını test etmenin C veya C ++ ' dan daha hızlı bir yolu var mı ?

GÜNCELLEME : Özel platformum iOS. Bu, belirli bir karedeki pikselleri bir daireyle sınırlayan bir kutu bulanıklaştırma işlevinin bir parçasıdır.

GÜNCELLEME : Kabul edilen cevabı denedikten sonra , kodun bir satırında normal x >= start && x <= endşekilde yapmanın üstünde bir hız artışı emri aldım .

GÜNCELLEME : İşte XCode'un derleyicisi ile sonraki ve öncesi kodu:

YENİ YOL

// diff = (end - start) + 1
#define POINT_IN_RANGE_AND_INCREMENT(p, range) ((p++ - range.start) < range.diff)

Ltmp1313:
 ldr    r0, [sp, #176] @ 4-byte Reload
 ldr    r1, [sp, #164] @ 4-byte Reload
 ldr    r0, [r0]
 ldr    r1, [r1]
 sub.w  r0, r9, r0
 cmp    r0, r1
 blo    LBB44_30

ESKİ YOL

#define POINT_IN_RANGE_AND_INCREMENT(p, range) (p <= range.end && p++ >= range.start)

Ltmp1301:
 ldr    r1, [sp, #172] @ 4-byte Reload
 ldr    r1, [r1]
 cmp    r0, r1
 bls    LBB44_32
 mov    r6, r0
 b      LBB44_33
LBB44_32:
 ldr    r1, [sp, #188] @ 4-byte Reload
 adds   r6, r0, #1
Ltmp1302:
 ldr    r1, [r1]
 cmp    r0, r1
 bhs    LBB44_36

Dallanmayı azaltmanın veya ortadan kaldırmanın böylesine dramatik bir hızlanmayı nasıl sağlayabileceği oldukça şaşırtıcı.


28
Bunun sizin için yeterince hızlı olmadığından neden endişeleniyorsunuz?
Matt Ball

90
Kimin umurunda, ilginç bir soru. Bu sadece bir meydan okuma için bir meydan okuma.
David, Reinstate Monica

46
@SLaks Yani tüm bu soruları körü körüne görmezden gelmeliyiz ve sadece "optimize edicinin yapmasına izin verilsin mi?"
David, Reinstate Monica'yı

87
sorunun neden sorulduğu önemli değil. Cevap hayır
tay10r

41
Bu, uygulamalarımdan birinde bir işlevde bir darboğaz
jjxtra

Yanıtlar:


527

Bunu sadece bir karşılaştırma / dal ile yapmak için eski bir hile var. Hızı gerçekten geliştirip geliştirmeyeceği sorgulamaya açık olabilir ve bunu yapsa bile, muhtemelen fark etmek veya önemsemek için çok azdır, ancak sadece iki karşılaştırma ile başladığınızda, büyük bir gelişme şansı oldukça uzaktır. Kod şöyle görünür:

// use a < for an inclusive lower bound and exclusive upper bound
// use <= for an inclusive lower bound and inclusive upper bound
// alternatively, if the upper bound is inclusive and you can pre-calculate
//  upper-lower, simply add + 1 to upper-lower and use the < operator.
    if ((unsigned)(number-lower) <= (upper-lower))
        in_range(number);

Tipik, modern bir bilgisayarla (yani, iki tamamlayıcı kullanan herhangi bir şey), imzasıza dönüştürme gerçekten bir nop - sadece aynı bitlerin nasıl görüntülendiğinde bir değişiklik.

Tipik bir durumda, upper-lower(varsayılan) bir döngü dışında önceden hesaplayabileceğinizi unutmayın , böylece normalde önemli bir zamana katkıda bulunmaz. Şube talimatlarının sayısını azaltmanın yanı sıra, bu (genellikle) şube tahminini de iyileştirir. Bu durumda, sayı aralığın alt ucunun altında mı yoksa üst ucunun üstünde mi olsa aynı dal alınır.

Bunun nasıl çalıştığı konusunda temel fikir oldukça basittir: işaretsiz bir sayı olarak bakıldığında negatif bir sayı, pozitif bir sayı olarak başlayan her şeyden daha büyük olacaktır.

Pratikte bu yöntem number, menşe noktasına kadar olan aralığı çevirir ve nerede numberaralıkta olup olmadığını kontrol eder . Eğer : Bu bağlanmış alt negatif üst yukarıda bağlanmış ise ve: daha büyük .[0, D]D = upper - lowernumberD


8
@ TomásBadan: Her ikisi de makul bir makinede bir döngü olacak. Pahalı olan şube.
Oliver Charlesworth

3
Kısa devre nedeniyle ek dallanma yapılır? Bu durumda, lower <= x & x <= upper(yerine lower <= x && x <= upper) daha iyi performans ile sonuçlanır mı?
Markus Mayr

6
@ AK4749, jxh: Bu külçe olduğu kadar havalı olmaktan çıkıyorum, çünkü maalesef bunun pratikte daha hızlı olduğunu öne sürecek hiçbir şey yok (birisi sonuçta ortaya çıkan montajcı ve profil oluşturma bilgisini karşılaştırana kadar). Tüm bildiğimiz için, OP'nin derleyicisi OP'nin kodunu tek bir şube opcode ile oluşturabilir ...
Oliver Charlesworth

152
VAY!!! Bu, bu özel kod satırı için uygulamamda bir büyüklük iyileştirme sırası ile sonuçlandı. Üst-alt hesabı önceden hesaplayarak profillemem bu fonksiyonun% 25'inden% 2'nin altına düştü! Darboğaz şimdi toplama ve çıkarma işlemleri, ama şimdi yeterince iyi olabileceğini düşünüyorum :)
jjxtra

28
Ah, @PsychoDad soruyu güncelledi, bunun neden daha hızlı olduğu açık. Gerçek kod derleyici da kısa devre optimize Bu nedenle de karşılaştırma için, bir yan etkiye sahiptir.
Oliver Charlesworth

17

Böyle küçük ölçekte kodlamak için önemli optimizasyonlar yapmak nadirdir. Büyük performans kazançları, kodu daha yüksek bir seviyeden gözlemleyerek ve değiştirerek gelir. Aralık testi ihtiyacını tamamen ortadan kaldırabilir veya O (n ^ 2) yerine sadece O (n) yapabilirsiniz. Eşitsizliğin bir tarafının her zaman ima edilmesi için testleri yeniden sıralayabilirsiniz. Algoritma ideal olsa bile, bu kodun aralık testini 10 milyon kez nasıl yaptığını gördüğünüzde ve kazançları toplamanın ve paralel olarak birçok test yapmak için SSE'yi kullandığınızda kazanç elde etme olasılığı daha yüksektir.


16
Yanıtlarımın yanında durduğum aşağı oylara rağmen: Oluşturulan montaj (kabul edilen cevaba yapılan bir açıklamada macun bağlantısına bakın) bir piksel işleme fonksiyonunun iç döngüsünde bir şey için oldukça korkunç. Kabul edilen cevap düzgün bir hiledir, ancak dramatik etkisi, bir dalın bir kısmını her bir yineleme için ortadan kaldırmak için beklenenin çok ötesindedir. Bazı ikincil etkiler hâkimdir ve yine de tüm süreci bu test üzerinde optimize etme girişiminin tozda zekice aralık karşılaştırması kazanmasını bekliyorum.
Ben Jackson

17

Aynı veriler üzerinden testi kaç kez yapmak istediğinize bağlıdır.

Testi tek bir kez yapıyorsanız, muhtemelen algoritmayı hızlandırmanın anlamlı bir yolu yoktur.

Bunu sonlu değerler kümesi için yapıyorsanız, bir arama tablosu oluşturabilirsiniz. Dizin oluşturma işlemi daha pahalı olabilir, ancak tüm tabloyu önbellekte sığdırabiliyorsanız, işleri hızlandıracak olan tüm dalları koddan kaldırabilirsiniz.

Verileriniz için arama tablosu 128 ^ 3 = 2,097,152 olacaktır. Üç değişkenden birini kontrol edebiliyorsanız start = N, bir kerede tüm örnekleri göz önünde bulundurursanız , çalışma kümesinin boyutu 128^2 = 16432baytlara düşer , bu da çoğu modern önbelleğe iyi uyum sağlar.

Dalsız bir arama tablosunun, açık karşılaştırmalardan yeterince hızlı olup olmadığını görmek için gerçek kodu karşılaştırmanız gerekir.


Yani bir değer, başlangıç ​​ve bitiş verilen bir tür arama depolarsınız ve aralarında olup olmadığını söyleyen bir BOOL içerir mi?
jjxtra

Doğru. Bu bir 3D arama tablosu şöyle olacaktır: bool between[start][end][x]. Erişim düzeninizin neye benzeyeceğini biliyorsanız (örneğin x tekdüze bir şekilde artıyor), tablonun tamamı belleğe sığmasa bile konumu korumak için tabloyu tasarlayabilirsiniz.
Andrew Prock

Bu yöntemi denemeye ve nasıl gittiğini görmeye gidip edemeyeceğimi göreceğim. Nokta daire içinde ise bit ayarlanacak hat başına biraz vektör ile yapmayı planlıyorum. Bunun bir bayt veya int32'den bit maskelemeden daha hızlı olacağını mı düşünüyorsunuz?
jjxtra

2

Bu cevap, kabul edilen cevapla yapılan bir test hakkında rapor vermek içindir. Sıralı rasgele tamsayının büyük bir vektörü üzerinde kapalı bir aralık testi yaptım ve sürpriz olarak temel yöntem (düşük <= num && num <= yüksek) yukarıdaki kabul edilen cevaptan daha hızlı! Test, HP Pavilion g6 (6GB ram'lı AMD A6-3400APU) ​​üzerinde yapılmıştır. İşte test için kullanılan temel kod:

int num = rand();  // num to compare in consecutive ranges.
chrono::time_point<chrono::system_clock> start, end;
auto start = chrono::system_clock::now();

int inBetween1{ 0 };
for (int i = 1; i < MaxNum; ++i)
{
    if (randVec[i - 1] <= num && num <= randVec[i])
        ++inBetween1;
}
auto end = chrono::system_clock::now();
chrono::duration<double> elapsed_s1 = end - start;

yukarıdaki kabul edilen cevap olan aşağıdakilerle karşılaştırıldığında:

int inBetween2{ 0 };
for (int i = 1; i < MaxNum; ++i)
{
    if (static_cast<unsigned>(num - randVec[i - 1]) <= (randVec[i] - randVec[i - 1]))
        ++inBetween2;
}

RandVec'in sıralı bir vektör olduğuna dikkat edin. Herhangi bir MaxNum boyutu için ilk yöntem ikincisini makinemde atıyor!


1
Verilerim sıralanmamış ve testlerim iPhone arm CPU'da. Farklı veri ve CPU'lu sonuçlarınız farklı olabilir.
jjxtra

testimde sıralanan sadece üst sınırın alt sınırdan küçük olmamasını sağlamaktı.
rezeli

1
Sıralanan sayılar, şube tahmininin çok güvenilir olacağı ve geçiş noktalarında birkaç tanesi hariç tüm dalları doğru alacağı anlamına gelir. Şubesiz kodun avantajı, öngörülemeyen verilerdeki bu tür yanlış tahminlerden kurtulmasıdır.
Andreas Klebinger

0

Değişken aralık kontrolü için:

if (x >= minx && x <= maxx) ...

Bit işlemini kullanmak daha hızlıdır:

if ( ((x - minx) | (maxx - x)) >= 0) ...

Bu iki dalı bire indirecektir.

Güvenli kasayı önemsiyorsanız:

if ((int32_t)(((uint32_t)x - (uint32_t)minx) | ((uint32_t)maxx - (uint32_t)x)) > = 0) ...

Daha fazla değişken aralık kontrolünü bir araya getirebilirsiniz:

if (( (x - minx) | (maxx - x) | (y - miny) | (maxy - y) ) >= 0) ...

Bu 4 dalı 1'e indirecektir.

Öyle hızlı 3.4 kat gcc eski olandan:

resim açıklamasını buraya girin


-4

Tamsayı üzerinde sadece bitsel işlem yapmak mümkün değil mi?

0 ile 128 arasında olması gerektiğinden, 8. bit ayarlanmışsa (2 ^ 7) 128 veya daha fazladır. Yine de kapsamlı bir karşılaştırma yapmak istediğiniz için kenar durumu bir acı olacaktır.


3
x <= endNerede olduğunu bilmek istiyor end <= 128. Hayır x <= 128.
Ben Voigt

1
Bu ifade " 0 ile 128 arasında olması gerektiğinden, 8. bit ayarlanmışsa (2 ^ 7) 128 veya daha fazladır " yanlıştır. 256.'yı düşünün.
Happy Green Kid Naps

1
Evet, görünüşe göre bunu yeterince düşünmedim. Afedersiniz.
icedwater
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.