<= 'Den daha hızlı mı?


1574

if( a < 901 )daha hızlı if( a <= 900 ).

Bu basit örnekte olduğu gibi değil, ancak döngü karmaşık kodunda küçük performans değişiklikleri var. Bu bile doğru olması durumunda üretilen makine kodu ile bir şeyler yapmak zorunda olduğunu düşünüyorum.


153
Tarihsel önemi, cevabın kalitesi ve performanstaki diğer önemli soruların açık kalması göz önüne alındığında, bu sorunun neden kapatılması (ve oyların şu anda gösterildiği gibi silinmemesi) için hiçbir neden göremiyorum . En fazla kilitlenmelidir. Ayrıca, sorunun kendisi yanlış bilgilendirilmiş / naif olsa bile, bir kitapta ortaya çıkması, orijinal yanlış bilginin orada bir yerde "güvenilir" kaynaklarda var olduğu anlamına gelir ve bu nedenle bu soru, bunu temizlemeye yardımcı olduğu için yapıcıdır.
Jason C

32
Bize hangi kitaptan bahsettiğinizi hiç söylemediniz .
Jonathon Reinhart

160
Yazmak <, yazmaktan iki kat daha hızlıdır <=.
Deqing

6
8086'da doğruydu.
Joshua

7
Upvotes sayısı açıkça aşırı derecede aşırı aktif hale getiren yüzlerce insan olduğunu göstermektedir.
m93a

Yanıtlar:


1704

Hayır, çoğu mimaride daha hızlı olmayacak. Siz belirtmediniz, ancak x86'da tüm integral karşılaştırmaları genellikle iki makine talimatında uygulanacaktır:

  • A testveya cmptalimatEFLAGS
  • Ve karşılaştırma türüne (ve kod düzenine) bağlı olarak bir Jcc(atlama) talimatı :
    • jne - Eşit değilse atlama -> ZF = 0
    • jz - Sıfır ise atlama (eşit) -> ZF = 1
    • jg - Daha büyükse zıpla -> ZF = 0 and SF = OF
    • (vb...)

Örnek (kısalık için düzenlendi)$ gcc -m32 -S -masm=intel test.c

    if (a < b) {
        // Do something 1
    }

Derleme:

    mov     eax, DWORD PTR [esp+24]      ; a
    cmp     eax, DWORD PTR [esp+28]      ; b
    jge     .L2                          ; jump if a is >= b
    ; Do something 1
.L2:

Ve

    if (a <= b) {
        // Do something 2
    }

Derleme:

    mov     eax, DWORD PTR [esp+24]      ; a
    cmp     eax, DWORD PTR [esp+28]      ; b
    jg      .L5                          ; jump if a is > b
    ; Do something 2
.L5:

Bu yüzden ikisi arasındaki tek fark jgbir jgetalimattır. İkisi de aynı süreyi alacak.


Hiçbir şeyin farklı atlama talimatlarının aynı süreyi aldığını göstermediğine dair yorumu ele almak istiyorum. Bu cevaplamak biraz zor, ama işte verebilirim: Intel Komut Seti Referansında , hepsi tek bir ortak talimat altında gruplandırılmıştır Jcc(Koşul karşılandığında atlama). Aynı gruplandırma Ek C'de bulunan Optimizasyon Referans Kılavuzu altında birlikte yapılır . Gecikme ve Verim.

Gecikme - Yürütme çekirdeğinin, talimat oluşturan tüm μops'ların yürütülmesini tamamlaması için gereken saat döngüsü sayısı.

Verim - Sorunlu bağlantı noktalarının aynı komutu tekrar kabul etmeden önce beklemesi gereken saat döngü sayısı. Birçok talimat için, bir talimatın verimi gecikmesinden önemli ölçüde daha az olabilir

Değerleri Jcc:

      Latency   Throughput
Jcc     N/A        0.5

aşağıdaki dipnotla Jcc:

7) Koşullu atlama talimatlarının seçimi, şubelerin öngörülebilirliğini artırmak için Bölüm 3.4.1, “Şube Tahmin Optimizasyonu” bölümünün tavsiyelerine dayanmalıdır. Dallar başarılı bir şekilde tahmin edildiğinde, gecikmesi jccetkin bir şekilde sıfırdır.

Bu nedenle, Intel belgelerindeki hiçbir şey bir Jcctalimatı diğerlerinden farklı şekilde ele almaz .

Talimatları uygulamak için kullanılan gerçek devre hakkında düşünürse EFLAGS, koşulların karşılanıp karşılanmadığını belirlemek için farklı bitlerde basit VE / VEYA kapıları olacağını varsayabiliriz . Bu durumda, iki biti test eden bir komutun yalnızca bir testten daha fazla veya daha az zaman alması için hiçbir neden yoktur (Kapı yayılma gecikmesini yok saymak, saat süresinden çok daha azdır).


Düzenle: Kayan Nokta

Bu, x87 kayan nokta için de geçerlidir: (Yukarıdaki kodla hemen hemen aynı kod, ancak doubleyerine int.)

        fld     QWORD PTR [esp+32]
        fld     QWORD PTR [esp+40]
        fucomip st, st(1)              ; Compare ST(0) and ST(1), and set CF, PF, ZF in EFLAGS
        fstp    st(0)
        seta    al                     ; Set al if above (CF=0 and ZF=0).
        test    al, al
        je      .L2
        ; Do something 1
.L2:

        fld     QWORD PTR [esp+32]
        fld     QWORD PTR [esp+40]
        fucomip st, st(1)              ; (same thing as above)
        fstp    st(0)
        setae   al                     ; Set al if above or equal (CF=0).
        test    al, al
        je      .L5
        ; Do something 2
.L5:
        leave
        ret

239
@Dyppl aslında jgve jnleaynı talimatlar, 7F:-)
Jonathon Reinhart

17
Gerçekten bir seçenek diğerinden daha hızlıysa, optimize edicinin kodu değiştirebileceğinden bahsetmiyoruz.
Elazar Leibovich

3
bir şeyin aynı miktarda talimatla sonuçlanması, tüm bu talimatların toplam toplam süresinin aynı olacağı anlamına gelmez. Aslında daha fazla talimat daha hızlı yürütülebilir. Çevrim başına talimatlar sabit bir sayı değildir, talimatlara bağlı olarak değişir.
jontejj

22
@jontejj Bunun çok farkındayım. Cevabımı bile okudun mu? Aynı sayıda talimat hakkında hiçbir şey belirtmedim , bir atlama talimatının bir bayrağa bakması ve diğer atlama talimatının iki bayrağa bakması dışında, esasen aynı teftişlere derlendiğini söyledim . Anlamsal olarak aynı olduklarını göstermek için yeterli delilden daha fazlasını verdiğime inanıyorum.
Jonathon Reinhart

2
@jontejj Çok iyi bir noktaya değindin. Bu cevabın alabildiği kadar görünürlük için, muhtemelen biraz temizlik yapmalıyım. Geri dönüşünüz için teşekkür ederiz.
Jonathon Reinhart

593

Tarihsel olarak (1980'lerden ve 1990'ların başlarından bahsediyoruz), bunun doğru olduğu bazı mimariler vardı . Temel sorun, tamsayı karşılaştırmasının doğası gereği tamsayı çıkarma yoluyla uygulanmasıdır. Bu, aşağıdaki durumlara yol açar.

Comparison     Subtraction
----------     -----------
A < B      --> A - B < 0
A = B      --> A - B = 0
A > B      --> A - B > 0

Şimdi, A < Bçıkarma, el ile toplama ve çıkarma sırasında taşıdığınız ve ödünç aldığınız gibi, çıkışın doğru olması için yüksek bir bit ödünç alması gerektiğinde. Bu "ödünç alınan" bit genellikle taşıma biti olarak adlandırılır ve bir dal talimatıyla test edilebilir. Çıkarma eşit olarak sıfıra eşit olan sıfır ise , sıfır bit olarak adlandırılan ikinci bir bit ayarlanır.

Genellikle bir tanesi taşıma biti ve biri sıfır biti olmak üzere en az iki koşullu dal talimatı vardır.

Şimdi, konunun özüne ulaşmak için, önceki tabloyu taşıma ve sıfır bit sonuçlarını içerecek şekilde genişletelim.

Comparison     Subtraction  Carry Bit  Zero Bit
----------     -----------  ---------  --------
A < B      --> A - B < 0    0          0
A = B      --> A - B = 0    1          1
A > B      --> A - B > 0    1          0

Bu nedenle, bir dalın uygulanması bir A < Btalimatta yapılabilir, çünkü taşıma biti sadece bu durumda açıktır , yani,

;; Implementation of "if (A < B) goto address;"
cmp  A, B          ;; compare A to B
bcz  address       ;; Branch if Carry is Zero to the new address

Ancak, eşit olmayan veya eşit olmayan bir karşılaştırma yapmak istiyorsak, eşitlik durumunu yakalamak için sıfır bayrağını ek olarak kontrol etmemiz gerekir.

;; Implementation of "if (A <= B) goto address;"
cmp A, B           ;; compare A to B
bcz address        ;; branch if A < B
bzs address        ;; also, Branch if the Zero bit is Set

Yani, bazı makinelerde, bir "daha az" karşılaştırma kullanılarak kudretini tasarrufu bir makine talimat . Bu, alt megahertz işlemci hızı ve 1: 1 CPU - bellek hızı oranları çağında geçerliydi, ancak bugün neredeyse tamamen alakasız.


10
Ayrıca, x86 gibi mimariler jgehem sıfır hem de işaret / taşıma bayraklarını test eden komutlar uygular .
greyfade

3
Belirli bir mimari için doğru olsa bile. Derleyici yazarlarının hiçbirinin fark etmediği ve yavaş olanı daha hızlı olanla değiştirmek için bir optimizasyon eklemediği ihtimali nedir?
Jon Hanna

8
Bu 8080'de geçerlidir. Sıfır atlamak ve eksi atlamak için talimatlar vardır, ancak her ikisini aynı anda test edemeyen hiçbiri yoktur.

4
Bu, Motorola 68HC11 / 12'ye de uzanan 6502 ve 65816 işlemci ailesinde de geçerlidir.
Lucas

31
Hatta 8080 tarihinde <=testi uygulanabilir bir işlenen takas ve için test ile öğretim not <(eşdeğeri >=Bu arzu olduğunu) <=takas işlenenler ile: cmp B,A; bcs addr. Bu testin Intel tarafından atlanmasının nedeni budur, gereksiz olduğunu düşündüler ve o zamanlar gereksiz talimatlar
alamazsınız

92

Dahili tamsayı türlerinden bahsettiğimizi varsayarsak, birinin diğerinden daha hızlı olmasının olası bir yolu yoktur. Açıkçası anlamsal olarak aynılar. Her ikisi de derleyiciden tam olarak aynı şeyi yapmasını ister. Sadece korkunç bir şekilde derlenmiş bir derleyici bunlardan biri için aşağı kod üretebilir.

Bazı platformu olsaydı <daha hızlı oldu <=basit tamsayı türleri için, derleyici gerekir her zaman dönüştürmek <=için <sabitler için. Olmayan herhangi bir derleyici sadece kötü bir derleyici olurdu (o platform için).


6
+1 katılıyorum. Derleyici hangi hıza sahip olacağına karar verene kadar <ne <=hızı ne de vardır. Eğer ... genellikle zaten ölmüş kod optimizasyonu, kuyruk çağrı optimizasyonu, (vesilelerle ve unrolling,) döngü kaldırma, çeşitli döngü otomatik paralelleştirilmesi vb gerçekleştirmek düşünün, bu derleyiciler için çok basit bir optimizasyon Neden atık zaman prematüre optimizasyon durulması ? Çalışan bir prototip alın, en önemli optimizasyonların nerede olduğunu belirlemek için profil oluşturun, ilerlemeyi ölçmek için bu optimizasyonları önem sırasına ve profile göre tekrarlayın ...
Otistik

Bir sabit bir değere sahip olan bir karşılaştırma daha yavaş olabilir bazı kenar durumlar olur <, örneğin, ne zaman bu dönüşümün (a < C)için (a <= C-1)(bazı sabiti C) neden olan Ctalimat setinde kodlamak için daha zor hale gelebilir. Örneğin, bir komut kümesi, karşılaştırmalarda -127'den 128'e kadar işaretli sabitleri kompakt bir biçimde gösterebilir, ancak bu aralığın dışındaki sabitlerin daha uzun, daha yavaş bir kodlama veya tamamen başka bir komut kullanılarak yüklenmesi gerekir. Dolayısıyla, böyle bir karşılaştırmanın (a < -127)açık bir dönüşümü olmayabilir.
BeeOnRope

@BeeOnRope Sorun, içinde farklı sabitler olması nedeniyle farklılık gösteren işlemlerin gerçekleştirilmesinin performansı etkileyip etkilemeyeceği değil , aynı işlemi farklı sabitler kullanarak ifade etmenin performansı etkileyip etkileyemeyeceği idi. Karşılaştırdığımız değiliz Yani için orada hiçbir seçim var çünkü, ihtiyacınız birini kullanın. Karşılaştırdığımız için aynı doğruluk tablosunu çünkü farklı kodlama veya farklı talimatlar gerektirmeyen hangi. Birinin herhangi bir kodlaması aynı şekilde diğerinin kodlamasıdır. a > 127a > 128a > 127a >= 128
David Schwartz

"[<= Daha yavaş olan] bir platform olsaydı derleyicinin her zaman sabitler için dönüşüm <=yapması gerektiği" ifadesine genel olarak yanıt veriyordum <. Bildiğim kadarıyla bu dönüşüm sabiti değiştirmeyi içeriyor. Örneğin, a <= 42olarak derlenmektedir a < 43çünkü <hızlıdır. Bazı uç durumlarda, böyle bir dönüşüm verimli olmaz çünkü yeni sabit daha fazla veya daha yavaş talimatlar gerektirebilir. Tabii ki a > 127ve a >= 128eşdeğerdir ve bir derleyici her iki formu da (aynı) en hızlı şekilde kodlamalıdır, ancak bu söylediğimle tutarsız değildir.
BeeOnRope

67

İkisinin de daha hızlı olmadığını görüyorum. Derleyici, her koşulda farklı bir değere sahip aynı makine kodunu üretir.

if(a < 901)
cmpl  $900, -4(%rbp)
jg .L2

if(a <=901)
cmpl  $901, -4(%rbp)
jg .L3

Benim iförneğim Linux'ta x86_64 platformundaki GCC'den.

Derleyici yazarları oldukça zeki insanlar ve bu şeyleri ve çoğumuzun kabul ettiği birçok şeyi düşünüyorlar.

Sabit değilse, her iki durumda da aynı makine kodunun üretildiğini fark ettim.

int b;
if(a < b)
cmpl  -4(%rbp), %eax
jge   .L2

if(a <=b)
cmpl  -4(%rbp), %eax
jg .L3

9
Bunun x86'ya özgü olduğunu unutmayın.
Michael Petrotta

10
Bence if(a <=900)tam olarak aynı grubu oluşturduğunu göstermek için bunu kullanmalısın :)
Lipis

2
@AdrianCornish Üzgünüm .. Ben bunu düzenledim .. aşağı yukarı aynı .. ama eğer ikincisini <= 900 olarak değiştirirseniz asm kodu tamamen aynı olacaktır :) şimdi hemen hemen aynı. biliyorum .. OKB için :)
Lipis

3
@Boann if (true) değerine indirgenebilir ve tamamen ortadan kaldırılabilir.
Qsario

5
Hiç kimse bu optimizasyonun sadece sabit karşılaştırmalar için geçerli olduğuna dikkat çekmedi . Bunun garantisini değil iki değişken karşılaştırmak için böyle yapılması.
Jonathon Reinhart

51

Kayan nokta kodu için, <= karşılaştırma modern mimarilerde bile daha yavaş olabilir (tek bir komutla). İşte ilk fonksiyon:

int compare_strict(double a, double b) { return a < b; }

PowerPC'de, önce bu bir kayan nokta karşılaştırması gerçekleştirir (güncelleştirme cr, koşul yazmacı), daha sonra koşul kaydını bir GPR'ye taşır, "karşılaştırılandan daha az" bitini yerine kaydırır ve sonra geri döner. Dört talimat alır.

Şimdi bunun yerine bu işlevi göz önünde bulundurun:

int compare_loose(double a, double b) { return a <= b; }

Bu, compare_strictyukarıdakiyle aynı işi gerektirir , ancak şimdi iki ilgi alanı vardır: "küçüktür" ve "eşittir". Bu, crorbu iki biti bir araya getirmek için ekstra bir talimat gerektirir ( - bitsel VEYA koşul kaydı). Bu yüzden compare_loosebeş talimat compare_strictgerektirir, dört talimat gerektirir.

Derleyicinin ikinci işlevi şu şekilde optimize edebileceğini düşünebilirsiniz:

int compare_loose(double a, double b) { return ! (a > b); }

Ancak bu, NaN'leri yanlış ele alacaktır. NaN1 <= NaN2ve NaN1 > NaN2her ikisinin de yanlış olarak değerlendirilmesi gerekir.


Neyse ki x86 (x87) üzerinde böyle çalışmıyor. fucomipZF ve CF'yi ayarlar.
Jonathon Reinhart

3
@JonathonReinhart: Sana PowerPC ne yaptığını ediyoruz yanlış anlama düşünüyorum - koşul kayıt cr olduğu gibi bayrakların eşdeğer ZFve CFx86 üzerinde. (CR daha esnek olmasına rağmen.) Posterin bahsettiği şey, sonucu bir GPR'ye taşımaktır: bu, PowerPC'de iki talimat alır, ancak x86'nın koşullu bir hareket talimatı vardır.
Dietrich Epp

@DietrichEpp İfademden sonra eklemek istediğim şuydu: EFLAGS'ın değerine göre hemen atlayabilirsiniz. Açık olmadığım için üzgünüm.
Jonathon Reinhart

1
@JonathonReinhart: Evet, ayrıca CR'nin değerine göre hemen atlayabilirsiniz. Cevap, ekstra talimatların geldiği atlamadan bahsetmiyor.
Dietrich Epp

34

Belki de bu isimsiz kitabın yazarı, a > 0daha hızlı çalışan a >= 1ve evrensel olarak doğru olduğunu düşünen bir kitap okumuştur .

Ama bunun nedeni 0a'nın dahil olması (çünkü CMPmimariye bağlı olarak, örneğin yerine geçebilir OR) değil <.


1
Tabii ki, bir "hata ayıklama" derlemesinde, ama (a >= 1)daha yavaş çalıştırmak için kötü bir derleyici alacaktı (a > 0), çünkü eski
önemsizce

2
@BeeOnRope Bazen bir optimize edicinin hangi karmaşık şeyleri optimize edebileceği ve bunu yapmanın ne kadar kolay olmadığı konusunda şaşırıyorum.
glglgl

1
Gerçekten de, asm çıkışını her zaman önemli olan çok az fonksiyon için kontrol etmeye değer. Bununla birlikte, yukarıdaki dönüşüm çok basittir ve onlarca yıldır basit derleyicilerde bile gerçekleştirilmiştir.
BeeOnRope

32

En azından, bu doğru olsaydı, bir derleyici önemsiz bir şekilde <= b'yi! .


Neden! (A> b) <= b'nin optimize edilmiş versiyonudur. ! (A> b) birinde 2 işlem değil mi?
Abhishek Singh

6
@AbhishekSingh NOTsadece diğer talimatlarla ( jevs. jne) yapılır
Pavel Gatnar

15

Aynı hıza sahipler. Belki bazı özel mimarilerde söyledikleri doğrudur, ancak x86 ailesinde en azından aynı olduklarını biliyorum. Çünkü bunu yapmak için CPU bir çıkarma (a - b) yapar ve ardından bayrak kaydının bayraklarını kontrol eder. Bu kaydın iki bitine ZF (sıfır Bayrağı) ve SF (işaret bayrağı) denir ve bir döngüde yapılır, çünkü bunu bir maske işlemiyle gerçekleştirir.


14

Bu, C'nin derlendiği temel mimariye büyük ölçüde bağımlı olacaktır. Bazı işlemciler ve mimariler, farklı sayıda döngüde çalışan eşit veya eşit veya daha az açık talimatlara sahip olabilir.

Ancak derleyici etrafında çalışabileceği ve ilgisiz hale getirebileceği için bu oldukça sıra dışı olurdu.


1
EĞER stiller arasında bir fark varsa. 1) tespit edilemez. 2) Tuzuna değecek herhangi bir derleyici, kodun anlamını değiştirmeden yavaş formdan daha hızlı forma dönüşüm yapacaktır. Böylece ekilen sonuç talimatı aynı olacaktır.
Martin York

Tamamen kabul etti, her durumda oldukça önemsiz ve aptalca bir fark olurdu. Kesinlikle platform agnostik olması gereken bir kitapta bahsedecek bir şey yok.
Telgin

@lttlrck: Anladım. Bir süre aldı (saçma). Hayır, algılanamazlar çünkü ölçümlerini imkansız kılan daha birçok şey var. İşlemci duruyor / önbellek kaçıyor / sinyaller / işlem değiştiriyor. Bu nedenle, normal bir işletim sistemi durumunda, tek döngü düzeyindeki şeyler fiziksel olarak ölçülemez. Ölçümlerden gelen tüm bu parazitleri ortadan kaldırabiliyorsanız (yerleşik bellek ile bir çip üzerinde çalıştırın ve işletim sistemi yok), o zaman hala endişelenmek için zamanlayıcılarınızın ayrıntı düzeyine sahipsiniz, ancak teorik olarak yeterince uzun süre çalıştırırsanız bir şey görebilirsiniz.
Martin York

12

TL; DR yanıtı

Çoğu mimari, derleyici ve dil kombinasyonu için daha hızlı olmayacaktır.

Tam cevap

Diğer cevaplar üzerinde yoğunlaşmıştır x86 mimarisi ve bilmiyorum ARM oluşturulan kodun özellikle yorum için yeterince iyi (senin örneğin montajcı olarak görünüyor) mimarisini, ama bu bir örneğidir mikro optimizasyonu ise çok mimari spesifiktir ve bir optimizasyon olması gerektiği gibi bir anti-optimizasyon olması muhtemeldir .

Bu nedenle, bu tür bir mikro optimizasyonun kargo kültünün bir örneği olduğunu öneririm en iyi yazılım mühendisliği uygulamasından ziyade programlamasına .

Muhtemelen bunun bir optimizasyon olduğu bazı mimariler var, ama tam tersinin doğru olabileceği en az bir mimari biliyorum. Saygıdeğer Transputer mimarisi sadece makine kodu talimat almış eşit ve daha büyük ya da eşit bütün karşılaştırmalar bu ilkel inşa edilmesi gerekiyordu, bu yüzden.

O zaman bile, hemen hemen tüm durumlarda, derleyici, değerlendirme talimatlarını pratikte, hiçbir karşılaştırmanın diğerine göre herhangi bir avantajı olmayacak şekilde sipariş edebilir. En kötü durumda, işlenen yığınındaki en üstteki iki öğeyi değiştirmek için bir ters talimat (REV) eklemesi gerekebilir . Bu, çalıştırmak için tek bir döngü alan tek bir bayt talimatıydı, bu yüzden mümkün olan en küçük ek yük vardı.

Bunun gibi bir mikro optimizasyonun bir optimizasyon veya anti-optimizasyon olup olmadığı, kullandığınız belirli mimariye bağlıdır, bu nedenle mimariye özgü mikro optimizasyonları kullanma alışkanlığına alışmak genellikle kötü bir fikirdir, aksi takdirde içgüdüsel olarak uygun olmadığında bir tane kullanın ve okuduğunuz kitabın tam olarak bu olduğu anlaşılıyor.


6

Var olsa bile farkı fark etmemelisiniz. Ayrıca, pratikte, bazı sihirli sabitler kullanmayacaksanız, ek bir şey yapmanız a + 1veya a - 1durumu ayakta tutmanız gerekir, ki bu çok kötü bir uygulamadır.


1
Kötü uygulama nedir? Sayacı artırmak veya azaltmak? O zaman dizin gösterimini nasıl saklarsınız?
jcolebrand

5
2 değişken türünü karşılaştırıyorsanız. Elbette bir döngü veya başka bir şey için değer ayarlamanız önemsizdir. Ancak x <= y değeriniz varsa ve y bilinmiyorsa, bunu x <y + 1
değerine

@ JustinDanielson kabul etti. Çirkin, kafa karıştırıcı vb.
Bahsetmiyorum

4

Ekstra karakter biraz daha yavaş kod işleme ile sonuçlandığından, çoğu komut dosyası dilinde satırın doğru olduğunu söyleyebilirsiniz. Bununla birlikte, en iyi cevabın işaret ettiği gibi, C ++ üzerinde hiçbir etkisi olmamalıdır ve komut dosyası diliyle yapılan herhangi bir şey muhtemelen optimizasyonla ilgili değildir.


Bir şekilde katılmıyorum. Rekabetçi programlamada, komut dosyası dilleri genellikle bir soruna en hızlı çözümü sunar, ancak doğru çözümü elde etmek için doğru tekniklerin (okuma: optimizasyon) uygulanması gerekir.
Tyler Crompton

3

Bu cevap yazdığım zaman, sadece genel olarak <vs <= sabit bir değil belirli bir örneği ilgili başlık soruya bakıyordu a < 901vs. a <= 900. Birçok derleyici her zaman <ve arasında dönüştürme yaparak sabitlerin büyüklüğünü küçültür<= , örneğin x86 anında işlenen -128..127 için daha kısa 1 bayt kodlamaya sahiptir.

ARM ve özellikle AArch64 için, anında kodlama yapabilmek, dar bir alanı bir kelimedeki herhangi bir konuma döndürme yeteneğine bağlıdır. Yani cmp w0, #0x00f000iken, encodeable olurducmp w0, #0x00effff olmayabilir. Bu nedenle, karşılaştırma zamanı sabiti ile karşılaştırma zamanı sabiti karşılaştırması için daha küçük hale getirme kuralı her zaman AArch64 için geçerli değildir.


<vs. <= genel olarak, çalışma zamanı değişkeni koşulları dahil

Çoğu makinede montaj dilinde, karşılaştırmanın <=maliyeti< . Bu, üzerinde dallanıyor, 0/1 tamsayı oluşturmak için booleanize ediyorsanız veya dalsız bir seçim işlemi (x86 CMOV gibi) için bir belirteç olarak kullanıp kullanmadığınız için geçerlidir. Diğer cevaplar sorunun sadece bu kısmını ele aldı.

Ancak bu soru C ++ operatörleri, optimize edicinin girdisi ile ilgilidir. Normalde ikisi de eşit derecede verimlidir; kitaptan gelen tavsiye tamamen sahte görünüyor çünkü derleyiciler her zaman asm'de uyguladıkları karşılaştırmayı dönüştürebilirler. Ancak, kullanımda en az bir istisna vardır.<= , kullanımın derleyicinin optimize edemediği bir şeyi yanlışlıkla oluşturabileceği .

Bir döngü koşulu olarak, derleyicinin bir döngünün sonsuz olmadığını kanıtlamasını durdurduğunda <=, niteliksel olarak farklı olan durumlar vardır <. Bu, otomatik vektörleştirmeyi devre dışı bırakarak büyük bir fark yaratabilir.

İmzasız taşma, işaretli taşmadan (UB) farklı olarak temel-2 sargı olarak iyi tanımlanmıştır. İmzalı döngü sayaçları genellikle imzalı taşma UB'ye dayanarak optimize eden derleyicilerle bundan güvenlidir: ++i <= sizeher zaman sonunda yanlış olur. ( Her C Programcısının Tanımsız Davranış Hakkında Bilmesi Gerekenler )

void foo(unsigned size) {
    unsigned upper_bound = size - 1;  // or any calculation that could produce UINT_MAX
    for(unsigned i=0 ; i <= upper_bound ; i++)
        ...

Derleyiciler, tanımlanmamış davranışa yol açanlar hariç olmak üzere, yalnızca olası tüm giriş değerleri için C ++ kaynağının (tanımlanmış ve yasal olarak gözlemlenebilir) davranışını koruyacak şekilde optimize edebilir .

(Basit i <= size sorun da yaratacaktır, ancak üst sınırın hesaplanmasının, umursadığınız ancak derleyicinin dikkate alması gereken bir girdi için yanlışlıkla sonsuz bir döngü olasılığını tanıtmanın daha gerçekçi bir örneği olduğunu düşündüm.)

Bu durumda, size=0yol açar upper_bound=UINT_MAXve i <= UINT_MAXher zaman doğrudur. Yani bu döngü sonsuzdursize=0 ve programcı olarak büyük olasılıkla size size = 0 iletmek istemese de derleyici buna saygı duymak zorundadır. Derleyici bu işlevi, = 0 değerinin imkansız olduğunu kanıtlayabileceği bir arayanın içine yerleştirebilirse, o zaman harika, olabildiğince optimize edebilir i < size.

Asm , döngü içinde gerçek değere ihtiyaç duyulmuyorsa, if(!size) skip the loop; do{...}while(--size);bir for( i<size )döngüyü optimize etmenin normalde verimli bir yoludur i( Döngüler neden her zaman "do ... while" stilinde (kuyruk atlama) derlenir? ).

Ancak bu {} süre sonsuz olamaz: ile girilirse size==0, 2 ^ n yineleme alırız. ( For döngüsü C'deki tüm işaretsiz tam sayılar üzerinde yineleme , sıfır da dahil olmak üzere tüm işaretsiz tam sayılar üzerinde bir döngü ifade etmeyi mümkün kılar, ancak taşıma bayrağı olmadan asm'de olduğu gibi kolay değildir.)

Döngü sayacının sarmalanması bir olasılıkla, modern derleyiciler genellikle "pes eder" ve neredeyse agresif bir şekilde optimize etmezler.

Örnek: 1 ile n arasındaki tamsayıların toplamı

İmzasız i <= nyenilgileri kullanmaksum(1 .. n) , Gauss'un n * (n+1) / 2formülüne dayanan kapalı bir formla döngüleri optimize eden clang deyim tanımasını yener .

unsigned sum_1_to_n_finite(unsigned n) {
    unsigned total = 0;
    for (unsigned i = 0 ; i < n+1 ; ++i)
        total += i;
    return total;
}

Godbolt derleyici gezgininde clang7.0 ve gcc8.2'den x86-64 asm

 # clang7.0 -O3 closed-form
    cmp     edi, -1       # n passed in EDI: x86-64 System V calling convention
    je      .LBB1_1       # if (n == UINT_MAX) return 0;  // C++ loop runs 0 times
          # else fall through into the closed-form calc
    mov     ecx, edi         # zero-extend n into RCX
    lea     eax, [rdi - 1]   # n-1
    imul    rax, rcx         # n * (n-1)             # 64-bit
    shr     rax              # n * (n-1) / 2
    add     eax, edi         # n + (stuff / 2) = n * (n+1) / 2   # truncated to 32-bit
    ret          # computed without possible overflow of the product before right shifting
.LBB1_1:
    xor     eax, eax
    ret

Ama naif versiyon için, clang'dan aptal bir döngü elde ediyoruz.

unsigned sum_1_to_n_naive(unsigned n) {
    unsigned total = 0;
    for (unsigned i = 0 ; i<=n ; ++i)
        total += i;
    return total;
}
# clang7.0 -O3
sum_1_to_n(unsigned int):
    xor     ecx, ecx           # i = 0
    xor     eax, eax           # retval = 0
.LBB0_1:                       # do {
    add     eax, ecx             # retval += i
    add     ecx, 1               # ++1
    cmp     ecx, edi
    jbe     .LBB0_1            # } while( i<n );
    ret

GCC her iki şekilde de kapalı form kullanmaz, bu nedenle döngü koşulu seçimi gerçekten zarar vermez ; ibir XMM kaydının elemanlarında paralel olarak 4 değer çalıştırarak SIMD tamsayı eklenmesi ile otomatik vektörleşir .

# "naive" inner loop
.L3:
    add     eax, 1       # do {
    paddd   xmm0, xmm1    # vect_total_4.6, vect_vec_iv_.5
    paddd   xmm1, xmm2    # vect_vec_iv_.5, tmp114
    cmp     edx, eax      # bnd.1, ivtmp.14     # bound and induction-variable tmp, I think.
    ja      .L3 #,       # }while( n > i )

 "finite" inner loop
  # before the loop:
  # xmm0 = 0 = totals
  # xmm1 = {0,1,2,3} = i
  # xmm2 = set1_epi32(4)
 .L13:                # do {
    add     eax, 1       # i++
    paddd   xmm0, xmm1    # total[0..3] += i[0..3]
    paddd   xmm1, xmm2    # i[0..3] += 4
    cmp     eax, edx
    jne     .L13      # }while( i != upper_limit );

     then horizontal sum xmm0
     and peeled cleanup for the last n%3 iterations, or something.

Ayrıca çok küçük nve / veya sonsuz döngü durumu için kullandığını düşündüğüm düz bir skaler döngü var .

BTW, bu döngülerin her ikisi de döngü yükü üzerinde bir talimat (ve Sandybridge ailesi CPU'larında bir uop) harcar. sub eax,1/ cmp / jcc jnzyerine add eax,1/ daha verimli olur. 2 yerine 1 uop (sub / jcc veya cmp / jcc'nin makro kaynaşmasından sonra). Her iki döngüden sonraki kod EAX'ı koşulsuz olarak yazar, bu nedenle döngü sayacının son değerini kullanmaz.


Güzel bir örnek. EFLAGS kullanımı nedeniyle sıra dışı yürütme üzerindeki potansiyel etki hakkındaki diğer yorumunuz ne olacak? Tamamen teorik mi yoksa JB'nin JBE'den daha iyi bir boru hattına yol açması gerçekten olabilir mi?
rustyx

@rustyx: Bunu başka bir cevabın altında bir yerde yorumladım mı? Derleyiciler, kısmi bayrak duraklarına neden olan kod yaymayacak ve kesinlikle bir C <veya için değil <=. Ancak, ZF ayarlandığında (ecx == 0) veya CF ayarlanmışsa (EAX == 1'in bit 3'ü) test ecx,ecx/ bt eax, 3/ jbeatlayacak ve çoğu CPU'da kısmi bir bayrak durmasına neden olacağı için okuduğu bayrakların hepsi bayrak yazmak için son talimattan gelir. Sandybridge ailesinde, aslında durmuyor, sadece bir birleşme yeri eklemesi gerekiyor. cmp/ testtüm bayrakları yaz, ancak btZF'yi değiştirmeden bırakır. felixcloutier.com/x86/bt
Peter Cordes

2

Yalnızca bilgisayarları yaratan insanlar boolean mantığı ile kötü durumdaysa. Hangi olmamalılar.

Her karşılaştırma ( >= <= > <) aynı hızda yapılabilir.

Her karşılaştırmanın ne olduğu sadece bir çıkarma (fark) ve pozitif / negatif olup olmadığını görmektir.
(Eğer msbayarlanırsa, sayı negatif olur)

Nasıl kontrol edilir a >= b? Sub Pozitif a-b >= 0olup olmadığını kontrol edin a-b.
Nasıl kontrol edilir a <= b? Sub Pozitif 0 <= b-aolup olmadığını kontrol edin b-a.
Nasıl kontrol edilir a < b? Sub Negatif a-b < 0olup olmadığını kontrol edin a-b.
Nasıl kontrol edilir a > b? Sub Negatif 0 > b-aolup olmadığını kontrol edin b-a.

Basitçe söylemek gerekirse, bilgisayar bunu verilen op için kaputun altında yapabilir:

a >= b== msb(a-b)==0
a <= b== msb(b-a)==0
a > b== msb(b-a)==1
a < b==msb(a-b)==1

ve tabii ki bilgisayarın ya ==0da ==1ikisini de yapması gerekmez .
için ==0sadece ters olabilir msbdevresinden.

Her neyse, kesinlikle lol a >= bolarak hesaplanamazlardıa>b || a==b

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.