En sevdiğiniz bit bilge tekniği nedir? [kapalı]


14

Birkaç gün önce StackExchange üyesi Anto bit bilge operatörler için geçerli kullanımları sordu . Kaymanın tamsayıları ikisinin gücü ile çarpmaktan ve bölmekten daha hızlı olduğunu söyledim. StackExchange üyesi Daemin, sağa kaymanın negatif sayılarla ilgili sorunlar sunduğunu belirterek karşılık verdi.

Bu noktada, vardiya operatörlerini işaretli tamsayılarla kullanmayı hiç düşünmemiştim. Öncelikle bu tekniği düşük seviye yazılım geliştirmede kullandım; bu nedenle her zaman işaretsiz tamsayılar kullandım. C, işaretsiz tamsayılarda mantıksal kaymalar gerçekleştirir. Doğru bir mantıksal kaydırma gerçekleştirirken işaret bitine dikkat edilmez. Boşaltılan bitler sıfırlarla doldurulur. Bununla birlikte, C, işaretli bir tamsayı sağını kaydırırken aritmetik bir kaydırma işlemi gerçekleştirir. Boş bitler işaret biti ile doldurulur. Bu fark, negatif bir değerin, sıfıra doğru kısaltılmak yerine sonsuza yuvarlanmasına neden olur; bu, işaretli tam sayı bölümünden farklı bir davranıştır.

Birkaç dakikalık düşünce birinci dereceden bir çözümle sonuçlandı. Çözelti, kaydırmadan önce koşullu olarak negatif değerleri pozitif değerlere dönüştürür. Bir değer, vardiya işlemi gerçekleştirildikten sonra koşullu olarak negatif biçimine dönüştürülür.

int a = -5;
int n = 1;

int negative = q < 0; 

a = negative ? -a : a; 
a >>= n; 
a = negative ? -a : a; 

Bu çözümün sorunu, koşullu atama ifadelerinin genellikle en az bir atlama komutuna çevrilmesidir ve atlama komutları, her iki komut yolunun kodunu çözmeyen işlemcilerde pahalı olabilir. Bir talimat boru hattını iki kez yeniden astarlamak zorunda kalmak, bölme üzerinde kayma ile elde edilen herhangi bir performans kazancında iyi bir çökme sağlar.

Yukarıdakilerle birlikte, Cumartesi günü şartlı görev sorununun cevabı ile uyandım. Bir aritmetik vardiya işlemi gerçekleştirirken yaşadığımız yuvarlama problemi, sadece ikisinin tamamlayıcı gösterimi ile çalışırken ortaya çıkar. Kişinin kompleman temsili ile gerçekleşmez. Sorunun çözümü, vardiya işlemini gerçekleştirmeden önce bir ikinin tamamlayıcı değerini bir kişinin tamamlayıcı değerine dönüştürmeyi içerir. Daha sonra kişinin tamamlayıcı değerini ikinin tamamlayıcı değerine dönüştürmeliyiz. Şaşırtıcı bir şekilde, bu operasyon setini, vardiya işlemini gerçekleştirmeden önce negatif değerleri şartlı olarak dönüştürmeden gerçekleştirebiliriz.

int a = -5;
int n = 1;

register int sign = (a >> INT_SIZE_MINUS_1) & 1

a = (a - sign) >> n + sign;   

İkinin tamamlayıcı negatif değeri, çıkartılarak kişinin tamamlayıcı negatif değerine dönüştürülür. Flip tarafında, bir kişinin kompleman negatif değeri bir ikisini tamamlayarak negatif değerine dönüştürülür. Yukarıda listelenen kod, işaret biti ikisinin tamamlayıcısından tamamlayıcısına dönüştürmek için kullanıldığından işe yarar . Yalnızca negatif değerler işaret bitlerini ayarlayacaktır; bu nedenle, a pozitif olduğunda değişken işareti sıfıra eşit olacaktır .

Yukarıdakilerle birlikte, yukarıdaki hileler gibi hileler çantanıza girmiş gibi düşünebilir misiniz? En sevdiğiniz bit bilge kesmek nedir? Her zaman yeni performans odaklı bit bilge kesmek için arıyorum.


3
Bu soru ve hesap adınız - Dünya tekrar mantıklı ...
JK

+1 Benimki ve başka türlü bir takip olarak ilginç bir soru;)
Anto

Bir kez de hızlı parite hesaplamaları yaptım. Eşlik biraz acıdır, çünkü geleneksel olarak, hepsi çok fazla atlama gerektiren döngüler ve biraz ayarlanmışsa saymayı içerir. Parite, shift ve XOR kullanılarak hesaplanabilir, daha sonra birbiri ardına yapılanların bir kısmı döngülerden ve sıçramalardan kaçınır.
hızla

2
Bu tekniklerle ilgili bir kitap olduğunu biliyor musunuz? - Hackerlar Delight amazon.com/Hackers-Delight-Henry-S-Warren/dp/0201914654
nikie

Evet, bit işlemlerine de ayrılmış bir web sitesi var. URL'yi unuttum, ancak google onu yakında açacak.
quickly_now

Yanıtlar:


23

Gosper'in hackini (HAKMEM # 175) seviyorum, bir sayı almanın ve aynı sayıda bitle bir sonraki sayıyı almanın çok kurnaz bir yolu. Örneğin, bu şekilde kürün kombinasyonları oluştururken faydalıdır n:

int set = (1 << k) - 1;
int limit = (1 << n);
while (set < limit) {
    doStuff(set);

    // Gosper's hack:
    int c = set & -set;
    int r = set + c;
    set = (((r^set) >>> 2) / c) | r;
}

7
+1. Ama bundan sonra, bir hata ayıklama oturumu sırasında yorum yapmadan bunu bulmak için kabuslar göreceğim .
nikie

@nikie, muahahahaha! (Bunu Project Euler sorunları gibi şeyler için kullanma eğilimindeyim - günlük işim çok kombinatorik içermiyor).
Peter Taylor

7

Hızlı ters kare kök yöntemi şimdiye kadar gördüğüm bir kare kökü tersini hesaplamak için en tuhaf bit düzeyinde teknikleri kullanır:

float Q_rsqrt( float number )
{
    long i;
    float x2, y;
    const float threehalfs = 1.5F;

    x2 = number * 0.5F;
    y  = number;
    i  = * ( long * ) &y;                       // evil floating point bit level hacking [sic]
    i  = 0x5f3759df - ( i >> 1 );               // what the fuck? [sic]
    y  = * ( float * ) &i;
    y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
    //    y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed

    return y;
}

Hızlı sqrt da şaşırtıcı. Carmack en büyük kodlayıcılardan biri gibi görünüyor.
BenjaminB

Vikipedi daha da eski kaynaklara sahiptir, örneğin beyond3d.com/content/articles/15
MSalters

0

3'e bölün - çalışma zamanı kütüphane çağrısına başvurmadan.

3'e bölünmenin (Yığın Taşması ile ilgili bir ipucu sayesinde) şu şekilde tahmin edilebilir:

X / 3 = [(x / 4) + (x / 12)]

Ve X / 12 (x / 4) / 3'tür. Burada aniden ortaya çıkan bir tekrarlama unsuru vardır.

Ayrıca, oynadığınız sayıların aralığını sınırlarsanız, gerekli yineleme sayısını sınırlandırabileceğiniz de ortaya çıkar.

Ve böylece, 2000'den küçük işaretsiz tamsayılar için aşağıdakiler hızlı ve basit bir / 3 algoritmasıdır. (Daha büyük sayılar için daha fazla adım eklemeniz yeterlidir). Derleyiciler, halkı bundan optimize eder, böylece hızlı ve küçük olurlar:

statik imzasız kısa FastDivide3 (const imzasız kısa arg)
{
  imzasız kısa RunningSum;
  imzasız kısa Fraksiyonel Twelth;

  RunningSum = arg >> 2;

  FractionalTwelth = RunningSum >> 2;
  RunningSum + = FractionalTwelth;

  Kesirliİki >> >> 2;
  RunningSum + = FractionalTwelth;

  Kesirliİki >> >> 2;
  RunningSum + = FractionalTwelth;

  Kesirliİki >> >> 2;
  RunningSum + = FractionalTwelth;

  // Daha fazla hassasiyet için yukarıdaki 2 satırın daha fazla tekrarı

  RunningSum'a dönüş;
}

1
Tabii ki, bu sadece çok belirsiz mikrodenetleyicilerle ilgilidir. Son yirmi yılda yapılan herhangi bir gerçek CPU, tamsayı bölümü için bir çalışma zamanı kütüphanesine ihtiyaç duymaz.
MSalters

1
Elbette, donanım çarpanı olmayan küçük mikrolar aslında çok yaygındır. Ve gömülü arazide çalışıyorsanız ve satılan bir milyon ürünün her birinde 0.10 $ tasarruf etmek istiyorsanız, o zaman kirli hileler daha iyi bilirsiniz. Tasarruf etti = ekstra kâr, patronunuzu çok mutlu ediyor.
quickly_now

Kirli ... sadece çarparak .0101010101(yaklaşık 1/3). Profesyonel ipucu: .000100010001ve ile çarpabilirsiniz 101(sadece 3 bit kaydırır, ancak daha iyi bir yaklaşıma sahiptir.010101010101
MSalters

Bunu yalnızca tamsayılarla ve kayan nokta olmadan nasıl yapabilirim?
quickly_now

1
Bitsel, x * 101 = x + x << 2. Benzer şekilde, x * 0.000100010001 x >> 4 + x >> 8 + x >> 12'dir.
MSalters

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.