SORU
10 EKİM 2014, Cuma


Bu işaretçi kullanılarak sıcak döngüde garip deoptimization neden olur

Geçenlerde garip bir deoptimization (ya da daha doğrusu cevapsız optimizasyonu fırsat) rastladım.

3-bit 8-bit tamsayı için tamsayı dizileri etkin bir şekilde açmak için bu işlevi göz önünde bulundurun. Her döngü tekrarında 16 in ayıklar:

void unpack3bit(uint8_t* target, char* source, int size) {
   while(size > 0){
      uint64_t t = *reinterpret_cast<uint64_t*>(source);
      target[0] = t & 0x7;
      target[1] = (t >> 3) & 0x7;
      target[2] = (t >> 6) & 0x7;
      target[3] = (t >> 9) & 0x7;
      target[4] = (t >> 12) & 0x7;
      target[5] = (t >> 15) & 0x7;
      target[6] = (t >> 18) & 0x7;
      target[7] = (t >> 21) & 0x7;
      target[8] = (t >> 24) & 0x7;
      target[9] = (t >> 27) & 0x7;
      target[10] = (t >> 30) & 0x7;
      target[11] = (t >> 33) & 0x7;
      target[12] = (t >> 36) & 0x7;
      target[13] = (t >> 39) & 0x7;
      target[14] = (t >> 42) & 0x7;
      target[15] = (t >> 45) & 0x7;
      source =6;
      size-=6;
      target =16;
   }
}

Burada kod parçaları için oluşturulan derleme:

 ...
 367:   48 89 c1                mov    rcx,rax
 36a:   48 c1 e9 09             shr    rcx,0x9
 36e:   83 e1 07                and    ecx,0x7
 371:   48 89 4f 18             mov    QWORD PTR [rdi 0x18],rcx
 375:   48 89 c1                mov    rcx,rax
 378:   48 c1 e9 0c             shr    rcx,0xc
 37c:   83 e1 07                and    ecx,0x7
 37f:   48 89 4f 20             mov    QWORD PTR [rdi 0x20],rcx
 383:   48 89 c1                mov    rcx,rax
 386:   48 c1 e9 0f             shr    rcx,0xf
 38a:   83 e1 07                and    ecx,0x7
 38d:   48 89 4f 28             mov    QWORD PTR [rdi 0x28],rcx
 391:   48 89 c1                mov    rcx,rax
 394:   48 c1 e9 12             shr    rcx,0x12
 398:   83 e1 07                and    ecx,0x7
 39b:   48 89 4f 30             mov    QWORD PTR [rdi 0x30],rcx
 ...

Oldukça verimli görünüyor. Sadece shift right and target store bir tampon izledi. Ama şimdi, bir yapı, bir yöntemin işlevi değiştirirsem ne olur: bak

struct T{
   uint8_t* target;
   char* source;
   void unpack3bit( int size);
};

void T::unpack3bit(int size) {
        while(size > 0){
           uint64_t t = *reinterpret_cast<uint64_t*>(source);
           target[0] = t & 0x7;
           target[1] = (t >> 3) & 0x7;
           target[2] = (t >> 6) & 0x7;
           target[3] = (t >> 9) & 0x7;
           target[4] = (t >> 12) & 0x7;
           target[5] = (t >> 15) & 0x7;
           target[6] = (t >> 18) & 0x7;
           target[7] = (t >> 21) & 0x7;
           target[8] = (t >> 24) & 0x7;
           target[9] = (t >> 27) & 0x7;
           target[10] = (t >> 30) & 0x7;
           target[11] = (t >> 33) & 0x7;
           target[12] = (t >> 36) & 0x7;
           target[13] = (t >> 39) & 0x7;
           target[14] = (t >> 42) & 0x7;
           target[15] = (t >> 45) & 0x7;
           source =6;
           size-=6;
           target =16;
        }
}

Oluşturulan derleme eskisi gibi olması gerektiğini düşündüm, ama değil. İşte bunun bir parçası:

...
 2b3:   48 c1 e9 15             shr    rcx,0x15
 2b7:   83 e1 07                and    ecx,0x7
 2ba:   88 4a 07                mov    BYTE PTR [rdx 0x7],cl
 2bd:   48 89 c1                mov    rcx,rax
 2c0:   48 8b 17                mov    rdx,QWORD PTR [rdi] // Load, BAD!
 2c3:   48 c1 e9 18             shr    rcx,0x18
 2c7:   83 e1 07                and    ecx,0x7
 2ca:   88 4a 08                mov    BYTE PTR [rdx 0x8],cl
 2cd:   48 89 c1                mov    rcx,rax
 2d0:   48 8b 17                mov    rdx,QWORD PTR [rdi] // Load, BAD!
 2d3:   48 c1 e9 1b             shr    rcx,0x1b
 2d7:   83 e1 07                and    ecx,0x7
 2da:   88 4a 09                mov    BYTE PTR [rdx 0x9],cl
 2dd:   48 89 c1                mov    rcx,rax
 2e0:   48 8b 17                mov    rdx,QWORD PTR [rdi] // Load, BAD!
 2e3:   48 c1 e9 1e             shr    rcx,0x1e
 2e7:   83 e1 07                and    ecx,0x7
 2ea:   88 4a 0a                mov    BYTE PTR [rdx 0xa],cl
 2ed:   48 89 c1                mov    rcx,rax
 2f0:   48 8b 17                mov    rdx,QWORD PTR [rdi] // Load, BAD!
 ...

Gördüğünüz gibi, her vardiya önce bellek (mov rdx,QWORD PTR [rdi]) ek bir gereksiz load sunduk. target işaretçiyi şimdi yerel bir değişken yerine bir üyesi olan) her zaman içine saklamadan önce yeniden yüklenmesi gerekiyor gibi görünüyor.Bu kod oldukça (benim ölçüm de  civarında) yavaşlatır.

İlk düşündüm C bellek modeli zorlayan bir üyesi gösterici olmayabilir saklı bir kayıt ama olmalı yeniden, ama bu zor bir seçim gibi görünüyordu, o-cekti yapmak çok kalıcı iyileştirmeleri imkansız. Derleyici bir kayıt target burada depolamak değil ki çok şaşırdım.

Üye yerel bir değişken içine kendimi işaretçi önbellekleme çalıştım:

void T::unpack3bit(int size) {
    while(size > 0){
       uint64_t t = *reinterpret_cast<uint64_t*>(source);
       uint8_t* target = this->target; // << ptr cached in local variable
       target[0] = t & 0x7;
       target[1] = (t >> 3) & 0x7;
       target[2] = (t >> 6) & 0x7;
       target[3] = (t >> 9) & 0x7;
       target[4] = (t >> 12) & 0x7;
       target[5] = (t >> 15) & 0x7;
       target[6] = (t >> 18) & 0x7;
       target[7] = (t >> 21) & 0x7;
       target[8] = (t >> 24) & 0x7;
       target[9] = (t >> 27) & 0x7;
       target[10] = (t >> 30) & 0x7;
       target[11] = (t >> 33) & 0x7;
       target[12] = (t >> 36) & 0x7;
       target[13] = (t >> 39) & 0x7;
       target[14] = (t >> 42) & 0x7;
       target[15] = (t >> 45) & 0x7;
       source =6;
       size-=6;
       this->target =16;
    }
}

Bu kod da "" ek depolar olmadan. çevirici iyi verim Yani benim tahminim: derleyici "sıcak işaretçi" her zaman yerel bir değişkende saklanmalıdır. bu yüzden bir yapı üyesi bir gösterici, yük kaldırma için izin verilmez

  • Neden derleyici bu yükleri optimize edilemiyor?
  • Bu yasaklar o C bellek modeli mi? Ya da sadece benim derleyici bir eksiklik mi?
  • Benim tahminim ya optimizasyon yapılabilir oluş nedeni nedir doğru mu?

Kullanılan derleyici g 4.8.2-19ubuntu1 -O3 optimizasyon ile. Ben de benzer sonuçlar ile clang 3.4-1ubuntu3 denedim: Çınlama yerel target işaretçi. yöntem vektörize etmek bile mümkün. Ancak, this->target işaretçi aynı sonucu verir kullanımı: her mağaza, işaretçiyi önce fazladan Bir yük.

Kontrol ettim çevirici bazı benzer yöntem ve sonuç aynı: Öyle görünüyor ki üye this Her zaman yeniden önce bir mağaza bile böyle bir yük olabilir sadece, bahçenin dışında döngü. Kod bir sürü bu ek depolar kurtulmak, özellikle sıcak kodunu yukarıda bildirilen yerel bir değişken işaretçi kendimi saklama almak için yeniden yazmak zorunda kalacağım.Ama ben yerel bir değişken bir işaretçi önbellekleme kesinlikle Derleyiciler çok zeki aldık bu gün erken optimizasyonu için uygun olarak her zaman bu tür ayrıntılar ile işe yaramaz düşündüm. Ama yanlış geldim galiba. Sıcak bir döngüde üyesi bir işaretçi önbelleğe gerekli manuel optimizasyon tekniği gibi görünüyor.

CEVAP
10 EKİM 2014, Cuma


Pointer aliasing sorunu, this this->target arasında ironik gibi görünüyor. Derleyici hesabına başlatıldı oldukça müstehcen imkanı alıyor:

this->target = &this

Bu durumda, this->target[0] yazılı this içeriğini değiştirebilir (ve böylece, bu->hedef).

Bellek aliasing sorunu yukarıda sınırlı değildir. Prensip olarak, XX this->target[XX] () uygun değer verilen birini kullanın this işaret edebilir.

Bu işaretçi değişkenleri bildirerek çözülebilir daha iyi ____ anahtar kelime ile sınırlamak C tecrübeli değilim.

Bunu Paylaş:
  • Google+
  • E-Posta
Etiketler:

YORUMLAR

SPONSOR VİDEO

Rastgele Yazarlar

  • Android Police

    Android Poli

    21 NİSAN 2010
  • Damien Walters

    Damien Walte

    20 AĞUSTOS 2006
  • tinycammonitor

    tinycammonit

    14 Aralık 2010