Saya berdebat dengan teman tentang dua cuplikan. Yang lebih cepat dan mengapa ?
value = 5;
if (condition) {
value = 6;
}
dan:
if (condition) {
value = 6;
} else {
value = 5;
}
Bagaimana jika nilai
adalah matriks ?
Catatan: saya tahu bahwa nilai = kondisi ? 6 : 5;
ada dan saya berharap untuk menjadi lebih cepat, tapi itu bukan't option.
Edit (diminta oleh staf karena pertanyaan ditahan pada saat ini):
TL;DR: Di unoptimized kode, jika
tanpa yang lain
tampaknya irrelevantly lebih efisien tetapi bahkan dengan tingkat yang paling dasar dari optimasi diaktifkan kode ini pada dasarnya ditulis ulang untuk value = kondisi + 5
.
Saya mencoba dan dihasilkan perakitan untuk kode berikut:
int ifonly(bool condition, int value)
{
value = 5;
if (condition) {
value = 6;
}
return value;
}
int ifelse(bool condition, int value)
{
if (condition) {
value = 6;
} else {
value = 5;
}
return value;
}
Di gcc 6.3 dengan optimasi cacat (-O0
), yang relevan perbedaan adalah:
mov DWORD PTR [rbp-8], 5
cmp BYTE PTR [rbp-4], 0
je .L2
mov DWORD PTR [rbp-8], 6
.L2:
mov eax, DWORD PTR [rbp-8]
untuk ifonly
, sementara ifelse
telah
cmp BYTE PTR [rbp-4], 0
je .L5
mov DWORD PTR [rbp-8], 6
jmp .L6
.L5:
mov DWORD PTR [rbp-8], 5
.L6:
mov eax, DWORD PTR [rbp-8]
Yang terakhir terlihat sedikit kurang efisien karena memiliki ekstra melompat tapi keduanya memiliki setidaknya dua tahun dan paling banyak tiga tugas jadi kecuali jika anda benar-benar perlu untuk memeras setiap tetes terakhir dari kinerja (petunjuk: kecuali jika anda bekerja pada sebuah pesawat ruang angkasa anda don't, dan bahkan kemudian anda probably don't) perbedaan won't akan terlihat.
Namun, bahkan dengan terendah optimasi tingkat (-O1
) kedua fungsi untuk mengurangi sama:
test dil, dil
setne al
movzx eax, al
add eax, 5
yang pada dasarnya adalah setara dengan
return 5 + condition;
dengan asumsi kondisi
adalah nol atau satu.
Lebih tinggi optimasi tingkat don't benar-benar mengubah output, kecuali mereka mengelola untuk menghindari movzx
dengan efisien penekanan keluar EAX
yang mendaftar di awal.
Disclaimer: Anda mungkin tidak't menulis 5 + kondisi
diri sendiri (meskipun standar jaminan bahwa konversi benar
untuk integer tipe memberi 1
) karena niat anda mungkin tidak segera jelas bagi orang yang membaca kode anda (yang mungkin termasuk diri anda di masa depan). Titik dari kode ini adalah untuk menunjukkan bahwa apa compiler menghasilkan dalam kedua kasus adalah (hampir) identik. Ciprian Tomoiaga negara-negara itu cukup baik di komentar:
a manusia's tugasnya adalah untuk menulis kode bagi manusia dan biarkan compiler menulis kode untuk mesin.
Jawaban dari CompuChip menunjukkan bahwa untuk int
mereka berdua yang dioptimalkan untuk perakitan yang sama, jadi itu doesn't peduli.
Bagaimana jika nilai matriks ?
Aku akan menafsirkan ini dalam cara yang lebih umum, yaitu bagaimana jika nilai
adalah dari jenis dan konstruksi dan tugas-tugas yang mahal (dan gerakan yang murah).
kemudian
T value = init1;
if (condition)
value = init2;
adalah sub-optimal karena dalam kasus kondisi
adalah benar, anda tidak perlu inisialisasi untuk init1
dan kemudian anda melakukan copy tugas.
T value;
if (condition)
value = init2;
else
value = init3;
Ini lebih baik. Tapi masih sub-optimal jika default konstruksi lebih mahal dan jika copy konstruksi lebih mahal maka inisialisasi.
Anda memiliki operator kondisional solusi yang baik:
T value = condition ? init1 : init2;
Atau, jika anda don't seperti operator kondisional, anda dapat membuat fungsi pembantu seperti ini:
T create(bool condition)
{
if (condition)
return {init1};
else
return {init2};
}
T value = create(condition);
Tergantung pada apa yang init1
dan init2
anda juga dapat mempertimbangkan ini:
auto final_init = condition ? init1 : init2;
T value = final_init;
Tapi sekali lagi saya harus menekankan bahwa ini hanya relevan ketika pembangunan dan tugas-tugas yang benar-benar mahal untuk jenis tertentu. Dan bahkan kemudian, hanya dengan profil anda tahu pasti.
Dalam pseudo-bahasa assembly,
li #0, r0
test r1
beq L1
li #1, r0
L1:
may atau mungkin not menjadi lebih cepat dari
test r1
beq L1
li #1, r0
bra L2
L1:
li #0, r0
L2:
tergantung pada seberapa canggih CPU yang sebenarnya adalah. Pergi dari yang paling sederhana hingga paling mewah:
Dengan any CPU yang diproduksi setelah kira-kira tahun 1990, kinerja yang baik tergantung pada kode pas dalam instruksi cache. Bila ragu-ragu, karena itu, meminimalkan ukuran kode. Ini beratnya dalam mendukung contoh pertama.
Dengan dasar "di-order, lima-tahap pipeline" CPU, yang masih kira-kira apa yang anda dapatkan di banyak mikrokontroler, ada pipa gelembung setiap kali cabang—cabang bersyarat atau tanpa syarat—diambil, sehingga hal ini juga penting untuk meminimalkan jumlah cabang petunjuk. Ini juga berat dalam mendukung contoh pertama.
Agak lebih canggih Cpu—fancy cukup untuk melakukan "out-of-order eksekusi", tetapi tidak cukup untuk menggunakan yang terbaik dikenal implementasi dari konsep itu—mungkin dikenakan pipa gelembung setiap kali mereka menghadapi write-setelah-menulis hazards. Ini beratnya dalam mendukung second contoh, di mana r0
ditulis hanya sekali tidak peduli apa. Cpu ini biasanya cukup mewah untuk proses tanpa syarat cabang dalam instruksi fetcher, sehingga anda aren't hanya trading menulis-setelah-menulis penalti untuk cabang penalti.
Saya don't tahu jika ada yang masih membuat jenis CPU lagi. Namun, Cpu yang do gunakan "paling dikenal implementasi" out-of-order eksekusi adalah mungkin untuk memotong sudut pada kurang sering digunakan petunjuk, sehingga anda perlu untuk diketahui bahwa hal semacam ini bisa terjadi. Contoh nyata adalah data palsu ketergantungan pada tujuan register di popcnt
dan lzcnt
pada Sandy Bridge Cpu.
Jika cabang is sangat tak terduga, dan CPU anda memiliki bersyarat-set atau bersyarat-langkah petunjuk, ini adalah waktu untuk menggunakannya:
li #0, r0 tes r1 setne r0
atau
li #0, r0 li #1, r2 tes r1 movne r2, r0
Bersyarat-set versi ini juga lebih kompak dari yang lain alternatif; jika instruksi yang tersedia itu hampir dijamin untuk menjadi Hal yang Benar untuk skenario ini, bahkan jika cabang sudah bisa ditebak. Bersyarat-bergerak versi memerlukan tambahan awal mendaftar, dan selalu limbah salah satu li
instruksi's senilai pengiriman dan mengeksekusi sumber daya; jika cabang itu bahkan diprediksi, bercabang versi mungkin akan lebih cepat.
Di unoptimised kode, contoh pertama menetapkan variabel yang selalu sekali dan kadang-kadang dua kali. Contoh kedua hanya pernah menetapkan variabel sekali. Bersyarat adalah sama pada kedua kode jalan, sehingga seharusnya't peduli. Dalam kode dioptimalkan, hal ini tergantung pada compiler.
Seperti biasa, jika anda yang bersangkutan, menghasilkan perakitan dan melihat apa compiler adalah benar-benar lakukan.
Apa yang akan membuat anda berpikir salah satu dari mereka bahkan salah satu kapal yang lebih cepat atau lebih lambat?
unsigned int fun0 ( unsigned int condition, unsigned int value )
{
value = 5;
if (condition) {
value = 6;
}
return(value);
}
unsigned int fun1 ( unsigned int condition, unsigned int value )
{
if (condition) {
value = 6;
} else {
value = 5;
}
return(value);
}
unsigned int fun2 ( unsigned int condition, unsigned int value )
{
value = condition ? 6 : 5;
return(value);
}
Lebih baris kode dari bahasa tingkat tinggi memberikan compiler yang lebih untuk bekerja dengan begitu jika anda ingin membuat aturan umum tentang hal itu memberikan compiler kode yang lebih untuk bekerja dengan. Jika algoritma ini sama seperti kasus di atas maka salah satu akan mengharapkan compiler dengan sedikit optimasi untuk mencari tahu.
00000000 <fun0>:
0: e3500000 cmp r0, #0
4: 03a00005 moveq r0, #5
8: 13a00006 movne r0, #6
c: e12fff1e bx lr
00000010 <fun1>:
10: e3500000 cmp r0, #0
14: 13a00006 movne r0, #6
18: 03a00005 moveq r0, #5
1c: e12fff1e bx lr
00000020 <fun2>:
20: e3500000 cmp r0, #0
24: 13a00006 movne r0, #6
28: 03a00005 moveq r0, #5
2c: e12fff1e bx lr
bukan kejutan besar itu fungsi pertama dalam urutan yang berbeda, yang sama waktu eksekusi sekalipun.
0000000000000000 <fun0>:
0: 7100001f cmp w0, #0x0
4: 1a9f07e0 cset w0, ne
8: 11001400 add w0, w0, #0x5
c: d65f03c0 ret
0000000000000010 <fun1>:
10: 7100001f cmp w0, #0x0
14: 1a9f07e0 cset w0, ne
18: 11001400 add w0, w0, #0x5
1c: d65f03c0 ret
0000000000000020 <fun2>:
20: 7100001f cmp w0, #0x0
24: 1a9f07e0 cset w0, ne
28: 11001400 add w0, w0, #0x5
2c: d65f03c0 ret
Mudah-mudahan anda mendapatkan ide, anda bisa saja mencoba ini jika itu bukankah jelas bahwa implementasi yang berbeda itu tidak benar-benar berbeda.
Sejauh matriks pergi, tidak yakin bagaimana yang penting,
if(condition)
{
big blob of code a
}
else
{
big blob of code b
}
hanya akan menempatkan yang sama if-then-else pembungkus seluruh gumpalan besar dari kode yang akan mereka nilai=5 atau sesuatu yang lebih rumit. Demikian juga perbandingan bahkan jika itu adalah gumpalan besar dari kode itu masih harus dihitung, dan sama dengan atau tidak sama dengan sesuatu yang sering disusun dengan negatif, jika (kondisi) melakukan sesuatu yang sering disusun seolah-olah tidak kondisi goto.
00000000 <fun0>:
0: 0f 93 tst r15
2: 03 24 jz $+8 ;abs 0xa
4: 3f 40 06 00 mov #6, r15 ;#0x0006
8: 30 41 ret
a: 3f 40 05 00 mov #5, r15 ;#0x0005
e: 30 41 ret
00000010 <fun1>:
10: 0f 93 tst r15
12: 03 20 jnz $+8 ;abs 0x1a
14: 3f 40 05 00 mov #5, r15 ;#0x0005
18: 30 41 ret
1a: 3f 40 06 00 mov #6, r15 ;#0x0006
1e: 30 41 ret
00000020 <fun2>:
20: 0f 93 tst r15
22: 03 20 jnz $+8 ;abs 0x2a
24: 3f 40 05 00 mov #5, r15 ;#0x0005
28: 30 41 ret
2a: 3f 40 06 00 mov #6, r15 ;#0x0006
2e: 30 41
kami hanya pergi melalui latihan ini dengan orang lain yang baru-baru ini di stackoverflow. ini mips compiler menariknya dalam kasus itu tidak hanya menyadari fungsi yang sama, tetapi memiliki satu fungsi hanya melompat ke yang lain untuk menyelamatkan pada kode ruang. Tidak melakukan itu di sini meskipun
00000000 <fun0>:
0: 0004102b sltu $2,$0,$4
4: 03e00008 jr $31
8: 24420005 addiu $2,$2,5
0000000c <fun1>:
c: 0004102b sltu $2,$0,$4
10: 03e00008 jr $31
14: 24420005 addiu $2,$2,5
00000018 <fun2>:
18: 0004102b sltu $2,$0,$4
1c: 03e00008 jr $31
20: 24420005 addiu $2,$2,5
beberapa target lebih.
00000000 <_fun0>:
0: 1166 mov r5, -(sp)
2: 1185 mov sp, r5
4: 0bf5 0004 tst 4(r5)
8: 0304 beq 12 <_fun0+0x12>
a: 15c0 0006 mov $6, r0
e: 1585 mov (sp)+, r5
10: 0087 rts pc
12: 15c0 0005 mov $5, r0
16: 1585 mov (sp)+, r5
18: 0087 rts pc
0000001a <_fun1>:
1a: 1166 mov r5, -(sp)
1c: 1185 mov sp, r5
1e: 0bf5 0004 tst 4(r5)
22: 0204 bne 2c <_fun1+0x12>
24: 15c0 0005 mov $5, r0
28: 1585 mov (sp)+, r5
2a: 0087 rts pc
2c: 15c0 0006 mov $6, r0
30: 1585 mov (sp)+, r5
32: 0087 rts pc
00000034 <_fun2>:
34: 1166 mov r5, -(sp)
36: 1185 mov sp, r5
38: 0bf5 0004 tst 4(r5)
3c: 0204 bne 46 <_fun2+0x12>
3e: 15c0 0005 mov $5, r0
42: 1585 mov (sp)+, r5
44: 0087 rts pc
46: 15c0 0006 mov $6, r0
4a: 1585 mov (sp)+, r5
4c: 0087 rts pc
00000000 <fun0>:
0: 00a03533 snez x10,x10
4: 0515 addi x10,x10,5
6: 8082 ret
00000008 <fun1>:
8: 00a03533 snez x10,x10
c: 0515 addi x10,x10,5
e: 8082 ret
00000010 <fun2>:
10: 00a03533 snez x10,x10
14: 0515 addi x10,x10,5
16: 8082 ret
dan compiler
dengan ini saya kode satu akan mengharapkan target yang berbeda untuk mencocokkan serta
define i32 @fun0(i32 %condition, i32 %value) #0 {
%1 = icmp ne i32 %condition, 0
%. = select i1 %1, i32 6, i32 5
ret i32 %.
}
; Function Attrs: norecurse nounwind readnone
define i32 @fun1(i32 %condition, i32 %value) #0 {
%1 = icmp eq i32 %condition, 0
%. = select i1 %1, i32 5, i32 6
ret i32 %.
}
; Function Attrs: norecurse nounwind readnone
define i32 @fun2(i32 %condition, i32 %value) #0 {
%1 = icmp ne i32 %condition, 0
%2 = select i1 %1, i32 6, i32 5
ret i32 %2
}
00000000 <fun0>:
0: e3a01005 mov r1, #5
4: e3500000 cmp r0, #0
8: 13a01006 movne r1, #6
c: e1a00001 mov r0, r1
10: e12fff1e bx lr
00000014 <fun1>:
14: e3a01006 mov r1, #6
18: e3500000 cmp r0, #0
1c: 03a01005 moveq r1, #5
20: e1a00001 mov r0, r1
24: e12fff1e bx lr
00000028 <fun2>:
28: e3a01005 mov r1, #5
2c: e3500000 cmp r0, #0
30: 13a01006 movne r1, #6
34: e1a00001 mov r0, r1
38: e12fff1e bx lr
fun0:
push.w r4
mov.w r1, r4
mov.w r15, r12
mov.w #6, r15
cmp.w #0, r12
jne .LBB0_2
mov.w #5, r15
.LBB0_2:
pop.w r4
ret
fun1:
push.w r4
mov.w r1, r4
mov.w r15, r12
mov.w #5, r15
cmp.w #0, r12
jeq .LBB1_2
mov.w #6, r15
.LBB1_2:
pop.w r4
ret
fun2:
push.w r4
mov.w r1, r4
mov.w r15, r12
mov.w #6, r15
cmp.w #0, r12
jne .LBB2_2
mov.w #5, r15
.LBB2_2:
pop.w r4
ret
Sekarang secara teknis ada perbedaan kinerja dalam beberapa solusi ini, kadang-kadang hasilnya adalah 5 kasus telah melompat di atas hasilnya adalah 6 kode, dan sebaliknya, adalah cabang lebih cepat dari pelaksana melalui? orang bisa berdebat tapi eksekusi harus bervariasi. Tapi yang lebih dari kondisi jika dibandingkan jika tidak kondisi di kode yang dihasilkan dalam compiler melakukan jika ini melompat di atas yang lain menjalankan melalui. tapi ini tidak selalu karena coding style tapi perbandingan dan jika dan lain kasus apapun sintaks.
Ok, karena perakitan adalah salah satu kategori, saya hanya akan menganggap anda kode pseudo code (dan belum tentu c) dan menerjemahkannya oleh manusia ke 6502 perakitan.
Pilihan 1 (tanpa else)
ldy #$00
lda #$05
dey
bmi false
lda #$06
false brk
Opsi 2 (dengan yang lain)
ldy #$00
dey
bmi else
lda #$06
sec
bcs end
else lda #$05
end brk
Asumsi: Kondisi di Y daftarkan set ke 0 atau 1 pada baris pertama dari pilihan baik, hasilnya akan di akumulator.
Jadi, setelah menghitung siklus untuk kedua kemungkinan dari masing-masing kasus, kita melihat bahwa 1st membangun umumnya lebih cepat; 9 siklus ketika kondisi adalah 0 dan 10 siklus ketika kondisi adalah 1, sedangkan pilihan kedua adalah juga 9 siklus ketika kondisi adalah 0, tapi 13 siklus ketika kondisi 1. (jumlah siklus tidak termasuk BRK
pada akhir).
Kesimpulan: Jika hanya
lebih cepat dari If-Else
membangun.
Dan untuk kelengkapan, di sini adalah aplikasi yang dioptimalkan value = kondisi + 5
solusi:
ldy #$00
lda #$00
tya
adc #$05
brk
Ini memotong waktu kita turun ke 8 siklus (lagi-lagi tidak termasuk BRK
pada akhir).