C ++ 'da, değişkenleri önbelleğe almalı mıyım yoksa derleyicinin optimizasyonu yapmasına izin mi vermeliyim? (Örtüşme)


114

(Aşağıdaki kodu düşünün ptiptedir unsigned char*ve bitmap->widthbilinmeyen ve biz kullandığınız bazı dış kütüphanenin hangi sürümüne bağlıdır tam olarak hangi bazı tamsayı tipidir):

for (unsigned x = 0;  x < static_cast<unsigned>(bitmap->width);  ++x)
{
    *p++ = 0xAA;
    *p++ = 0xBB;
    *p++ = 0xCC;
}

Optimize etmeye değer mi [..]

Bunun yazarak daha verimli sonuçlar verebileceği bir durum olabilir mi:

unsigned width(static_cast<unsigned>(bitmap->width));
for (unsigned x = 0;  x < width;  ++x)
{
    *p++ = 0xAA;
    *p++ = 0xBB;
    *p++ = 0xCC;
}

... yoksa derleyicinin optimize etmesi önemsiz mi?

"Daha iyi" kod olarak neyi düşünürdünüz?

Editörün notu (Ike): Üstü çizili metni merak edenler için, orijinal soru, ifade edildiği şekliyle, tehlikeli bir şekilde konu dışı bölgeye yakındı ve olumlu geri bildirimlere rağmen kapatılmaya çok yakındı. Bunlar mahvoldu. Yine de lütfen sorunun bu etkilenen bölümlerine değinen cevaplayıcıları cezalandırmayın.


19
Eğer *paynı tiptedir widtho zamandan bu yana, optimize için önemsiz değildir pişaret olabilir widthve döngü içinde değiştirin.
emlai

31
Derleyicinin belirli bir işlemi optimize edip etmediğini sormak genellikle yanlış sorudur. Nihayetinde (genellikle) ilgilendiğiniz şey, hangi sürümün daha hızlı çalıştığıdır, bunu basitçe ölçmeniz gerekir.
SirGuy

4
@GuyGreer Katılıyorum, ancak sorunun iyi veya en azından ilginç olduğunu söylememe rağmen, maalesef cevabın "kullanım durumu başına ölçmek zorundasın" olduğunu düşündüm. Bunun nedeni, işlevselliğin taşınabilir olması ancak performansın olmamasıdır. Bu nedenle, derleyiciden başlayıp hedef sitede (işletim sistemi / donanım kombinasyonu) biten, derleme sürecinin her bölümüne bağlıdır. Ve elbette en iyi tahmin, derleyicinin bu konuda insandan daha zeki olmasıdır.
luk32

19
Derleyici olsaydım, iki örneğinizin aynı olmadığını görürdüm. pAynı anıya işaret etmesi mümkündür bitmap->width. Bu nedenle ilk örneği ikinci örneğe yasal olarak optimize edemiyorum.
Gizemli

4
"P" nerede saklanır? "Char * restrict p2 = p;" gibi bir şey yaparak gerçekten çok büyük bir performans kazanmanızı öneririm. ve sonra döngünüzde "p" yerine "p2" kullanın. Sonra, "p2" deki değişikliklerin tekrar p'ye uygulanmasını istiyorsanız, "p + = (p2-p);" kullanın. P2'nin yaşam süresi içinde p2 formuna kopyalanmamış bir işaretçi tarafından yazılan hiçbir göstericinin p2'den kopyalanan bir işaretçi kullanılarak okunamayacağına veya bunun tersine, p2'nin hiçbir kopyasının p2'nin yaşam süresinden sonra herhangi bir amaç için kullanılamayacağına, ancak bir derleyicinin bunları kullanabileceğine dikkat edin. başka yollarla gerçekleştirilemeyen optimizasyonları etkinleştirmek için gerçekler.
supercat

Yanıtlar:


81

İlk bakışta, derleyicinin her iki sürüm için de optimizasyon bayrakları etkinleştirilerek eşdeğer derleme oluşturabileceğini düşündüm. Kontrol ettiğimde sonucu görünce şaşırdım:

Kaynak unoptimized.cpp

not: bu kodun çalıştırılması amaçlanmamıştır.

struct bitmap_t
{
    long long width;
} bitmap;

int main(int argc, char** argv)
{
    for (unsigned x = 0 ; x < static_cast<unsigned>(bitmap.width) ; ++x)
    {
        argv[x][0] = '\0';
    }
    return 0;
}

Kaynak optimized.cpp

not: bu kodun çalıştırılması amaçlanmamıştır.

struct bitmap_t
{
    long long width;
} bitmap;

int main(int argc, char** argv)
{
    const unsigned width = static_cast<unsigned>(bitmap.width);
    for (unsigned x = 0 ; x < width ; ++x)
    {
        argv[x][0] = '\0';
    }
    return 0;
}

Derleme

  • $ g++ -s -O3 unoptimized.cpp
  • $ g++ -s -O3 optimized.cpp

Montaj (optimize edilmemiş.s)

    .file   "unoptimized.cpp"
    .text
    .p2align 4,,15
.globl main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    .cfi_personality 0x3,__gxx_personality_v0
    movl    bitmap(%rip), %eax
    testl   %eax, %eax
    je  .L2
    xorl    %eax, %eax
    .p2align 4,,10
    .p2align 3
.L3:
    mov %eax, %edx
    addl    $1, %eax
    movq    (%rsi,%rdx,8), %rdx
    movb    $0, (%rdx)
    cmpl    bitmap(%rip), %eax
    jb  .L3
.L2:
    xorl    %eax, %eax
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
.globl bitmap
    .bss
    .align 8
    .type   bitmap, @object
    .size   bitmap, 8
bitmap:
    .zero   8
    .ident  "GCC: (GNU) 4.4.7 20120313 (Red Hat 4.4.7-16)"
    .section    .note.GNU-stack,"",@progbits

Montaj (optimize edilmiş.s)

    .file   "optimized.cpp"
    .text
    .p2align 4,,15
.globl main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    .cfi_personality 0x3,__gxx_personality_v0
    movl    bitmap(%rip), %eax
    testl   %eax, %eax
    je  .L2
    subl    $1, %eax
    leaq    8(,%rax,8), %rcx
    xorl    %eax, %eax
    .p2align 4,,10
    .p2align 3
.L3:
    movq    (%rsi,%rax), %rdx
    addq    $8, %rax
    cmpq    %rcx, %rax
    movb    $0, (%rdx)
    jne .L3
.L2:
    xorl    %eax, %eax
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
.globl bitmap
    .bss
    .align 8
    .type   bitmap, @object
    .size   bitmap, 8
bitmap:
    .zero   8
    .ident  "GCC: (GNU) 4.4.7 20120313 (Red Hat 4.4.7-16)"
    .section    .note.GNU-stack,"",@progbits

diff

$ diff -uN unoptimized.s optimized.s
--- unoptimized.s   2015-11-24 16:11:55.837922223 +0000
+++ optimized.s 2015-11-24 16:12:02.628922941 +0000
@@ -1,4 +1,4 @@
-   .file   "unoptimized.cpp"
+   .file   "optimized.cpp"
    .text
    .p2align 4,,15
 .globl main
@@ -10,16 +10,17 @@
    movl    bitmap(%rip), %eax
    testl   %eax, %eax
    je  .L2
+   subl    $1, %eax
+   leaq    8(,%rax,8), %rcx
    xorl    %eax, %eax
    .p2align 4,,10
    .p2align 3
 .L3:
-   mov %eax, %edx
-   addl    $1, %eax
-   movq    (%rsi,%rdx,8), %rdx
+   movq    (%rsi,%rax), %rdx
+   addq    $8, %rax
+   cmpq    %rcx, %rax
    movb    $0, (%rdx)
-   cmpl    bitmap(%rip), %eax
-   jb  .L3
+   jne .L3
 .L2:
    xorl    %eax, %eax
    ret

Optimize edilmiş versiyon için oluşturulan montaj , her yinelemede ofseti hesaplayan optimize edilmemiş versiyonun aksine sabiti ( lea) yükler ( ).widthwidthmovq ).

Zamanım olduğunda, sonunda bununla ilgili bazı ölçütler yayınlarım. İyi soru.


3
Yalnızca optimize edilmemiş durumda const unsignedyerine unsignedkoduna çevirirseniz kodun farklı şekilde üretilip üretilmediğini görmek ilginç olurdu .
Mark Ransom

2
@MarkRansom Sanırım bir fark yaratmamalı: Sabit olma "vaadi", tüm döngü için değil, yalnızca tek karşılaştırma sırasında
Hagen von Eitzen

13
Lütfen optimizasyonu test etmek için bu işlevi ASLA kullanmayın main. Gcc, kasıtlı olarak soğuk olarak işaretler ve bu nedenle bunun için bazı optimizasyonları devre dışı bırakır. Buradaki durum bu mu bilmiyorum ama bu, içine girmek için önemli bir alışkanlık.
Marc Glisse

3
@MarcGlisse% 100 haklısın. Acelem yazdım, geliştireceğim.
YSC

3
Burada , global olduğu varsayılarak , godbolt üzerindeki tek bir derleme birimindeki her iki işleve ilişkin bir bağlantı var bitmap. CSEd olmayan sürüm, cmpbu durumda performans için bir sorun olmayan bir bellek işleneni kullanır . Eğer bir yerelse, derleyici diğer işaretçilerin onu "bilemeyeceğini" varsayabilir ve onu işaret edebilir. Okunabilirliği iyileştirdiği (veya zarar vermediği) ya da performans kritik olduğu sürece, küreselleri içeren ifadeleri geçici değişkenlerde saklamak kötü bir fikir değildir. Çok şey olmadıkça, bu tür yerliler genellikle kayıtlarda yaşayabilir ve asla dökülmezler.
Peter Cordes

38

Aslında kod pasajınızda söyleyebilmek için yetersiz bilgi var ve aklıma gelen tek şey takma ad. Bizim açımızdan bakıldığında, oldukça İstemediğiniz olduğu açıktır pve bitmapbellekte aynı konuma noktaya ancak (çünkü derleyici olduğunu bilmiyor ve ptiptedir char*derleyici bile bu kod çalışması için vardır) pvebitmap örtüşme.

Bu, bu durumda, döngü bitmap->widthişaretçiden geçerse, ptekrar okurken bunun görülmesi gerektiği anlamına gelir.bitmap->width , bunun daha sonra anlamına gelir, bu da onu yerel bir değişkende saklamanın yasadışı olacağı anlamına gelir.

Bununla birlikte, bazı derleyicilerin aslında bazen aynı kodun iki versiyonunu oluşturacağına inanıyorum (bunun ikinci dereceden kanıtını gördüm, ancak bu durumda derleyicinin ne yaptığı hakkında hiçbir zaman doğrudan bilgi aramadım) ve hızlı bir şekilde işaretçilerin olup olmadığını kontrol edin. alias ve uygun olduğunu belirlerse daha hızlı kodu çalıştırın.

Bununla birlikte, iki sürümün performansını ölçmekle ilgili yorumumun arkasında duruyorum, param, kodun iki sürümü arasında tutarlı bir performans farkı görmemekle ilgili.

Kanımca, amacınız derleyici optimizasyon teorileri ve teknikleri hakkında bilgi edinmekse bu gibi sorular sorun değil, ancak buradaki nihai amacınız programı daha hızlı çalıştırmaksa zaman kaybıdır (işe yaramaz bir mikro optimizasyon).


1
@GuyGreer: Önemli bir optimizasyon engelleyicidir; Farklı öğelerin yazma ve okumalarının sırasız olduğu veya olmadığı durumları tanımlamak yerine, dil kurallarının etkili türlerle ilgili kurallara odaklanmasının talihsiz olduğunu düşünüyorum. Böyle bir terimle yazılan kurallar, derleyici ve programcı ihtiyaçlarını karşılamada mevcut olanlardan çok daha iyi bir iş çıkarabilir.
supercat

3
@GuyGreer - bu durumda bir restrictniteleyici, takma ad sorununun cevabı olmaz mı?
LThode

4
Benim deneyimlerime göre restrict, büyük ölçüde vur-kaçır. MSVC bunu düzgün bir şekilde yapan gördüğüm tek derleyici. ICC, satır içi olsalar bile işlev çağrıları yoluyla takma ad bilgisini kaybeder. Ve GCC, her bir girdi parametresini restrict( thisüye işlevleri dahil ) olarak bildirmediğiniz sürece genellikle herhangi bir fayda sağlamaz .
Gizemli

1
@Mistik: Unutulmaması gereken bir şey, charher türden takma adların olmasıdır, bu nedenle bir karakteriniz varsa * restricther şeyde kullanmanız gerekir . Veya GCC'nin katı diğer adlandırma kurallarını devre dışı bıraktıysanız, -fno-strict-aliasingher şey olası bir takma ad olarak kabul edilir.
Zan Lynx

1
@Ray restrictC ++ 'da benzer anlambilim için en son teklif N4150'dir .
TC

24

Tamam beyler, ben de ölçtüm GCC -O3 (Linux x64'te GCC 4.9 kullanarak).

İkinci sürüm% 54 daha hızlı çalışıyor!

Öyleyse, sanırım örtüşme meselesi, bunun hakkında hiç düşünmemiştim.

[Düzenle]

Tüm işaretçiler ile tanımlanmış ilk sürümü tekrar denedim __restrict__ve sonuçlar aynı. Garip .. Ya takma ad sorun değil ya da bazı nedenlerden dolayı derleyici bunu iyi optimize etmiyor.__restrict__ .

[Düzenle 2]

Tamam, sanırım problemin takma ad olduğunu ispatlayabildim. Orijinal testimi bu sefer işaretçi yerine bir dizi kullanarak tekrarladım:

const std::size_t n = 0x80000000ull;
bitmap->width = n;
static unsigned char d[n*3];
std::size_t i=0;
for (unsigned x = 0;  x < static_cast<unsigned>(bitmap->width);  ++x)
{
    d[i++] = 0xAA;
    d[i++] = 0xBB;
    d[i++] = 0xCC;
}

Ve ölçüldü (bağlamak için "-mcmodel = large" kullanmak zorunda kaldı). Sonra denedim:

const std::size_t n = 0x80000000ull;
bitmap->width = n;
static unsigned char d[n*3];
std::size_t i=0;
unsigned width(static_cast<unsigned>(bitmap->width));
for (unsigned x = 0;  x < width;  ++x)
{
    d[i++] = 0xAA;
    d[i++] = 0xBB;
    d[i++] = 0xCC;
}

Ölçü sonuçları aynıydı - Görünüşe göre derleyici kendi kendine optimize edebilmiş.

Sonra (bir işaretçi ile, orijinal kodları güvenilir pzaman, bu kez) ptiptedir std::uint16_t*. Kesin örtüşme nedeniyle sonuçlar yine aynıydı. Sonra "-fno-katı-örtüşme" ile inşa etmeyi denedim ve yine zaman farkını gördüm.


4
Teknik olarak soruyu cevaplasa da, bu bir yorum olmalı gibi görünüyor. Ayrıca, ne yazık ki, takma adın önemli olduğunu göstermediniz. Muhtemelen, kesinlikle makul görünüyor, ancak bu, öyle olduğu sonucuna varmaktan farklı.
SirGuy

@GuyGreer: [edit 2] 'ye bakın - şimdi bunun oldukça kanıtlanmış olduğunu düşünüyorum.
Yaron Cohen-Tal

2
Döngünüzde "x" varken neden "i" değişkenini kullanmaya başladığınızı merak ediyorum?
Jesper Madsen

1
Bu ifadeyi % 54 daha hızlı anlamayı zor bulan sadece ben miyim? Optimize edilmemiş olanın 1.54 katı mı yoksa başka bir şey mi demek istiyorsun?
Roddy

3
@ YaronCohen-Tal iki kat daha hızlı? Etkileyici, ama "% 54 daha hızlı" demek istediğim şey değil!
Roddy

24

Diğer yanıtlar, işaretçi işlemini döngüden kaldırmanın, karakterin herhangi bir şeyi takma ad vermesine izin veren takma kurallara bağlı olarak tanımlanmış davranışı değiştirebileceğini ve bu nedenle çoğu durumda bir insan için açıkça doğru olsa bile bir derleyici için izin verilen bir optimizasyon olmadığını belirtmiştir. programcı.

Ayrıca, işlemi döngüden çıkarmanın performans açısından her zaman bir gelişme olmamakla birlikte genellikle bir gelişme olduğunu ve okunabilirlik açısından genellikle olumsuz olduğunu belirtmişlerdir.

Çoğunlukla bir "üçüncü yol" olduğunu belirtmek isterim. İstediğiniz yineleme sayısına kadar saymak yerine sıfıra doğru geri sayabilirsiniz. Bu, yineleme sayısının döngünün başlangıcında yalnızca bir kez gerekli olduğu anlamına gelir, bundan sonra depolanması gerekmez. Daha da iyisi, derleyici düzeyinde, genellikle açık bir karşılaştırma ihtiyacını ortadan kaldırır, çünkü azaltma işlemi genellikle sayacın hem azaltmadan önce (taşıma bayrağı) hem de sonra (sıfır işareti) sıfır olup olmadığını gösteren bayraklar ayarlayacaktır.

for (unsigned x = static_cast<unsigned>(bitmap->width);x > 0;  x--)
{
    *p++ = 0xAA;
    *p++ = 0xBB;
    *p++ = 0xCC;
}

Döngünün bu sürümünün, 0 .. (genişlik-1) aralığı yerine 1. genişlik aralığında x değerleri verdiğine dikkat edin. Bu sizin durumunuzda önemli değil çünkü aslında x'i hiçbir şey için kullanmıyorsunuz ama bu farkında olmanız gereken bir şey. 0 .. (genişlik-1) aralığında x değerlerine sahip bir geri sayım döngüsü istiyorsanız yapabilirsiniz.

for (unsigned x = static_cast<unsigned>(bitmap->width); x-- > 0;)
{
    *p++ = 0xAA;
    *p++ = 0xBB;
    *p++ = 0xCC;
}

Ayrıca, bitmap-> width ile yaptığınız tek şey onu doğrudan bir değişkene atamak olduğu için, karşılaştırma kuralları üzerindeki etkisi hakkında endişelenmeden, yukarıdaki örneklerdeki yayınlardan da kurtulabilirsiniz.


2
Olarak biçimlendirilmiş ikinci durumu gördüm x --> 0ve "aşağıya doğru" operatörle sonuçlandı. Çok komik. Not: Son koşul için bir değişken yapmayı okunabilirlik açısından negatif olarak düşünmüyorum, aslında tam tersi olabilir.
Mark Ransom

Gerçekten bağlıdır, bazen bir ifade o kadar korkunç olur ki onu birden fazla ifadeye ayırmak okunabilirliği artırır, ancak burada durumun böyle olduğuna inanmıyorum.
plugwash

1
+1 İyi bir gözlem, yine de onu yükseltmenin static_cast<unsigned>(bitmap->width)ve widthdöngüde kullanmanın aslında okunabilirlik için bir gelişme olduğunu savunabilirim, çünkü artık okuyucunun satır başına ayrıştırması gereken daha az şey var. Başkalarının görüşleri farklı olabilir.
SirGuy

1
Aşağı doğru saymanın daha üstün olduğu birçok başka durum vardır (örneğin, bir listeden öğeleri çıkarırken). Bunun neden daha sık yapılmadığını bilmiyorum.
Ian Goldby

3
Daha çok optimal asm gibi görünen döngüler yazmak istiyorsanız, kullanın do { } while(), çünkü ASM'de sonunda koşullu dallarla döngüler yaparsınız. Normal for(){}ve while(){}döngüler, derleyici her zaman en az bir kez çalıştığını kanıtlayamazsa döngü koşulunu döngüden önce bir kez test etmek için ekstra talimatlar gerektirir. Elbette, kullanın for()veya while()döngünün bir kez çalışıp çalışmadığını veya daha okunabilir olduğunda kontrol etmenin yararlı olduğu durumlarda.
Peter Cordes

11

Burada optimizasyonu engelleyebilecek tek şey, kesin örtüşme kuralıdır . Kısaca :

"Kesin örtüşme, C (veya C ++) derleyicisi tarafından yapılan, işaretçilerin farklı türlerdeki nesnelere başvurmadan kaldırılmasının hiçbir zaman aynı bellek konumuna (yani birbirlerinin takma adlarına) başvurmayacağı varsayımıdır."

[...]

Kuralın istisnası char*, herhangi bir türe işaret etmesine izin verilen a'dır.

İstisna aynı zamanda unsignedvesigned char işaretçileri.

Bu kodda olduğu: Sen modifiye *paracılığıyla pbir olan unsigned char*derleyici böylece gerekir o işaret olabilir varsayalım bitmap->width. Bu nedenle, önbelleğe alınması bitmap->widthgeçersiz bir optimizasyondur. Bu optimizasyonu engelleyici davranış YSC'nin cevabında gösterilmiştir. .

Yalnızca ve ancak tür polmayan charve decltype(bitmap->width)tür olmayan bir işarete işaret edilirse , önbelleğe alma olası bir optimizasyon olur.


10

Başlangıçta sorulan soru:

Optimize etmeye değer mi?

Ve buna cevabım (hem yukarı hem aşağı oyların iyi bir karışımını toplamak ..)

Derleyicinin bunun için endişelenmesine izin verin.

Derleyici neredeyse kesinlikle sizden daha iyi bir iş çıkaracaktır. Ve 'optimizasyonunuzun' 'bariz' koddan daha iyi olduğuna dair hiçbir garanti yoktur - ölçtünüz mü?

Daha da önemlisi, optimize ettiğiniz kodun programınızın performansı üzerinde herhangi bir etkisi olduğuna dair herhangi bir kanıtınız var mı?

Olumsuz oylara (ve şimdi takma ad sorununu görmeye) rağmen, hala geçerli bir cevap olarak bundan memnunum. Bir şeyi optimize etmeye değip değmeyeceğini bilmiyorsanız, muhtemelen değildir.

Elbette oldukça farklı bir soru şu olabilir:

Bir kod parçasını optimize etmeye değip değmeyeceğini nasıl anlarım?

Öncelikle, uygulamanızın veya kitaplığınızın şu anda olduğundan daha hızlı çalışması gerekiyor mu? Kullanıcı çok uzun süre mi bekletiliyor? Yazılımınız yarınınki yerine dünün hava durumunu tahmin ediyor mu?

Yazılımınızın ne için olduğuna ve kullanıcılarınızın ne beklediğine bağlı olarak bunu yalnızca siz gerçekten anlatabilirsiniz.

Yazılımınızın biraz optimizasyona ihtiyacı olduğunu varsayarsak, yapılacak bir sonraki şey ölçmeye başlamaktır. Profilciler, kodunuzun zamanını nerede geçirdiğini size söyleyecektir. Parçanız bir darboğaz olarak görünmüyorsa, en iyisi tek başına bırakılmasıdır. Profil oluşturucular ve diğer ölçüm araçları, değişikliklerinizin bir fark yaratıp yaratmadığını size söyleyecektir. Kodu optimize etmeye çalışmak için saatler harcamak mümkündür, ancak fark edilir bir fark yaratmadığınızı görmek mümkündür.

Yine de "optimize etmek" ile neyi kastediyorsunuz?

'Optimize edilmiş' kod yazmıyorsanız, kodunuz elinizden geldiğince açık, temiz ve öz olmalıdır. "Erken optimizasyon kötüdür" argümanı, özensiz veya verimsiz kod için bir bahane değildir.

Optimize edilmiş kod, normalde performans için yukarıdaki özelliklerden bazılarını feda eder. Ek yerel değişkenler getirmeyi, beklenenden daha geniş kapsamlı nesnelere sahip olmayı ve hatta normal döngü sırasını tersine çevirmeyi içerebilir. Bunların tümü daha az açık veya öz olabilir, bu nedenle bunu neden yaptığınıza ilişkin kodu (kısaca!) Belgeleyin.

Ancak çoğu zaman, 'yavaş' kodla, bu mikro optimizasyonlar son çaredir. İlk bakılması gereken yer algoritmalar ve veri yapılarıdır. İşi yapmaktan kaçınmanın bir yolu var mı? Doğrusal aramalar ikili aramalarla değiştirilebilir mi? Bağlantılı bir liste burada bir vektörden daha mı hızlı olur? Veya bir karma tablo? Sonuçları önbelleğe alabilir miyim? Burada iyi 'verimli' kararlar vermek, performansı genellikle bir derece veya daha fazla etkileyebilir!


12
Bir bitmap görüntüsünün genişliği boyunca yineleme yaptığınızda, döngü mantığı döngüde harcanan sürenin önemli bir kısmı olabilir. Erken optimizasyon hakkında endişelenmek yerine, bu durumda, en baştan verimli olan en iyi uygulamaları geliştirmek daha iyidir.
Mark Ransom

4
@MarkRansom kısmen kabul etti: Ancak "en iyi uygulamalar" a: görüntüleri doldurmak için mevcut bir kitaplığı veya API çağrısını kullanın veya b: GPU'nun sizin için yapmasını sağlayın. OP'nin önerdiği ölçülmemiş mikro optimizasyon türü asla olmamalıdır. Ve bu kodun birden fazla kez çalıştırıldığını veya 16 pikselden daha büyük bitmap'lerle genişliğini nasıl bilebilirsiniz ...?
Roddy

@Veedrac. -1'in gerekçesini takdir edin. Soruyu yanıtladığımdan beri, sorunun itici gücü ince ve önemli ölçüde değişti. Eğer (genişletilmiş) cevabın hala yardımcı olmadığını düşünüyorsanız, benim için onu silme zamanı ... "Değer mi ..." zaten her zaman öncelikle fikir temelli.
Roddy

@Roddy Düzenlemeleri takdir ediyorum, yardımcı oluyorlar (ve yorumum muhtemelen çok sert geliyordu). Yine de hala kararsızım, çünkü bu gerçekten Stack Overflow için uygun olmayan bir sorunun cevabı. Buradaki yüksek oylu cevaplar gibi, uygun bir cevap pasaja özel gibi görünüyor.
Veedrac

6

Böyle bir durumda aşağıdaki kalıbı kullanıyorum. Neredeyse ilk durumunuz kadar kısadır ve ikinci durumdan daha iyidir, çünkü geçici değişkeni döngüde yerel tutar.

for (unsigned int x = 0, n = static_cast<unsigned>(bitmap->width); x < n; ++x)
{
  *p++ = 0xAA;
  *p++ = 0xBB;
  *p++ = 0xCC;
}

Bu, akıllı derleyici, hata ayıklama derlemesi veya belirli derleme bayraklarından daha azıyla daha hızlı olacaktır.

Düzenleme1 : Bir döngünün dışına sabit bir işlem yerleştirmek iyi bir programlama modelidir. Özellikle C / C ++ 'da makine operasyonunun temellerinin anlaşıldığını gösterir. Kendilerini kanıtlama çabasının bu uygulamayı takip etmeyen insanlar üzerinde olması gerektiğini savunuyorum. Derleyici iyi bir kalıp için cezalandırırsa, bu derleyicideki bir hatadır.

Edit2: : Ben vs2013 orijinal kod karşı benim önerim, got% 1 gelişme ölçülür ettik. Daha iyisini yapabilir miyiz? Basit bir manuel optimizasyon, egzotik talimatlara başvurmadan x64 makinedeki orijinal döngüye göre 3 kat iyileştirme sağlar. Aşağıdaki kod, küçük endian sistemini ve uygun şekilde hizalanmış bitmap'i varsayar. TEST 0 orijinaldir (9 sn), TEST 1 daha hızlıdır (3 sn). Bahse girerim birileri bunu daha da hızlı yapabilir ve testin sonucu bitmap'in boyutuna bağlıdır. Kesinlikle yakında gelecekte, derleyici tutarlı bir şekilde en hızlı kodu üretebilecek. Korkarım bu, derleyicinin aynı zamanda bir programcı yapay zekası olacağı bir gelecek olacak, yani işsiz kalacağız. Ancak şimdilik, döngüde fazladan işlemin gerekmediğini bildiğinizi gösteren bir kod yazın.

#include <memory>
#include <time.h>

struct Bitmap_line
{
  int blah;
  unsigned int width;
  Bitmap_line(unsigned int w)
  {
    blah = 0;
    width = w;
  }
};

#define TEST 0 //define 1 for faster test

int main(int argc, char* argv[])
{
  unsigned int size = (4 * 1024 * 1024) / 3 * 3; //makes it divisible by 3
  unsigned char* pointer = (unsigned char*)malloc(size);
  memset(pointer, 0, size);
  std::unique_ptr<Bitmap_line> bitmap(new Bitmap_line(size / 3));
  clock_t told = clock();
#if TEST == 0
  for (int iter = 0; iter < 10000; iter++)
  {
    unsigned char* p = pointer;
    for (unsigned x = 0; x < static_cast<unsigned>(bitmap->width); ++x)
    //for (unsigned x = 0, n = static_cast<unsigned>(bitmap->width); x < n; ++x)
    {
      *p++ = 0xAA;
      *p++ = 0xBB;
      *p++ = 0xCC;
    }
  }
#else
  for (int iter = 0; iter < 10000; iter++)
  {
    unsigned char* p = pointer;
    unsigned x = 0;
    for (const unsigned n = static_cast<unsigned>(bitmap->width) - 4; x < n; x += 4)
    {
      *(int64_t*)p = 0xBBAACCBBAACCBBAALL;
      p += 8;
      *(int32_t*)p = 0xCCBBAACC;
      p += 4;
    }

    for (const unsigned n = static_cast<unsigned>(bitmap->width); x < n; ++x)
    {
      *p++ = 0xAA;
      *p++ = 0xBB;
      *p++ = 0xCC;
    }
  }
#endif
  double ms = 1000.0 * double(clock() - told) / CLOCKS_PER_SEC;
  printf("time %0.3f\n", ms);

  {
    //verify
    unsigned char* p = pointer;
    for (unsigned x = 0, n = static_cast<unsigned>(bitmap->width); x < n; ++x)
    {
      if ((*p++ != 0xAA) || (*p++ != 0xBB) || (*p++ != 0xCC))
      {
        printf("EEEEEEEEEEEEERRRRORRRR!!!\n");
        abort();
      }
    }
  }

  return 0;
}

İnt64_t ve int32_t yerine üç int64_t kullanırsanız, 64bit'te% 25 daha tasarruf edebilirsiniz.
Antonín Lejsek

5

Dikkate alınması gereken iki şey var.

A) Optimizasyon ne sıklıkla çalışacak?

Yanıt çok sık değilse, yalnızca bir kullanıcı bir düğmeyi tıkladığında olduğu gibi, kodunuzu okunamaz hale getirirse zahmet etmeyin. Cevap saniyede 1000 kez ise, muhtemelen optimizasyona gitmek isteyeceksiniz. Hatta biraz karmaşıksa, bir sonraki adama yardım etmek için neler olduğunu açıklamak için bir yorum yazdığınızdan emin olun.

B) Bu, kodun bakımını / sorun gidermesini zorlaştıracak mı?

Performansta büyük bir kazanç görmüyorsanız, kodunuzu sadece birkaç saat tıklaması kaydetmek için şifreli yapmak iyi bir fikir değildir. Pek çok insan size iyi bir programcının koda bakıp neler olup bittiğini anlayabilmesi gerektiğini söyleyecektir. Bu doğru. Sorun şu ki, iş dünyasında bunu anlamak için fazladan zaman harcanıyor. Öyleyse, okumayı daha güzel hale getirebiliyorsanız, yapın. Arkadaşlarınız bunun için size teşekkür edecek.

Bu, şahsen B örneğini kullanacağımı söyledi.


4

Derleyici birçok şeyi optimize edebilir. Örneğiniz için, okunabilirlik, sürdürülebilirlik ve kod standardınızı izleyenler için gitmelisiniz. Nelerin optimize edilebileceği (GCC ile) hakkında daha fazla bilgi için bu blog gönderisine bakın .


4

Genel bir kural olarak, siz devralmanız gerektiğine karar verene kadar derleyicinin optimizasyonu sizin için yapmasına izin verin. Bunun mantığının performansla ilgisi yoktur, daha çok insan tarafından okunabilirlikle ilgilidir. In engin Vakaların çoğunda, programın okunabilirliği performansıyla daha önemlidir. Bir insanın okuması daha kolay olan bir kod yazmayı hedeflemelisiniz ve daha sonra optimizasyon konusunda yalnızca performansın kodunuzun sürdürülebilirliğinden daha önemli olduğuna ikna olduğunuzda endişelenmelisiniz.

Performansın önemli olduğunu gördükten sonra, hangi döngülerin verimsiz olduğunu belirlemek ve bunları ayrı ayrı optimize etmek için kodda bir profil oluşturucu çalıştırmalısınız. Gerçekten de bu optimizasyonu yapmak istediğiniz durumlar olabilir (özellikle STL konteynerlerinin dahil olduğu C ++ 'ya geçerseniz), ancak okunabilirlik açısından maliyet harika.

Ek olarak, kodu gerçekten yavaşlatabileceği patolojik durumlar da düşünebilirim. Örneğin, derleyicinin bitmap->widthişlem boyunca sabit olduğunu kanıtlayamadığı durumu düşünün . widthDeğişkeni ekleyerek, derleyiciyi bu kapsamda yerel bir değişkeni tutmaya zorlarsınız. Platforma özgü bir nedenden ötürü, bu ekstra değişken bazı yığın alanı optimizasyonunu engellediyse, bayt kodlarını nasıl yaydığını yeniden düzenlemek ve daha az verimli bir şey üretmek zorunda kalabilir.

Örnek olarak, Windows x64'te, __chkstkişlevin 1 sayfadan fazla yerel değişken kullanması durumunda, işlevin önsözünde özel bir API çağrısı çağırmak zorunludur . Bu işlev, pencerelere gerektiğinde yığını genişletmek için kullandıkları koruma sayfalarını yönetme şansı verir. Ekstra değişkeniniz yığın kullanımını 1 sayfanın altından 1 sayfaya veya üstüne çıkarırsa, işleviniz artık __chkstkher girildiğinde çağırmak zorundadır . Bu döngüyü yavaş bir yolda optimize ederseniz, aslında hızlı yolu yavaş yolda kaydettiğinizden daha fazla yavaşlatabilirsiniz!

Elbette, biraz patolojik, ancak bu örneğin amacı, derleyiciyi gerçekten yavaşlatabilmenizdir. Yalnızca optimizasyonların nereye gittiğini belirlemek için işinizin profilini çıkarmanız gerektiğini gösterir. Bu arada, önemli olabilecek veya olmayabilecek bir optimizasyon için lütfen okunabilirlikten hiçbir şekilde ödün vermeyin.


4
Keşke C ve C ++, programcının umursamadığı şeyleri açıkça tanımlamak için daha fazla yol sunsa. Yalnızca derleyicilere şeyleri optimize etme şansı sağlamakla kalmazlar, aynı zamanda kodu okuyan diğer programcıları, örneğin, her seferinde bitmap-> genişliğini yeniden kontrol edip etmeyeceğini tahmin etme zorunluluğundan kurtarır ve bu değişikliklerin döngüyü etkilediğinden emin olur veya Bitmap-> genişliğini önbelleğe alıp almayacağı, bu değişikliklerin döngüyü etkilememesini sağlamak için olabilir. "Bunu önbelleğe al ya da kullan - umrumda değil" demek, programcının seçiminin nedenini açıklığa kavuşturacaktır.
supercat

@supercat Tüm kalbimle katılıyorum, çünkü biri bunu çözmek için yazmak istediğim tatlandırılmış başarısız dil yığınlarına bakıp bakmadığımı görebilir. Birisinin umursamadığı "neyi", buna değmeyecek kadar fazla sözdizimi olmadan tanımlamanın oldukça zor olduğunu gördüm. Arayışıma boşuna devam ediyorum.
Cort Ammon

Bunu her durumda tanımlamak mümkün değil, ancak tip sisteminin yardımcı olabileceği birçok durum olduğunu düşünüyorum. Herhangi bir türe uygulanabilecek "uçucu" dan biraz daha gevşek olan bir tür niteleyiciye sahip olmak yerine, karakter türlerini "evrensel erişimci" yapmaya karar verdi , bu türden erişimlerin sırayla işleneceği anlambilimiyle Nitelikli olmayan eşdeğer türde erişim ve aynı niteleyiciye sahip tüm değişken türlerine erişim. Bu, karakter türlerini kullanıp kullanmadığınızı netleştirmeye yardımcı olur çünkü birinin ...
supercat

... örtüşme davranışı veya birinin bunları kullanıp kullanmadığı, çünkü bunlar kişinin ihtiyaçlarına uyacak şekilde doğru boyuttadır. Ayrıca, karakter tipi erişimlerle ilişkili örtük engellerin aksine, çoğu durumda döngülerin dışına yerleştirilebilecek açık örtüşme engellerine sahip olmak yararlı olacaktır.
supercat

1
Bu akıllıca bir konuşmadır, ancak genel olarak, göreviniz için zaten C'yi seçerseniz, muhtemelen performans çok önemlidir ve farklı kurallar uygulanmalıdır. Aksi takdirde Ruby, Java, Python veya benzeri bir şey kullanmak daha iyi olabilir.
Audrius Meskauskas

4

Karşılaştırma yanlış iki kod snippet'ine beri

for (unsigned x = 0;  x < static_cast<unsigned>(bitmap->width);  ++x)

ve

unsigned width(static_cast<unsigned>(bitmap->width));
for (unsigned x = 0;  x<width ;  ++x)

eşdeğer değil

İlk durumda widthbağımlıdır ve const değildir ve sonraki yinelemeler arasında değişmeyeceği varsayılamaz. Bu nedenle optimize edilemez, ancak her döngüde kontrol edilmesi gerekir .

Optimize edilmiş durumunuzda , programın yürütülmesi sırasında bir noktada yerel bir değişkene değeri atanır bitmap->width. Derleyici, bunun gerçekten değişmediğini doğrulayabilir.

Çoklu iş parçacığı kullanmayı düşündünüz mü, yoksa değer, değeri uçucu olacak şekilde dışarıdan bağımlı olabilir. Söylemezseniz, derleyicinin tüm bunları çözmesi nasıl beklenir?

Derleyici, yalnızca kodunuzun izin verdiği kadar iyi yapabilir.


2

Derleyicinin kodu tam olarak nasıl optimize ettiğini bilmiyorsanız, kod okunabilirliğini ve tasarımını koruyarak kendi optimizasyonlarınızı yapmanız daha iyidir. Yeni derleyici sürümleri için yazdığımız her işlev için derleme kodunu kontrol etmek pratik olarak zordur.


1

Derleyici, bitmap->widthdeğeri widthyinelemeler arasında değiştirilebildiğinden optimize edemez. En yaygın birkaç neden vardır:

  1. Çoklu iş parçacığı. Derleyici, diğer iş parçacığının değeri değiştirmek üzere olup olmadığını tahmin edemez.
  2. Döngü içinde değişiklik, bazen değişkenin döngü içinde değiştirilip değiştirilmeyeceğini söylemek kolay değildir.
  3. Bu işlev çağrısı, örneğin bir iterator::end()ya da container::size()nedenle her zaman aynı sonucu verir eğer tahmin etmek zordur.

Özetlemek gerekirse (benim kişisel fikrim) yüksek düzeyde optimizasyon gerektiren yerler için bunu kendiniz yapmanız gerekir, diğer yerlerde bırakın, derleyici onu optimize edebilir veya etmeyebilir, kod okunabilirliği büyük bir fark yoksa ana hedeftir.

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.