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 < 901
vs. 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, #0x00f000
iken, 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 <= size
her 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=0
yol açar upper_bound=UINT_MAX
ve i <= UINT_MAX
her 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 <= n
yenilgileri kullanmaksum(1 .. n)
, Gauss'un n * (n+1) / 2
formü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 ; i
bir 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 n
ve / 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 jnz
yerine 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.