Üçlü operatör if-else bloğunun iki katı mı?


246

Üçlü operatörün eşdeğerinden daha hızlı veya en azından aynı olduğu her yerde okudum if-else bloğun.

Ancak, aşağıdaki testi yaptım ve durumun böyle olmadığını öğrendim:

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());

Bilgisayarım yukarıdaki kodu çalıştırmak için 85 ms sürdü. Ama eğer yorum yaparsam if-else chunk ve üçlü operatör satırını uncomment, yaklaşık 157 ms sürecek.

Bu neden oluyor?


96
Düzeltilmesi gereken ilk şey: DateTimeperformansı ölçmek için kullanmayın . Kullanın Stopwatch. Daha sonra, zaman daha uzun - ölçmek için çok kısa bir zaman.
Jon Skeet

49
RandomNesneyi oluştururken bir tohum kullanın , böylece her zaman aynı sırayı verir. Farklı verileri farklı kodlarla test ederseniz, performans farklılıklarını çok iyi görebilirsiniz.
Guffa

12
Ayrıca derleyici optimizasyonları açık ve hata ayıklayıcı takılı olmadan yayın modunda derlemeyi / çalıştırmayı denediniz mi?
Chris Sinclair

7
@LarryOBrien: İlginç bir şey. Ben sadece hızlı bir LINQPad testi yaptım ve dizi veya sıralı çok farklı sonuçlar elde. Aslında, sıralama ile raporlanan aynı hız farkı yeniden. Sıralama kaldırıldığında saat farkı da kaldırılır.
Chris Sinclair

39
Buradaki nokta, performans testi mikrooptimizasyonlarının zor olmasıdır . Sonuçta gözlemlediğiniz hemen hemen her şey, anlamlı koddaki farklarla değil, test kodunuzdaki hatalarla ilgilidir. Burada listelenenleri düzelttiğinizde, daha fazlası olacak, sizi temin ederim. Hikayenin ahlaki, mikrooptimizasyonlarla uğraşmayın veya ilk etapta onları test etmeye çalışmayın. Kodun ölçülmesi gerçekten zorsa, bir darboğaz olacak kadar yavaş olmadığı anlamına gelir; boşver.
Servy

Yanıtlar:


376

Bu soruyu cevaplamak için, bu vakaların her biri için X86 ve X64 JIT'ler tarafından üretilen montaj kodunu inceleyeceğiz.

X86, eğer / sonra

    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, üçlü

    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, eğer / sonra

    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, üçlü

    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 

Birincisi: X86 kodu neden X64'ten bu kadar yavaş?

Bu, kodun aşağıdaki özelliklerinden kaynaklanmaktadır:

  1. X64'te birkaç ek kayıt vardır ve her kayıt 64 bittir. Bu, X64 JIT'in idiziden yükleme dışında kayıtları kullanarak iç döngüyü tamamen gerçekleştirmesine izin verirken, X86 JIT döngüde birkaç yığın işlemi (bellek erişimi) yerleştirir.
  2. value64 bitlik bir tamsayıdır, bu X86'da 2 makine talimatı ( addardından gelir adc), ancak X64'te sadece 1 ( add) gerektirir.

İkincisi: üçlü operatör neden hem X86 hem de X64'te daha yavaş?

Bunun nedeni, JIT'in optimize edicisini etkileyen işlem sırasındaki küçük bir farktır. Üçlü operatör yerine direkt olarak kodlama tam zamanında için 2ve 3de addkendileri makine talimatları, (bir kayıttaki) bir ara değişken oluşturmak tam zamanında sonuçları tutmak. Bu kayıt daha sonra eklenmeden önce 32 bit'ten 64 bit'e işaretlenir value. Tüm bunlar, X64 için kayıtlarda gerçekleştirildiğinden, üçlü operatör için karmaşıklıkta önemli artışa rağmen, net etki biraz azaltılmıştır.

Öte yandan X86 JIT daha büyük ölçüde etkilenir, çünkü iç döngüde yeni bir ara değerin eklenmesi, başka bir değerin "dökülmesine" neden olur ve bu da iç döngüde en az 2 ek bellek erişimine neden olur (erişimlere bakın) için [ebp-14h]) X86 üçlü kodu.


18
Derleyici üçlüyü bir if-else içine genişletebilir.
dezfowler

13
X86'nın yalnızca üçlü kullanırken daha yavaş olduğuna dikkat edin - if / else kullanılırken x64 ile aynı hızdadır . Cevaplanacak soru şudur: "Üçlü operatörü kullanırken X86 kodu neden X64'ten çok daha yavaş?".
Eren Ersönmez

18
Şüphesiz bunun için iyi bir neden yoktur ve MS bunu düzeltmelidir - çünkü Ternary if / else için sadece daha kısa bir sözdizimidir? Kesinlikle bir performans cezası ödemeyi beklemezsiniz.
niico

6
@niico üçlü operatör hakkında 'düzeltilecek' hiçbir şey yoktur. bu durumda kullanımı sadece farklı bir kayıt tahsisine neden olur. Farklı bir durumda, cevabımda açıklamaya çalıştığım gibi, if / else'den daha hızlı olabilir.
Eren Ersönmez

6
@ ErenErsönmez: Elbette düzeltilecek bir şey var. Optimize edici ekip iki vakayı dikkatli bir şekilde analiz edebilir ve üçlü operatörün, bu durumda, tıpkı if-kadar hızlı olmasına yol açmanın bir yolunu bulabilir. Tabii ki, böyle bir düzeltme mümkün olmayabilir veya çok pahalı olabilir.
Brian

63

EDIT: Tüm değişiklikler ... aşağıya bakın.

Sonuçlarınızı x64 CLR'de üretemiyorum, ancak x86'da yapabilirim . X64'te koşullu işleç ile if / else arasında küçük bir fark (% 10'dan az) görebiliyorum, ancak gördüğünüzden çok daha küçük.

Aşağıdaki olası değişiklikleri yaptım:

  • Bir konsol uygulamasında çalıştırın
  • /o+ /debug-Hata ayıklayıcı ile derleyin ve çalıştırın
  • Her iki kod parçasını JIT için bir kez, daha fazla doğruluk için birçok kez çalıştırın
  • kullanım Stopwatch

Sonuçları /platform:x64("yoksay" satırları olmadan):

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

Sonuçları /platform:x86("yoksay" satırları olmadan):

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

Sistem bilgilerim:

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

Yani eskisinden farklı olarak, sanırım sen edilir gerçek bir fark görme - ve x86 JIT ile ilgisi hepsi bu. Tam olarak neye sebep olduğunu söylemek istemiyorum.

İlginç bir şekilde, önce diziyi sıralamaksızın, en azından x64'te yaklaşık 4.5x uzunluğunda testler yapıyorum. Benim tahminim bunun şube tahmini ile ilgisi var.

Kod:

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);
    }
}

31
Herkesin hala bilmek için ölmekte olduğu soru, neden ufak bir fark olduğu.
Brad M

1
@BradM: IL farklı olacak ve herhangi bir fark, JIT derlendiğinde her şeyi yapabilirdi ve CPU'nun kendisi kötü şeyler yaptı.
Jon Skeet

4
@JonSkeet FYI. tam açıkladığınız gibi kodunuzu çalıştırdınız. 19s vs 52s x86 ve 19s vs 21s x64.
Eren Ersönmez

5
@ user1032613: Ben yapabilirsiniz şimdi sonuçları yeniden. Düzenlememe bakın. Daha önce şüphe ettiğiniz için özür dilerim - mimaride bir değişikliğin yapabileceği fark şaşırtıcı ...
Jon Skeet

3
@ BЈовић: Gerçekten. Hiç çoğaltamama olarak başladı, ama zamanla gelişti. Sebebini vermiyor, ama yine de yararlı bilgiler olduğunu düşündüm (örn. X64 vs x86 farkı), bu yüzden bıraktım.
Jon Skeet

43

Farkın if / else vs ternary ile bir ilgisi yok.

Jitted demontajlara bakarak (burada repaste olmayacağım, pls @ 280Z28'in cevabına bakın), elma ve portakalları karşılaştırdığınız ortaya çıkıyor . Bir olayda, iki farklı oluşturmak +=sabit değerlerle işlemleri birinden birini seçiyorsunuz bir koşula bağlıdır hangi ve diğer durumda, bir oluşturmak +=nerede eklenti değer bir koşula bağlıdır.

Eğer / else vs ternary'yi gerçekten karşılaştırmak istiyorsanız, bu daha adil bir karşılaştırma olacaktır (şimdi her ikisi de eşit derecede "yavaş" olacaktır, hatta üçlünün biraz daha hızlı olduğunu söyleyebiliriz):

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

vs.

value += i > 0 ? 2 : 3;

Şimdi, sökme işlemi if/elseaşağıda gösterildiği gibi olur. Döngü değişkeni ( i) için yazmaçları kullanmayı bıraktığından, bu durum üçlü durumdan biraz daha kötüdür .

                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] 

5
Elma ve portakalları karşılaştırmayı vurgulamaya ne dersiniz ?
Ken Kin

6
Aslında elma ve portakalları karşılaştırdığını söylemezdim. Varyantlar her ikisi de aynı olması anlambilim iyileştirici yüzden olabilir hem optimizasyon varyantlarını denemek ve daha verimli hangisi tercih ya durumda.
Vlad

Testi önerdiğiniz gibi yaptım: başka bir değişken tanıttı diff, ancak üçlü hala çok daha yavaş - söylediklerinize hiç. Bu "cevabı" göndermeden önce deneyi yaptınız mı?
user1032613

9

Düzenle:

Koşullu işleçle değil, if-else deyimiyle yapılabilecek bir örnek eklendi.


Cevaptan önce, lütfen [ Hangisi daha hızlı? ] Bay Lippert'in blogunda. Bence Sayın Ersönmez'in cevabı buradaki en doğru cevap .

Üst düzey bir programlama diliyle aklımızda tutmamız gereken bir şeyden bahsetmeye çalışıyorum.

Öncelikle, koşullu operatörün C♯'deki if-else deyimiyle daha hızlı veya eşit performansta olması gerektiğini hiç duymadım .

Nedeni basittir, if-else deyimi ile herhangi bir işlem yapılmazsa:

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

Koşullu operatörün gerekliliği, her iki taraf için de bir değer olması gerektiğidir ve C♯'de her iki tarafın :da aynı tipte olmasını gerektirir . Bu sadece if-else deyiminden farklı kılar. Böylece sorunuz, makine kodunun talimatının nasıl oluşturulduğunu soran bir soru haline gelir, böylece performans farkı.

Koşullu operatör ile semantik olarak:

İfade ne olursa olsun, bir değer vardır.

Ancak if-else ifadesiyle:

İfade doğru olarak değerlendirilirse, bir şeyler yapın; değilse, başka bir şey yapın.

Bir değer mutlaka if-else deyimiyle ilgili değildir. Varsayımınız yalnızca optimizasyonla mümkündür.

Aralarındaki farkı göstermek için başka bir örnek şöyle olacaktır:

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

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

ancak yukarıdaki kod derlenirse if-else deyimini koşullu işleçle değiştirin, derlemeyeceksiniz:

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

Koşullu işleç ve if-else ifadeleri aynı şeyi yaptığınızda aynı kavramsaldır, C koşullu işleçle daha da hızlıdır , çünkü C platformun montajına daha yakındır.


Sağladığınız orijinal kod için, koşullu işleç bir foreach döngüsünde kullanılır ve aralarındaki farkı görmek için işleri karıştırır. Bu yüzden aşağıdaki kodu öneriyorum:

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);
    }
}

ve aşağıdakiler, optimize edilmiş olan ve olmayan IL'nin iki versiyonudur. Uzun olduklarından, göstermek için bir görüntü kullanıyorum, sağ taraf optimize edilmiş:

(Resmi gerçek boyutunda görmek için tıklayın.) hSN6s.png

Her iki kod sürümünde, koşullu işlecin IL'si if-else deyiminden daha kısa görünüyor ve yine de sonunda üretilen makine kodundan şüphe var. Her iki yöntemin talimatları aşağıdadır ve eski görüntü optimize edilmemiştir, ikincisi optimize edilmiştir:

  • Optimize edilmemiş talimatlar: (Tam boyutlu resmi görmek için tıklayın.) ybhgM.png

  • Optimize edilmiş talimatlar: (Tam boyutlu resmi görmek için tıklayın.) 6kgzJ.png

İkincisinde, sarı blok yalnızca eğer çalıştırılırsa kod i<=0ve mavi blok ise i>0. Her iki yönerge sürümünde, if-else deyimi daha kısadır.

Farklı talimatlar için [ TÜFE'nin ] mutlaka aynı değildir. Mantıksal olarak, aynı talimat için, daha fazla talimat daha uzun döngüye mal olur. Ancak, talimat alma süresi ve boru / önbellek de dikkate alındıysa, gerçek toplam yürütme süresi işlemciye bağlıdır. İşlemci ayrıca dalları tahmin edebilir.

Modern işlemcilerin daha fazla çekirdeği var, işler bununla daha karmaşık olabilir. Intel işlemci kullanıcısıysanız, [ Intel® 64 ve IA-32 Mimari Optimizasyon Referans Kılavuzu ] 'na göz atmak isteyebilirsiniz .

Donanım tarafından uygulanan bir CLR olup olmadığını bilmiyorum, ancak evet ise, koşullu operatörle muhtemelen daha hızlı olursunuz, çünkü IL açıkça daha azdır.

Not: Tüm makine kodu x86'dır.


7

Jon Skeet'in yaptıklarını yaptım ve 1 iterasyon ve 1000 iterasyondan geçtim ve hem OP hem de Jon'dan farklı bir sonuç aldım. Benimki, üçlü sadece biraz daha hızlı. Tam kod aşağıdadır:

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();
    }

Programımın çıktısı:

If-Else için geçen süre: 00: 00: 00.0140543

Üçlü için geçen süre: 00: 00: 00.0136723

If-Else için geçen süre: 00: 00: 14.0167870

Üçlü için geçen süre: 00: 00: 13.9418520

Milisaniye cinsinden bir başka işlem:

If-Else için geçen süre: 20

Üçlü için geçen süre: 19

If-Else için geçen süre: 13854

Üçlü için geçen süre: 13610

Bu 64 bit XP'de çalışıyor ve hata ayıklamadan koştum.

Düzenle - x86'da çalışıyor:

X86 ile büyük bir fark var. Bu, daha önce olduğu gibi aynı xp 64 bit makinede hata ayıklamadan yapıldı, ancak x86 CPU'lar için oluşturuldu. Bu daha çok OP'lere benziyor.

If-Else için geçen süre: 18

Üçlü için geçen süre: 35

If-Else için geçen süre: 20512

Üçlü için geçen süre: 32673


Lütfen x86 üzerinde deneyebilir misiniz? Teşekkürler.
user1032613

@ user1032613 Hata ayıklama ve hata ayıklama olmadan çalıştırırsanız büyük bir fark olabileceğini düşünüyorum.
CodeCamper

@ user1032613 Az önce x86'dan gelen verilerle yazımı düzenledim. Üçlünün 2 kat daha yavaş olduğu daha çok sizinki gibi görünüyor.
Shaz

5

Oluşturulan birleştirici kodu hikayeyi anlatacak:

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

üretir:

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

Buna karşılık:

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

üretir:

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:

Üçlü Yani olabilir daha kısa ve daha hızlı basitçe nedeniyle daha az talimatları kullanarak olmalı ve atlar eğer sen doğru / yanlış arıyoruz. 1 ve 0 dışında bir değer kullanırsanız, if / else ile aynı kodu alırsınız, örneğin:

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

üretir:

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:

Hangi if / else ile aynıdır.


4

Hata ayıklama olmadan çalıştır ctrl + F5 hata ayıklayıcı hem ifs hem de üçlü önemli ölçüde yavaşlıyor gibi görünüyor ama üçlü operatörü çok daha yavaşlatıyor gibi görünüyor.

Aşağıdaki kodu çalıştırdığımda burada sonuçlarım var. Ben küçük milisaniye farkı max = max optimize ve kaldırma derleyici neden olduğunu düşünüyorum ama muhtemelen üçlü operatör için bu optimizasyon yapmıyor. Birisi derlemeyi kontrol edip onaylayabilirse harika olurdu.

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

kod

  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("#.####"));
        }

4

Oluşturulan IL'ye bakıldığında, if / else deyiminden daha az işlem vardır (kopyalama ve @ JonSkeet kodunu yapıştırma). Ancak, bu daha hızlı bir süreç olması gerektiği anlamına gelmez!

IL'deki farklılıkları özetlemek için, if / else yöntemi, C # kodunun okuduğu ile hemen hemen aynıdır (dalın içinde ekleme gerçekleştirme), koşullu kod ise yığına (değere bağlı olarak) 2 veya 3 yükler ve daha sonra koşulun dışındaki değere ekler.

Diğer fark kullanılan dallanma talimatıdır. İf / else yöntemi, ilk koşulun üstünden atlamak için bir brtrue (true olursa dal) ve if ifadesinin ilkinden atlamak için koşulsuz bir dal kullanır. Koşullu kod, muhtemelen daha yavaş bir karşılaştırma olabilecek bir brtrue yerine bir bgt (şundan büyükse dal) kullanır.

Ayrıca (şube tahminini yeni okuduktan sonra), şubenin daha küçük olması için bir performans cezası olabilir. Koşullu dalın dalda yalnızca 1 talimatı vardır ancak if / else 7 vardır. Bu aynı zamanda uzun ve int kullanımı arasında neden bir fark olduğunu açıklayacaktır, çünkü int değerine geçmek if / else dallarındaki komut sayısını 1 azaltır. (okumayı daha az yapar)


1

Aşağıdaki kodda / operatörü üçlü operatörden kabaca 1,4 kat daha hızlı görünüyorsa. Ancak, geçici bir değişken getirmenin üçlü operatörün çalışma süresini yaklaşık 1,4 kat azalttığını buldum:

Diğer / Diğer: 98 ms

Üçlü: 141 ms

Geçici değişkenli üçlü: 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);
        }
    }
}

0

Çok fazla harika cevap ama ilginç bir şey buldum, çok basit değişiklikler etki yaratıyor. Aşağıdaki değişikliği yaptıktan sonra if-else ve üçlü operatörü çalıştırmak aynı zamanı alacaktır.

satırın altına yazmak yerine

value +=  i > 0 ? 2 : 3;

Bunu kullandım,

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

Aşağıdaki cevaplardan biri de üçlü operatör yazmanın kötü yolunun ne olduğunu belirtmektedir.

Umarım bu hangisinin daha iyi olduğunu düşünmek yerine üçlü operatör yazmanıza yardımcı olur.

İç İçe Üçlü Operatör: İç içe üçlü operatör ve birden fazla blokun yürütülmesi aynı süreyi bulur.

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.