Operator ternary dua kali lebih lambat dari blok if-else?

246

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?

pengguna1032613
sumber
96
Hal pertama yang harus diperbaiki: jangan gunakan DateTimeuntuk mengukur kinerja. Gunakan Stopwatch. Selanjutnya, waktu yang agak lama - itu waktu yang sangat singkat untuk diukur.
Jon Skeet
49
Gunakan seed ketika Anda membuat Randomobjek, sehingga selalu memberikan urutan yang sama. Jika Anda menguji kode yang berbeda dengan data yang berbeda, Anda dapat melihat perbedaan dalam kinerja.
Guffa
12
Apakah Anda juga mencoba mengkompilasi / menjalankannya dalam mode rilis dengan optimisasi kompiler dihidupkan, dan tanpa debugger terpasang?
Chris Sinclair
7
@LarryOBrien: Pengambilan yang menarik. Saya baru saja melakukan tes LINQPad cepat dan mendapatkan hasil yang sangat berbeda dengan array diurutkan atau tidak. Bahkan, dengan diurutkan saya mereproduksi perbedaan kecepatan yang sama dilaporkan. Menghapus pengurutan juga menghapus perbedaan waktu.
Chris Sinclair
39
Intinya di sini adalah bahwa optimasi mikro pengujian kinerja sulit . Hampir semua hal yang Anda amati dalam hasil Anda terkait dengan bug dalam kode pengujian Anda, bukan perbedaan dalam kode yang bermakna. Ketika Anda memperbaiki yang tercantum di sini, akan ada lebih banyak, saya dapat meyakinkan Anda. Moral cerita, jangan repot-repot dengan optimasi mikro atau mencoba mengujinya di tempat pertama. Jika kode sebenarnya sulit untuk diukur itu berarti itu tidak cukup lambat untuk menjadi hambatan; abaikan itu.
Servy

Jawaban:

376

Untuk menjawab pertanyaan ini, kami akan memeriksa kode perakitan yang dihasilkan oleh JIT X86 dan X64 untuk masing-masing kasus ini.

X86, jika / maka

    32:                 foreach (int i in array)
0000007c 33 D2                xor         edx,edx 
0000007e 83 7E 04 00          cmp         dword ptr [esi+4],0 
00000082 7E 1C                jle         000000A0 
00000084 8B 44 96 08          mov         eax,dword ptr [esi+edx*4+8] 
    33:                 {
    34:                     if (i > 0)
00000088 85 C0                test        eax,eax 
0000008a 7E 08                jle         00000094 
    35:                     {
    36:                         value += 2;
0000008c 83 C3 02             add         ebx,2 
0000008f 83 D7 00             adc         edi,0 
00000092 EB 06                jmp         0000009A 
    37:                     }
    38:                     else
    39:                     {
    40:                         value += 3;
00000094 83 C3 03             add         ebx,3 
00000097 83 D7 00             adc         edi,0 
0000009a 42                   inc         edx 
    32:                 foreach (int i in array)
0000009b 39 56 04             cmp         dword ptr [esi+4],edx 
0000009e 7F E4                jg          00000084 
    30:             for (int x = 0; x < iterations; x++)
000000a0 41                   inc         ecx 
000000a1 3B 4D F0             cmp         ecx,dword ptr [ebp-10h] 
000000a4 7C D6                jl          0000007C 

X86, ternary

    59:                 foreach (int i in array)
00000075 33 F6                xor         esi,esi 
00000077 83 7F 04 00          cmp         dword ptr [edi+4],0 
0000007b 7E 2D                jle         000000AA 
0000007d 8B 44 B7 08          mov         eax,dword ptr [edi+esi*4+8] 
    60:                 {
    61:                     value += i > 0 ? 2 : 3;
00000081 85 C0                test        eax,eax 
00000083 7F 07                jg          0000008C 
00000085 BA 03 00 00 00       mov         edx,3 
0000008a EB 05                jmp         00000091 
0000008c BA 02 00 00 00       mov         edx,2 
00000091 8B C3                mov         eax,ebx 
00000093 8B 4D EC             mov         ecx,dword ptr [ebp-14h] 
00000096 8B DA                mov         ebx,edx 
00000098 C1 FB 1F             sar         ebx,1Fh 
0000009b 03 C2                add         eax,edx 
0000009d 13 CB                adc         ecx,ebx 
0000009f 89 4D EC             mov         dword ptr [ebp-14h],ecx 
000000a2 8B D8                mov         ebx,eax 
000000a4 46                   inc         esi 
    59:                 foreach (int i in array)
000000a5 39 77 04             cmp         dword ptr [edi+4],esi 
000000a8 7F D3                jg          0000007D 
    57:             for (int x = 0; x < iterations; x++)
000000aa FF 45 E4             inc         dword ptr [ebp-1Ch] 
000000ad 8B 45 E4             mov         eax,dword ptr [ebp-1Ch] 
000000b0 3B 45 F0             cmp         eax,dword ptr [ebp-10h] 
000000b3 7C C0                jl          00000075 

X64, jika / kemudian

    32:                 foreach (int i in array)
00000059 4C 8B 4F 08          mov         r9,qword ptr [rdi+8] 
0000005d 0F 1F 00             nop         dword ptr [rax] 
00000060 45 85 C9             test        r9d,r9d 
00000063 7E 2B                jle         0000000000000090 
00000065 33 D2                xor         edx,edx 
00000067 45 33 C0             xor         r8d,r8d 
0000006a 4C 8B 57 08          mov         r10,qword ptr [rdi+8] 
0000006e 66 90                xchg        ax,ax 
00000070 42 8B 44 07 10       mov         eax,dword ptr [rdi+r8+10h] 
    33:                 {
    34:                     if (i > 0)
00000075 85 C0                test        eax,eax 
00000077 7E 07                jle         0000000000000080 
    35:                     {
    36:                         value += 2;
00000079 48 83 C5 02          add         rbp,2 
0000007d EB 05                jmp         0000000000000084 
0000007f 90                   nop 
    37:                     }
    38:                     else
    39:                     {
    40:                         value += 3;
00000080 48 83 C5 03          add         rbp,3 
00000084 FF C2                inc         edx 
00000086 49 83 C0 04          add         r8,4 
    32:                 foreach (int i in array)
0000008a 41 3B D2             cmp         edx,r10d 
0000008d 7C E1                jl          0000000000000070 
0000008f 90                   nop 
    30:             for (int x = 0; x < iterations; x++)
00000090 FF C1                inc         ecx 
00000092 41 3B CC             cmp         ecx,r12d 
00000095 7C C9                jl          0000000000000060 

X64, ternary

    59:                 foreach (int i in array)
00000044 4C 8B 4F 08          mov         r9,qword ptr [rdi+8] 
00000048 45 85 C9             test        r9d,r9d 
0000004b 7E 2F                jle         000000000000007C 
0000004d 45 33 C0             xor         r8d,r8d 
00000050 33 D2                xor         edx,edx 
00000052 4C 8B 57 08          mov         r10,qword ptr [rdi+8] 
00000056 8B 44 17 10          mov         eax,dword ptr [rdi+rdx+10h] 
    60:                 {
    61:                     value += i > 0 ? 2 : 3;
0000005a 85 C0                test        eax,eax 
0000005c 7F 07                jg          0000000000000065 
0000005e B8 03 00 00 00       mov         eax,3 
00000063 EB 05                jmp         000000000000006A 
00000065 B8 02 00 00 00       mov         eax,2 
0000006a 48 63 C0             movsxd      rax,eax 
0000006d 4C 03 E0             add         r12,rax 
00000070 41 FF C0             inc         r8d 
00000073 48 83 C2 04          add         rdx,4 
    59:                 foreach (int i in array)
00000077 45 3B C2             cmp         r8d,r10d 
0000007a 7C DA                jl          0000000000000056 
    57:             for (int x = 0; x < iterations; x++)
0000007c FF C1                inc         ecx 
0000007e 3B CD                cmp         ecx,ebp 
00000080 7C C6                jl          0000000000000048 

Pertama: mengapa kode X86 jauh lebih lambat daripada X64?

Ini disebabkan oleh karakteristik kode berikut:

  1. X64 memiliki beberapa register tambahan yang tersedia, dan setiap register adalah 64-bit. Hal ini memungkinkan JIT X64 untuk melakukan loop dalam sepenuhnya menggunakan register selain dari memuat idari array, sedangkan JIT X86 menempatkan beberapa operasi tumpukan (akses memori) dalam loop.
  2. valueadalah integer 64-bit, yang membutuhkan 2 instruksi mesin pada X86 ( adddiikuti oleh adc) 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 2dan 3dalam addinstruksi mesin, JIT membuat variabel perantara (dalam register) untuk menyimpan hasilnya. Register ini kemudian diperpanjang dari 32-bit menjadi 64-bit sebelum ditambahkan value. 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).

Sam Harwell
sumber
18
Compiler mungkin juga memperluas terner menjadi if-else.
dezfowler
13
Perhatikan bahwa x86 hanya lebih lambat saat menggunakan terner - sama cepatnya dengan x64 saat menggunakan if / else . Jadi pertanyaan yang harus dijawab adalah: "mengapa kode X86 jauh lebih lambat daripada X64 saat menggunakan operator ternary?".
Eren Ersönmez
18
Tentunya tidak ada alasan untuk ini dan MS harus 'memperbaikinya' - karena Ternary secara efektif hanya sintaks yang lebih pendek untuk if / else ?! Anda tentu tidak akan mengharapkan untuk membayar penalti kinerja.
niico
6
@niico tidak ada yang perlu 'diperbaiki' tentang operator ternary. Penggunaannya dalam hal ini hanya menyebabkan alokasi register yang berbeda. Dalam kasus yang berbeda, mungkin lebih cepat daripada jika / yang lain, ketika saya mencoba menjelaskan dalam jawaban saya.
Eren Ersönmez
6
@ ErenErsönmez: Tentu ada sesuatu untuk diperbaiki. Tim optimizer dapat dengan hati-hati menganalisis dua kasus dan menemukan cara untuk menyebabkan operator ternary, dalam hal ini, sama cepatnya dengan jika ada. Tentu saja, perbaikan semacam itu mungkin tidak layak atau terlalu mahal.
Brian
63

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:

  • Jalankan di aplikasi konsol
  • Bangun dengan /o+ /debug-, dan jalankan di luar debugger
  • Jalankan kedua bagian kode itu sekali untuk JIT, lalu banyak kali untuk akurasi lebih
  • Menggunakan Stopwatch

Hasil dengan /platform:x64(tanpa garis "abaikan"):

if/else with 1 iterations: 17ms
conditional with 1 iterations: 19ms
if/else with 1000 iterations: 17875ms
conditional with 1000 iterations: 19089ms

Hasil dengan /platform:x86(tanpa garis "abaikan"):

if/else with 1 iterations: 18ms
conditional with 1 iterations: 49ms
if/else with 1000 iterations: 17901ms
conditional with 1000 iterations: 47710ms

Detail sistem saya:

  • x64 i7-2720QM CPU @ 2.20GHz
  • 64-bit Windows 8
  • .NET 4.5

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:

using System;
using System.Diagnostics;

class Test
{
    static void Main()
    {
        Random r = new Random(0);
        int[] array = new int[20000000];
        for(int i = 0; i < array.Length; i++)
        {
            array[i] = r.Next(int.MinValue, int.MaxValue);
        }
        Array.Sort(array);
        // JIT everything...
        RunIfElse(array, 1);
        RunConditional(array, 1);
        // Now really time it
        RunIfElse(array, 1000);
        RunConditional(array, 1000);
    }

    static void RunIfElse(int[] array, int iterations)
    {        
        long value = 0;
        Stopwatch sw = Stopwatch.StartNew();

        for (int x = 0; x < iterations; x++)
        {
            foreach (int i in array)
            {
                if (i > 0)
                {
                    value += 2;
                }
                else
                {
                    value += 3;
                }
            }
        }
        sw.Stop();
        Console.WriteLine("if/else with {0} iterations: {1}ms",
                          iterations,
                          sw.ElapsedMilliseconds);
        // Just to avoid optimizing everything away
        Console.WriteLine("Value (ignore): {0}", value);
    }

    static void RunConditional(int[] array, int iterations)
    {        
        long value = 0;
        Stopwatch sw = Stopwatch.StartNew();

        for (int x = 0; x < iterations; x++)
        {
            foreach (int i in array)
            {
                value += i > 0 ? 2 : 3;
            }
        }
        sw.Stop();
        Console.WriteLine("conditional with {0} iterations: {1}ms",
                          iterations,
                          sw.ElapsedMilliseconds);
        // Just to avoid optimizing everything away
        Console.WriteLine("Value (ignore): {0}", value);
    }
}
Jon Skeet
sumber
31
Jadi pertanyaan semua orang masih ingin tahu, adalah mengapa ada perbedaan kecil.
Brad M
1
@BradM: Ya, IL akan berbeda, dan perbedaan apa pun bisa melakukan semua hal pada saat JIT-dikompilasi dan kemudian CPU itu sendiri telah melakukan hal-hal buruk untuk itu.
Jon Skeet
4
@JonSkeet FYI. jalankan kode Anda, persis seperti yang Anda jelaskan. 19 vs 52 di x86, dan 19 vs 21 di x64.
Eren Ersönmez
5
@ user1032613: Sekarang saya dapat mereproduksi hasil Anda. Lihat hasil edit saya. Permintaan maaf karena meragukan Anda sebelumnya - sungguh menakjubkan perbedaan perubahan dalam arsitektur dapat membuat ...
Jon Skeet
3
@ BЈовић: Memang. Ini dimulai karena tidak dapat mereproduksi sama sekali, tetapi berkembang seiring waktu. Itu tidak memberikan alasan, tapi saya pikir itu masih informasi yang berguna (misalnya perbedaan x64 vs x86) yang mengapa saya meninggalkannya.
Jon Skeet
43

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):

int diff;
if (i > 0) 
    diff = 2;
else 
    diff = 3;
value += diff;

vs.

value += i > 0 ? 2 : 3;

Sekarang pembongkaran untuk if/elsemenjadi seperti yang ditunjukkan di bawah ini. Perhatikan bahwa ini sedikit lebih buruk daripada kasus ternary, karena berhenti menggunakan register untuk variabel loop ( i) juga.

                if (i > 0)
0000009d  cmp         dword ptr [ebp-20h],0 
000000a1  jle         000000AD 
                {
                    diff = 2;
000000a3  mov         dword ptr [ebp-24h],2 
000000aa  nop 
000000ab  jmp         000000B4 
                }
                else
                {
                    diff = 3;
000000ad  mov         dword ptr [ebp-24h],3 
                }
                value += diff;
000000b4  mov         eax,dword ptr [ebp-18h] 
000000b7  mov         edx,dword ptr [ebp-14h] 
000000ba  mov         ecx,dword ptr [ebp-24h] 
000000bd  mov         ebx,ecx 
000000bf  sar         ebx,1Fh 
000000c2  add         eax,ecx 
000000c4  adc         edx,ebx 
000000c6  mov         dword ptr [ebp-18h],eax 
000000c9  mov         dword ptr [ebp-14h],edx 
000000cc  inc         dword ptr [ebp-28h] 
Eren Ersönmez
sumber
5
Bagaimana kalau menekankan membandingkan apel dan jeruk ?
Ken Kin
6
Yah, saya tidak akan mengatakan bahwa itu membandingkan apel dan jeruk. Kedua varian memiliki semantik yang sama , sehingga pengoptimal dapat mencoba kedua varian pengoptimalan dan memilih mana yang lebih efisien dalam kedua kasus.
Vlad
Saya melakukan tes seperti yang Anda sarankan: memperkenalkan variabel lain diff, tetapi ternary masih jauh lebih lambat - sama sekali tidak apa yang Anda katakan. Apakah Anda melakukan percobaan sebelum memposting "jawaban" ini?
user1032613
9

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:

if (i > 0)
{
    value += 2;
}
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:

var array1=new[] { 1, 2, 3 };
var array2=new[] { 5, 6, 7 };

if(i>0)
    array1[1]=4;
else
    array2[2]=4;

kode di atas mengkompilasi, namun, ganti pernyataan if-else dengan operator kondisional tidak akan dikompilasi:

var array1=new[] { 1, 2, 3 };
var array2=new[] { 5, 6, 7 };
(i>0?array1[1]:array2[2])=4; // incorrect usage 

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:

public static class TestClass {
    public static void TestConditionalOperator(int i) {
        long value=0;
        value+=i>0?2:3;
    }

    public static void TestIfElse(int i) {
        long value=0;

        if(i>0) {
            value+=2;
        }
        else {
            value+=3;
        }
    }

    public static void TestMethod() {
        TestConditionalOperator(0);
        TestIfElse(0);
    }
}

dan berikut ini adalah dua versi IL yang dioptimalkan dan tidak. Karena panjang, saya menggunakan gambar untuk ditampilkan, sisi kanan adalah yang dioptimalkan:

(Klik untuk melihat gambar ukuran penuh.) hSN6s.png

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.) ybhgM.png

  • Instruksi yang dioptimalkan: (Klik untuk melihat gambar ukuran penuh.) 6kgzJ.png

Dalam yang terakhir, blok kuning adalah kode yang hanya dieksekusi jika i<=0, dan blok biru adalah kapan i>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.

Ken Kin
sumber
7

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:

static void runIfElse(int[] array, int iterations)
    {
        long value = 0;
        Stopwatch ifElse = new Stopwatch();
        ifElse.Start();
        for (int c = 0; c < iterations; c++)
        {
            foreach (int i in array)
            {
                if (i > 0)
                {
                    value += 2;
                }
                else
                {
                    value += 3;
                }
            }
        }
        ifElse.Stop();
        Console.WriteLine(String.Format("Elapsed time for If-Else: {0}", ifElse.Elapsed));
    }

    static void runTernary(int[] array, int iterations)
    {
        long value = 0;
        Stopwatch ternary = new Stopwatch();
        ternary.Start();
        for (int c = 0; c < iterations; c++)
        {
            foreach (int i in array)
            {
                value += i > 0 ? 2 : 3;
            }
        }
        ternary.Stop();


        Console.WriteLine(String.Format("Elapsed time for Ternary: {0}", ternary.Elapsed));
    }

    static void Main(string[] args)
    {
        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;

        runIfElse(array, 1);
        runTernary(array, 1);
        runIfElse(array, 1000);
        runTernary(array, 1000);
        
        Console.ReadLine();
    }

Output dari program saya:

Waktu yang lewat untuk If-Else: 00: 00: 00.0140543

Waktu yang berlalu untuk Ternary: 00: 00: 00.0136723

Waktu berlalu untuk If-Else: 00: 00: 14.0167870

Waktu yang berlalu untuk Ternary: 00: 00: 13.9418520

Proses lain dalam milidetik:

Waktu yang berlalu untuk If-Else: 20

Waktu yang berlalu untuk Ternary: 19

Waktu yang berlalu untuk If-Else: 13854

Waktu yang berlalu untuk Ternary: 13610

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.

Waktu yang berlalu untuk If-Else: 18

Waktu yang berlalu untuk Ternary: 35

Waktu yang berlalu untuk If-Else: 20512

Waktu yang berlalu untuk Ternary: 32673

Shaz
sumber
Bisakah Anda mencobanya di x86? Terima kasih.
user1032613
@ user1032613 Saya pikir mungkin ada perbedaan besar jika Anda menjalankan tanpa debugging vs dengan debugging.
CodeCamper
@ user1032613 Saya baru saja mengedit posting saya dengan data dari x86. Itu lebih mirip milikmu, di mana ternary 2x lebih lambat.
Shaz
5

Kode assembler yang dihasilkan akan mengisahkan:

a = (b > c) ? 1 : 0;

Menghasilkan:

mov  edx, DWORD PTR a[rip]
mov  eax, DWORD PTR b[rip]
cmp  edx, eax
setg al

Sedangkan:

if (a > b) printf("a");
else printf("b");

Menghasilkan:

mov edx, DWORD PTR a[rip]
mov eax, DWORD PTR b[rip]
cmp edx, eax
jle .L4
    ;printf a
jmp .L5
.L4:
    ;printf b
.L5:

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:

a = (b > c) ? 2 : 3;

Menghasilkan:

mov edx, DWORD PTR b[rip]
mov eax, DWORD PTR c[rip]
cmp edx, eax
jle .L6
    mov eax, 2
jmp .L7
.L6:
    mov eax, 3
.L7:

Yang sama dengan if / else.


sumber
4

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.

--Run #1--
Type   | Milliseconds
Ternary 706
If     704
%: .9972
--Run #2--
Type   | Milliseconds
Ternary 707
If     704
%: .9958
--Run #3--
Type   | Milliseconds
Ternary 706
If     704
%: .9972

Kode

  for (int t = 1; t != 10; t++)
        {
            var s = new System.Diagnostics.Stopwatch();
            var r = new Random(123456789);   //r
            int[] randomSet = new int[1000]; //a
            for (int i = 0; i < 1000; i++)   //n
                randomSet[i] = r.Next();     //dom
            long _ternary = 0; //store
            long _if = 0;      //time
            int max = 0; //result
            s.Start();
            for (int q = 0; q < 1000000; q++)
            {
                for (int i = 0; i < 1000; i++)
                    max = max > randomSet[i] ? max : randomSet[i];
            }
            s.Stop();
            _ternary = s.ElapsedMilliseconds;
            max = 0;
            s = new System.Diagnostics.Stopwatch();
            s.Start();
            for (int q = 0; q < 1000000; q++)
            {
                for (int i = 0; i < 1000; i++)
                    if (max > randomSet[i])
                        max = max; // I think the compiler may remove this but not for the ternary causing the speed difference.
                    else
                        max = randomSet[i];
            }

            s.Stop();
            _if = s.ElapsedMilliseconds;
            Console.WriteLine("--Run #" + t+"--");
            Console.WriteLine("Type   | Milliseconds\nTernary {0}\nIf     {1}\n%: {2}", _ternary, _if,((decimal)_if/(decimal)_ternary).ToString("#.####"));
        }
CodeCamper
sumber
4

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)

Matthew Steeples
sumber
1

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:

Jika / Lain: 98 ms

Ternary: 141 ms

Ternary dengan temp var: 100 ms

using System;
using System.Diagnostics;

namespace ConsoleApplicationTestIfElseVsTernaryOperator
{
    class Program
    {
        static void Main(string[] args)
        {
            Random r = new Random(0);
            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;
            Stopwatch stopwatch = new Stopwatch();

            value = 0;
            stopwatch.Restart();
            foreach (int i in array)
            {
                if (i > 0)
                {
                    value += 2;
                }
                else
                {
                    value += 3;
                }
                // 98 ms
            }
            stopwatch.Stop();
            Console.WriteLine("If/Else: " + stopwatch.ElapsedMilliseconds.ToString() + " ms");

            value = 0;
            stopwatch.Restart();
            foreach (int i in array)
            {
                value += (i > 0) ? 2 : 3; 
                // 141 ms
            }

            stopwatch.Stop();
            Console.WriteLine("Ternary: " + stopwatch.ElapsedMilliseconds.ToString() + " ms");

            value = 0;
            int tempVar = 0;
            stopwatch.Restart();
            foreach (int i in array)
            {
                tempVar = (i > 0) ? 2 : 3;
                value += tempVar; 
                // 100ms
            }
            stopwatch.Stop();
            Console.WriteLine("Ternary with temp var: " + stopwatch.ElapsedMilliseconds.ToString() + " ms");

            Console.ReadKey(true);
        }
    }
}
Alexey Novikov
sumber
0

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

value +=  i > 0 ? 2 : 3;

Saya menggunakan ini,

int a =  i > 0 ? 2 : 3;
value += a;

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.

Ravindra Sinare
sumber