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
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.
Neden bu iki kez (1927) garip bir sonu...
Neden nesne yerine bir işaretçi kendis...
Aslında bir Yığın Taşma hatası neden o...
Neden tanıtan bir işe yaramaz MOV tali...
Neden benim değişken sonra tam ben bir...