Saya membaca di mana-mana bahwa operator ternary seharusnya lebih cepat daripada, atau setidaknya sama dengan, yang setara if
-else
blok.
Namun, saya melakukan tes berikut dan ternyata bukan itu masalahnya:
Random r = new Random();
int[] array = new int[20000000];
for(int i = 0; i < array.Length; i++)
{
array[i] = r.Next(int.MinValue, int.MaxValue);
}
Array.Sort(array);
long value = 0;
DateTime begin = DateTime.UtcNow;
foreach (int i in array)
{
if (i > 0)
{
value += 2;
}
else
{
value += 3;
}
// if-else block above takes on average 85 ms
// OR I can use a ternary operator:
// value += i > 0 ? 2 : 3; // takes 157 ms
}
DateTime end = DateTime.UtcNow;
MessageBox.Show("Measured time: " + (end-begin).TotalMilliseconds + " ms.\r\nResult = " + value.ToString());
Komputer saya membutuhkan 85 ms untuk menjalankan kode di atas. Tetapi jika saya berkomentar if
-else
chunk, dan batalkan komentar pada garis operator ternary, itu akan memakan waktu sekitar 157 ms.
Mengapa ini terjadi?
c#
performance
conditional-operator
pengguna1032613
sumber
sumber
DateTime
untuk mengukur kinerja. GunakanStopwatch
. Selanjutnya, waktu yang agak lama - itu waktu yang sangat singkat untuk diukur.Random
objek, sehingga selalu memberikan urutan yang sama. Jika Anda menguji kode yang berbeda dengan data yang berbeda, Anda dapat melihat perbedaan dalam kinerja.Jawaban:
Untuk menjawab pertanyaan ini, kami akan memeriksa kode perakitan yang dihasilkan oleh JIT X86 dan X64 untuk masing-masing kasus ini.
X86, jika / maka
X86, ternary
X64, jika / kemudian
X64, ternary
Pertama: mengapa kode X86 jauh lebih lambat daripada X64?
Ini disebabkan oleh karakteristik kode berikut:
i
dari array, sedangkan JIT X86 menempatkan beberapa operasi tumpukan (akses memori) dalam loop.value
adalah integer 64-bit, yang membutuhkan 2 instruksi mesin pada X86 (add
diikuti olehadc
) tetapi hanya 1 pada X64 (add
).Kedua: mengapa operator ternary lebih lambat pada X86 dan X64?
Ini disebabkan oleh perbedaan halus dalam urutan operasi yang berdampak pada pengoptimal JIT. Untuk JIT operator ternary, daripada langsung mengkode
2
dan3
dalamadd
instruksi mesin, JIT membuat variabel perantara (dalam register) untuk menyimpan hasilnya. Register ini kemudian diperpanjang dari 32-bit menjadi 64-bit sebelum ditambahkanvalue
. Karena semua ini dilakukan dalam register untuk X64, meskipun ada peningkatan kompleksitas yang signifikan untuk operator ternary, dampak netto agak diminimalkan.JIT X86 di sisi lain terkena dampak yang lebih besar karena penambahan nilai menengah baru di loop dalam menyebabkannya "menumpahkan" nilai lain, menghasilkan setidaknya 2 akses memori tambahan di loop dalam (lihat akses untuk
[ebp-14h]
dalam kode terner X86).sumber
Sunting: Semua perubahan ... lihat di bawah.
Saya tidak bisa mereproduksi hasil Anda di CLR x64, tetapi saya bisa di x86. Pada x64 saya bisa melihat yang kecil perbedaan (kurang dari 10%) antara operator bersyarat dan if / else, tetapi jauh lebih kecil dari yang Anda lihat.
Saya telah membuat perubahan potensial berikut:
/o+ /debug-
, dan jalankan di luar debuggerStopwatch
Hasil dengan
/platform:x64
(tanpa garis "abaikan"):Hasil dengan
/platform:x86
(tanpa garis "abaikan"):Detail sistem saya:
Jadi tidak seperti sebelumnya, saya pikir Anda sedang melihat perbedaan nyata - dan itu semua harus dilakukan dengan JIT x86. Saya tidak ingin mengatakan apa yang menyebabkan perbedaan - saya dapat memperbarui posting nanti dengan rincian lebih lanjut jika saya bisa repot-repot masuk ke cordbg :)
Menariknya, tanpa mengurutkan array terlebih dahulu, saya berakhir dengan tes yang memakan waktu sekitar 4,5x, setidaknya di x64. Dugaan saya adalah ini berkaitan dengan prediksi cabang.
Kode:
sumber
Perbedaannya benar-benar tidak ada hubungannya dengan if / else vs ternary.
Melihat pembongkaran jitted (saya tidak akan mengecam ulang di sini, tolong lihat jawaban @ 280Z28), ternyata Anda membandingkan apel dan jeruk . Dalam satu kasus, Anda membuat dua
+=
operasi berbeda dengan nilai konstan dan yang Anda pilih tergantung pada suatu kondisi, dan dalam kasus lain, Anda membuat di+=
mana nilai yang ditambahkan tergantung pada suatu kondisi.Jika Anda ingin benar-benar membandingkan jika / selain vs ternary, ini akan menjadi perbandingan yang lebih adil (sekarang keduanya akan sama-sama "lambat", atau kita bahkan bisa mengatakan ternary sedikit lebih cepat):
vs.
Sekarang pembongkaran untuk
if/else
menjadi seperti yang ditunjukkan di bawah ini. Perhatikan bahwa ini sedikit lebih buruk daripada kasus ternary, karena berhenti menggunakan register untuk variabel loop (i
) juga.sumber
diff
, tetapi ternary masih jauh lebih lambat - sama sekali tidak apa yang Anda katakan. Apakah Anda melakukan percobaan sebelum memposting "jawaban" ini?Edit:
Menambahkan contoh yang bisa dilakukan dengan pernyataan if-else tetapi bukan operator kondisional.
Sebelum jawabannya, silakan lihat [ Mana yang lebih cepat? ] di blog Mr. Lippert. Dan saya pikir jawaban Mr. Ersönmez adalah yang paling benar di sini.
Saya mencoba menyebutkan sesuatu yang harus kita ingat dengan bahasa pemrograman tingkat tinggi.
Pertama, saya tidak pernah mendengar bahwa operator kondisional seharusnya lebih cepat atau kinerja yang sama dengan pernyataan if-else di C♯ .
Alasannya sederhana bahwa bagaimana jika tidak ada operasi dengan pernyataan if-else:
Persyaratan operator bersyarat adalah harus ada nilai dengan kedua sisi, dan di C♯ juga mensyaratkan bahwa kedua sisi
:
memiliki tipe yang sama. Ini hanya membuatnya berbeda dari pernyataan if-else. Dengan demikian pertanyaan Anda menjadi pertanyaan yang menanyakan bagaimana instruksi kode mesin dihasilkan sehingga perbedaan kinerja.Dengan operator kondisional, semantiknya adalah:
Apa pun ekspresi yang dievaluasi, ada nilainya.
Tetapi dengan pernyataan if-else:
Jika ekspresi dievaluasi benar, lakukan sesuatu; jika tidak, lakukan hal lain.
Nilai tidak harus terlibat dengan pernyataan if-else. Asumsi Anda hanya mungkin dengan optimasi.
Contoh lain untuk menunjukkan perbedaan di antara mereka adalah seperti berikut:
kode di atas mengkompilasi, namun, ganti pernyataan if-else dengan operator kondisional tidak akan dikompilasi:
Operator bersyarat dan pernyataan if-else secara konseptual sama ketika Anda melakukan hal yang sama, mungkin bahkan lebih cepat dengan operator bersyarat di C , karena C lebih dekat dengan perakitan platform.
Untuk kode asli yang Anda berikan, operator kondisional digunakan dalam foreach-loop, yang akan mengacaukan segalanya untuk melihat perbedaan di antara mereka. Jadi saya mengusulkan kode berikut:
dan berikut ini adalah dua versi IL yang dioptimalkan dan tidak. Karena panjang, saya menggunakan gambar untuk ditampilkan, sisi kanan adalah yang dioptimalkan:
Dalam kedua versi kode, IL dari operator bersyarat terlihat lebih pendek dari pernyataan if-else, dan masih ada keraguan tentang kode mesin yang akhirnya dihasilkan. Berikut ini adalah petunjuk dari kedua metode ini, dan gambar sebelumnya tidak dioptimalkan, yang terakhir adalah yang dioptimalkan:
Instruksi yang tidak dioptimalkan: (Klik untuk melihat gambar ukuran penuh.)
Instruksi yang dioptimalkan: (Klik untuk melihat gambar ukuran penuh.)
Dalam yang terakhir, blok kuning adalah kode yang hanya dieksekusi jika
i<=0
, dan blok biru adalah kapani>0
. Dalam kedua versi instruksi, pernyataan if-else lebih pendek.Perhatikan bahwa, untuk instruksi yang berbeda, [ CPI ] tidak harus sama. Secara logis, untuk instruksi yang sama, lebih banyak instruksi membutuhkan siklus yang lebih lama. Tetapi jika instruksi mengambil waktu dan pipa / cache juga diperhitungkan, maka total waktu pelaksanaan sebenarnya tergantung pada prosesor. Prosesor juga dapat memprediksi cabang.
Prosesor modern bahkan memiliki lebih banyak inti, hal-hal dapat menjadi lebih kompleks dengan itu. Jika Anda adalah pengguna prosesor Intel, Anda mungkin ingin melihat [ Intel® 64 and IA-32 Architectures Reference Reference Manual ].
Saya tidak tahu apakah ada CLR yang diimplementasikan perangkat keras, tetapi jika ya, Anda mungkin lebih cepat dengan operator kondisional karena IL jelas lebih rendah.
Catatan: Semua kode mesin adalah x86.
sumber
Saya melakukan apa yang dilakukan Jon Skeet dan berlari melalui 1 iterasi dan 1.000 iterasi dan mendapat hasil yang berbeda dari OP dan Jon. Di tambang, terner hanya sedikit lebih cepat. Di bawah ini adalah kode yang tepat:
Output dari program saya:
Proses lain dalam milidetik:
Ini berjalan di 64-bit XP, dan saya berlari tanpa debugging.
Sunting - Berjalan di x86:
Ada perbedaan besar menggunakan x86. Ini dilakukan tanpa melakukan debug pada dan pada mesin xp 64-bit yang sama seperti sebelumnya, tetapi dibuat untuk CPU x86. Ini lebih mirip OP.
sumber
Kode assembler yang dihasilkan akan mengisahkan:
Menghasilkan:
Sedangkan:
Menghasilkan:
Jadi ternary bisa lebih pendek dan lebih cepat hanya karena menggunakan instruksi lebih sedikit dan tidak ada lompatan jika Anda mencari benar / salah. Jika Anda menggunakan nilai selain 1 dan 0, Anda akan mendapatkan kode yang sama dengan if / else, misalnya:
Menghasilkan:
Yang sama dengan if / else.
sumber
Jalankan tanpa debugging ctrl + F5 tampaknya debugger memperlambat baik ifs dan ternary secara signifikan tetapi tampaknya memperlambat operator ternary jauh lebih banyak.
Ketika saya menjalankan kode berikut di sini adalah hasil saya. Saya pikir perbedaan milidetik kecil disebabkan oleh kompiler mengoptimalkan max = max dan menghapusnya tetapi mungkin tidak membuat optimasi untuk operator ternary. Jika seseorang dapat memeriksa majelis dan mengonfirmasi ini akan luar biasa.
Kode
sumber
Melihat IL yang dihasilkan, ada 16 operasi lebih sedikit di dalamnya daripada dalam pernyataan if / else (menyalin dan menempelkan kode @ JonSkeet). Namun, itu tidak berarti itu harus menjadi proses yang lebih cepat!
Untuk meringkas perbedaan dalam IL, metode if / else diterjemahkan kurang lebih sama dengan membaca kode C # (melakukan penambahan dalam cabang) sedangkan kode kondisional memuat 2 atau 3 ke dalam tumpukan (tergantung pada nilainya) dan kemudian menambahkannya ke nilai di luar persyaratan.
Perbedaan lainnya adalah instruksi percabangan yang digunakan. Metode if / else menggunakan brtrue (cabang jika benar) untuk melompati kondisi pertama, dan cabang tanpa syarat untuk melompat dari yang pertama dari pernyataan if. Kode kondisional menggunakan bgt (cabang jika lebih besar dari) daripada brtrue, yang mungkin bisa menjadi perbandingan yang lebih lambat.
Juga (baru saja membaca tentang prediksi cabang) mungkin ada penalti kinerja untuk cabang yang lebih kecil. Cabang kondisional hanya memiliki 1 instruksi di dalam cabang tetapi if / else memiliki 7. Ini juga akan menjelaskan mengapa ada perbedaan antara menggunakan panjang dan int, karena mengubah ke int mengurangi jumlah instruksi di cabang if / else dengan 1 (membuat baca-depan lebih sedikit)
sumber
Dalam kode berikut, jika / yang lain tampaknya kira-kira 1,4 kali lebih cepat dari operator ternary. Namun, saya menemukan bahwa memperkenalkan variabel sementara mengurangi waktu operasi operator ternary sekitar 1,4 kali:
sumber
Terlalu banyak jawaban bagus tetapi saya menemukan sesuatu yang menarik, perubahan yang sangat sederhana membuat dampaknya. Setelah membuat perubahan di bawah ini, untuk mengeksekusi if-else dan operator ternary akan membutuhkan waktu yang sama.
alih-alih menulis di bawah baris
Saya menggunakan ini,
Salah satu jawaban di bawah ini juga menyebutkan bahwa cara yang buruk untuk menulis operator ternary.
Saya harap ini akan membantu Anda menulis operator ternary, alih-alih memikirkan mana yang lebih baik.
Nested Ternary Operator: Saya menemukan operator ternary yang bersarang dan beberapa jika blok juga akan mengambil waktu yang sama untuk mengeksekusi.
sumber