“As” ve nullable tiplerle performans sürprizleri


330

Ben sadece null edilebilir türleri ile ilgilenen Derinlik C # Bölüm 4 gözden geçiriyorum, ve ben yazmak için izin verir "as" operatörü kullanma hakkında bir bölüm ekliyorum:

object o = ...;
int? x = o as int?;
if (x.HasValue)
{
    ... // Use x.Value in here
}

Bunun gerçekten düzgün olduğunu ve C # 1 eşdeğeri üzerindeki performansı artırabileceğini düşündüm, "is" ve ardından bir alçı kullanarak - sonuçta, bu şekilde sadece bir kez dinamik tip kontrolü ve daha sonra basit bir değer kontrolü istemeliyiz .

Ancak durum böyle değil. Temelde bir nesne dizisi içindeki tüm tamsayıları toplayan aşağıdaki örnek bir test uygulaması ekledim - ancak dizi çok sayıda boş başvuru ve dize başvurusunun yanı sıra kutulu tamsayılar içeriyor. Karşılaştırma ölçütü, C # 1'de kullanmanız gereken kodu, "as" operatörünü kullanan kodu ve yalnızca bir LINQ çözümünü başlatması için ölçer. Şaşkınlığım için, C # 1 kodu bu durumda 20 kat daha hızlı - ve hatta LINQ kodu (dahil olan yineleyiciler göz önüne alındığında daha yavaş olmasını beklerdim) "as" kodunu atıyor.

isinstNull olabilecek türler için .NET uygulaması gerçekten yavaş mı? unbox.anySoruna neden olan ek mi? Bunun başka bir açıklaması var mı? Şu anda bunu performansa duyarlı durumlarda kullanmaya karşı bir uyarı eklemem gerekecek gibi geliyor ...

Sonuçlar:

Oyuncular: 10000000: 121
As: 10000000: 2211
LINQ: 10000000: 2143

Kod:

using System;
using System.Diagnostics;
using System.Linq;

class Test
{
    const int Size = 30000000;

    static void Main()
    {
        object[] values = new object[Size];
        for (int i = 0; i < Size - 2; i += 3)
        {
            values[i] = null;
            values[i+1] = "";
            values[i+2] = 1;
        }

        FindSumWithCast(values);
        FindSumWithAs(values);
        FindSumWithLinq(values);
    }

    static void FindSumWithCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            if (o is int)
            {
                int x = (int) o;
                sum += x;
            }
        }
        sw.Stop();
        Console.WriteLine("Cast: {0} : {1}", sum, 
                          (long) sw.ElapsedMilliseconds);
    }

    static void FindSumWithAs(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            int? x = o as int?;
            if (x.HasValue)
            {
                sum += x.Value;
            }
        }
        sw.Stop();
        Console.WriteLine("As: {0} : {1}", sum, 
                          (long) sw.ElapsedMilliseconds);
    }

    static void FindSumWithLinq(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = values.OfType<int>().Sum();
        sw.Stop();
        Console.WriteLine("LINQ: {0} : {1}", sum, 
                          (long) sw.ElapsedMilliseconds);
    }
}

8
Neden verilen koda bakmıyorsunuz? VS hata ayıklayıcı bile bunu gösterebilir.
Anton Tykhyy

2
Sadece merak ediyorum, CLR 4.0 ile de test yaptın mı?
Dirk Vollmar

1
@Anton: İyi bir nokta. Bir noktada yapacak (bu şu anda VS'de olmasa da) @divo: Evet ve her şeyden daha kötü. Ama sonra beta sürümünde, bu yüzden orada çok sayıda hata ayıklama kodu olabilir.
Jon Skeet

1
Bugün asnull olabilecek türlerde kullanabileceğinizi öğrendim . Diğer değer türlerinde kullanılamayacağı için ilginç. Aslında daha şaşırtıcı.
leppie

3
@Lepp değer türleri üzerinde çalışmaması mükemmel bir mantıklı. Bir düşünün, asbir türe döküm yapmaya çalışır ve başarısız olursa null değerini döndürür. Değer türlerini null olarak ayarlayamazsınız
Earlz

Yanıtlar:


209

Açıkçası JIT derleyicisinin ilk durumda üretebileceği makine kodu çok daha verimlidir. Gerçekten yardımcı olan bir kural, bir nesnenin sadece kutulu değerle aynı tipte bir değişkene kutudan çıkarılabilmesidir. Bu, JIT derleyicisinin çok verimli kod üretmesine izin verir, değer dönüşümleri dikkate alınmamalıdır.

olduğu nesnesi boş değildir ve beklenen türde değil, sürer ancak birkaç makine kodu talimatları eğer operatör testi kolay, sadece kontrol edilir. Oyuncular da kolaydır, JIT derleyicisi değer bitlerinin nesnedeki yerini bilir ve bunları doğrudan kullanır. Kopyalama veya dönüştürme işlemi yapılmaz, tüm makine kodu satıriçi olur ve yaklaşık bir düzine talimat alır. Boks yaygın olduğunda bunun .NET 1.0'da gerçekten verimli olması gerekiyordu.

İnt için yayınlanıyor mu? çok daha fazla iş gerektirir. Kutulu tamsayının değer temsili, öğesinin bellek düzeniyle uyumlu değil Nullable<int>. Bir dönüştürme gerekli ve olası kutulu numaralandırma türleri nedeniyle kod zor. JIT derleyicisi, işi yapmak için JIT_Unbox_Nullable adlı bir CLR yardımcı işlevine bir çağrı oluşturur. Bu, herhangi bir değer türü için genel amaçlı bir işlevdir, türleri kontrol etmek için çok sayıda kod vardır. Ve değer kopyalanır. Bu kod mscorwks.dll içinde kilitli olduğundan maliyeti tahmin etmek zor, ancak yüzlerce makine kodu talimatı büyük olasılıkla.

Linq OfType () genişletme yöntemi de is operatörünü ve kadroyu kullanır . Ancak bu tür bir tür için bir dökümdür. JIT derleyicisi, rastgele bir değer türüne bir döküm gerçekleştirebilen bir yardımcı işlev olan JIT_Unbox () çağrısı oluşturur. Daha Nullable<int>az işin gerekli olması gerektiği göz önüne alındığında, neden oyuncu kadar yavaş olduğu konusunda büyük bir açıklamam yok . Ben ngen.exe burada sorun neden şüpheli.


16
Tamam, ikna oldum. Sanırım, bir miras hiyerarşisine çıkma olasılıkları nedeniyle "pahalı" olduğunu düşünmeye alışkınım - ancak bir değer türü söz konusu olduğunda, bir hiyerarşi olasılığı yoktur, bu yüzden basit bir bitsel karşılaştırma olabilir . Hükümsüz dava için JIT kodu hala olsa JIT tarafından çok daha optimize edilebilir düşünüyorum.
Jon Skeet

26

Bana öyle geliyor ki, isinstnull edilebilir türlerde gerçekten yavaş. Yöntemde FindSumWithCastdeğiştim

if (o is int)

için

if (o is int?)

bu da yürütmeyi önemli ölçüde yavaşlatır. IL'de görebildiğim tek fark şu ki

isinst     [mscorlib]System.Int32

olarak değiştirildi

isinst     valuetype [mscorlib]System.Nullable`1<int32>

1
Bundan daha fazlası; "döküm" durumunda isinst, ardından hükümsüzlük testi ve ardından şartlı olarak bir unbox.any. Sıfırlanabilir durumda koşulsuz var unbox.any .
Jon Skeet

Evet, ikisini de çıkıyor isinstve unbox.anynull olabilecek türlerde daha yavaş.
Dirk Vollmar

@Jon: Cevabımın neden oyuncuya ihtiyaç duyulduğuna ilişkin gözden geçirebilirsiniz. (Bunun eski olduğunu biliyorum, ama sadece bu q'yu keşfettim ve CLC hakkında bildiklerimin 2c'sini sağlamam gerektiğini düşündüm).
Johannes Rudolph

22

Bu aslında Hans Passant'ın mükemmel cevabına bir Yorum olarak başladı, ancak çok uzun sürdü, bu yüzden buraya birkaç bit eklemek istiyorum:

İlk olarak, C # asoperatörü bir isinstIL talimatı yayar ( isoperatör de yapar ). (Bir başka ilginç talimat, castclassdoğrudan bir döküm yaptığınızda yayınlanır ve derleyici, çalışma zamanı denetiminin göz ardı edilemeyeceğini bilir.)

İşte isinst( ECMA 335 Bölüm III, 4.6 ):

Biçim: isinst typeTok

typeTok bir meta veri jetonu (a, typeref, typedefya da typespecarzu edilen sınıfı gösteren).

Eğer typeTok null olmayan bir değer türü veya zaman olarak yorumlanır genel parametre türüdür “kutulu” typeTok .

Eğer typeTok bir null türüdür Nullable<T>, bu “kutulu” olarak yorumlanırT

En önemlisi:

Gerçek tip (değil doğrulayıcı izlenen türü) ise obj olduğunu doğrulayıcı devredilemeyen-to tipi typeTok sonra isinstbaşarılı ve obj (aynı sonucu doğrulama olarak türünü izler iken) değişmeden döndürülür typeTok . Zorlamaların (§1.6) ve dönüşümlerin (§3.27) aksine, isinstasla bir nesnenin gerçek türünü değiştirmez ve nesne kimliğini korur (bkz. Bölüm I).

Yani, performans katili isinstbu durumda değil, ek olarak unbox.any. Hans'ın cevabından bu belli değildi, çünkü sadece JITed koduna baktı. Genel olarak, C # derleyicisi bir unbox.anya isinst T?( isinst Tya da ne zaman T, bir referans tipi olduğunda) çıkarır ).

Neden bunu yapıyor? isinst T?hiçbir zaman belirgin olan bir etkiye sahip değildir, yani geri dönersiniz T?. Bunun yerine, tüm bu talimatlar "boxed T"kutudan çıkarılabilecek bir talimatınızın olmasını sağlar T?. Bir gerçek elde etmek için T?, hala bizim "boxed T"için kutumuzu açmamız gerekiyor T?, bu yüzden derleyici bir unbox.anysonrasını yayınlıyor isinst. Bunu düşünürseniz, bu mantıklıdır çünkü "kutu formatı" T?sadece a'dır ve kutuyu "boxed T"yapmak castclassve isinstgerçekleştirmek tutarsız olur.

Hans'ın bulgusunu standarttan bazı bilgilerle yedeklemek , işte gidiyor:

(ECMA 335 Bölüm III, 4.33): unbox.any

Değer türünün kutulu biçimine uygulandığında, unbox.anykomut obj (tür O) içinde yer alan değeri ayıklar . (Bunu unboxizleyenle eşdeğerdir ldobj.) Bir referans türüne uygulandığında, unbox.anykomut castclasstypeTok ile aynı etkiye sahiptir.

(ECMA 335 Bölüm III, 4.32): unbox

Genellikle, unboxkutulu nesnenin içinde zaten bulunan değer türünün adresini hesaplar. Sıfırlanabilir değer türlerinin kutusu kaldırılırken bu yaklaşım mümkün değildir. Çünkü Nullable<T>değerler kutulu dönüştürülür Tskutu operasyonu sırasında, bir uygulama genellikle yeni üretim zorundadır Nullable<T>öbek üzerinde ve yeni ayrılan nesneye adresini hesaplamak.


En son alıntı yapılan cümlenin bir yazım hatası olabileceğini düşünüyorum; “... yığın üzerinde ...” “ yürütme yığınında ” olmamalı mı? Bazı yeni GC yığın örneğine geri dönme, orijinal sorunu neredeyse aynı yeni bir sorunla değiştiriyor gibi görünüyor.
Glenn Slayden

19

İlginç bir şekilde, operatör desteği hakkındaki geribildirimi dynamicdaha yavaş bir büyüklük sırası olarak Nullable<T>( bu erken teste benzer şekilde ) geçtim - çok benzer nedenlerden şüpheliyim.

Sevmeliyim Nullable<T>. Bir başka eğlenceli olan da null, JIT'in boş olmayan yapılar için lekelenmesine (ve kaldırılmasına rağmen) , aşağıdakileri için ödünç almasıdır Nullable<T>:

using System;
using System.Diagnostics;
static class Program {
    static void Main() { 
        // JIT
        TestUnrestricted<int>(1,5);
        TestUnrestricted<string>("abc",5);
        TestUnrestricted<int?>(1,5);
        TestNullable<int>(1, 5);

        const int LOOP = 100000000;
        Console.WriteLine(TestUnrestricted<int>(1, LOOP));
        Console.WriteLine(TestUnrestricted<string>("abc", LOOP));
        Console.WriteLine(TestUnrestricted<int?>(1, LOOP));
        Console.WriteLine(TestNullable<int>(1, LOOP));

    }
    static long TestUnrestricted<T>(T x, int loop) {
        Stopwatch watch = Stopwatch.StartNew();
        int count = 0;
        for (int i = 0; i < loop; i++) {
            if (x != null) count++;
        }
        watch.Stop();
        return watch.ElapsedMilliseconds;
    }
    static long TestNullable<T>(T? x, int loop) where T : struct {
        Stopwatch watch = Stopwatch.StartNew();
        int count = 0;
        for (int i = 0; i < loop; i++) {
            if (x != null) count++;
        }
        watch.Stop();
        return watch.ElapsedMilliseconds;
    }
}

Yowser. Bu gerçekten acı verici bir fark. Eek.
Jon Skeet

Başka iyi Tüm bunların çıktı takdirde, her iki benim orijinal kodu için uyarılar içermektedir götürdü oluyor ve bu :)
Jon Skeet

Bunun eski bir soru olduğunu biliyorum, ama " nullNULL yapılamayan yapılar için JIT noktaları (ve kaldırır)" ile ne demek istediğinizi açıklayabilir misiniz ? Çalışma nullzamanı sırasında varsayılan bir değerle veya başka bir şeyle değiştirildiğini mi kastediyorsunuz ?
Justin Morgan

2
@Justin - jenerik bir parametre ( Tvb.) Herhangi bir sayıda permütasyon ile çalışma zamanında jenerik bir yöntem kullanılabilir . Yığın vb gereksinimleri argümanlara (yerel için yığın alanı vb.) Bağlıdır, bu nedenle bir değer türü içeren herhangi bir benzersiz permütasyon için bir JIT alırsınız. Ancak, referansların hepsi aynı boyuttadır, bu nedenle bir JIT paylaşın. Değer başına tür JIT'i yaparken, bazı belirgin senaryoları kontrol edebilir ve imkansız null gibi şeyler nedeniyle erişilemez kodları çıkarmaya çalışır . Mükemmel değil, not edin. Ayrıca, yukarıdaki için AOT görmezden geliyorum.
Marc Gravell

Sınırlandırılmamış nullable testi hala 2.5 derece daha yavaştır, ancak countdeğişkeni kullanmadığınızda bazı optimizasyonlar devam eder . Ekleme Console.Write(count.ToString()+" ");sonrasında watch.Stop();her iki durumda da bir büyüklük sırasına altında sadece diğer testler yavaşlatır, ama sınırsız null testi değişmez. nullOrijinal kodun diğer testler için gerçekten boş kontrol ve artış yapmadığını doğruladığında, geçerken vakaları test ettiğinizde de değişiklikler olduğunu unutmayın . Linqpad
Mark Hurd

12

Bu yukarıdaki FindSumWithAsAndHas sonucudur: alternatif metin

Bu FindSumWithCast sonucudur: alternatif metin

Bulgular:

  • Kullanarak as, önce bir nesnenin Int32 örneği olup olmadığını test eder; Kaputun altında isinst Int32(elle yazılmış koda benzer: if (o int)). Ve kullanarak as, koşulsuz olarak nesnenin kutusundan çıkar. Ve bir özelliği aramak için gerçek bir performans katilidir (hala kaputun altında bir işlevdir), IL_0027

  • Cast kullanarak, önce nesnenin bir int if (o is int); Bu kaputun altında isinst Int32. İnt örneğiyse, IL_002D değerinin güvenli bir şekilde kutusundan çıkabilirsiniz

Basitçe söylemek gerekirse, bu asyaklaşım kullanmanın sözde kodu :

int? x;

(x.HasValue, x.Value) = (o isinst Int32, o unbox Int32)

if (x.HasValue)
    sum += x.Value;    

Ve bu, cast yaklaşımını kullanmanın sahte kodudur:

if (o isinst Int32)
    sum += (o unbox Int32)

Bu nedenle, cast ( (int)a[i], sözdizimi bir döküm gibi görünüyor, ancak aslında kutudan çıkma, yayınlama ve kutudan çıkarma aynı sözdizimini paylaşıyor, bir dahaki sefere doğru terminoloji ile bilgiç olacağım) yaklaşımı gerçekten daha hızlı, sadece bir değerin kutusunu kaldırmanız gerekiyor bir nesne kesinlikle bir int. Aynı şey bir asyaklaşım kullanarak söylenemez .


11

Bu yanıtı güncel tutmak için, bu sayfadaki tartışmanın çoğunun şimdi en iyi IL kodunu üreten ince bir sözdizimini destekleyen C # 7.1 ve .NET 4.7 ile tartışmaya başladığını belirtmek gerekir .

OP'nin orijinal örneği ...

object o = ...;
int? x = o as int?;
if (x.HasValue)
{
    // ...use x.Value in here
}

basitçe ...

if (o is int x)
{
    // ...use x in here
}

Sana bir .NET yazarken yeni sözdizimi için bir ortak kullanım olduğunu bulduk değer türünü (yani structiçinde C # ) olduğu uygular IEquatable<MyStruct>(çoğu gerektiği gibi). Güçlü yazılan Equals(MyStruct other)yöntemi uyguladıktan sonra , şimdi türlenmemiş Equals(Object obj)geçersiz kılmayı (devralınan Object) aşağıdaki şekilde zarif bir şekilde yeniden yönlendirebilirsiniz :

public override bool Equals(Object obj) => obj is MyStruct o && Equals(o);

 


Ek:Release yapı , IL (sırasıyla) Bu yanıt, yukarıda gösterilen birinci iki örnek fonksiyonlar için kod burada verilmektedir. Yeni sözdizimi için IL kodu gerçekten 1 bayt daha küçük olsa da, çoğunlukla sıfır çağrı yaparak (ikiye karşı) ve unboxmümkün olduğunca işlemden kaçınarak büyük kazanır .

// static void test1(Object o, ref int y)
// {
//     int? x = o as int?;
//     if (x.HasValue)
//         y = x.Value;
// }

[0] valuetype [mscorlib]Nullable`1<int32> x
        ldarg.0
        isinst [mscorlib]Nullable`1<int32>
        unbox.any [mscorlib]Nullable`1<int32>
        stloc.0
        ldloca.s x
        call instance bool [mscorlib]Nullable`1<int32>::get_HasValue()
        brfalse.s L_001e
        ldarg.1
        ldloca.s x
        call instance !0 [mscorlib]Nullable`1<int32>::get_Value()
        stind.i4
L_001e: ret

// static void test2(Object o, ref int y)
// {
//     if (o is int x)
//         y = x;
// }

[0] int32 x,
[1] object obj2
        ldarg.0
        stloc.1
        ldloc.1
        isinst int32
        ldnull
        cgt.un
        dup
        brtrue.s L_0011
        ldc.i4.0
        br.s L_0017
L_0011: ldloc.1
        unbox.any int32
L_0017: stloc.0
        brfalse.s L_001d
        ldarg.1
        ldloc.0
        stind.i4
L_001d: ret

Daha önce mevcut olan seçenekleri aşan yeni C # 7 sözdiziminin performansı hakkındaki açıklamamı kanıtlayan diğer testler için buraya bakın (özellikle 'D' örneği).


9

Daha fazla profil oluşturma:

using System;
using System.Diagnostics;

class Program
{
    const int Size = 30000000;

    static void Main(string[] args)
    {
        object[] values = new object[Size];
        for (int i = 0; i < Size - 2; i += 3)
        {
            values[i] = null;
            values[i + 1] = "";
            values[i + 2] = 1;
        }

        FindSumWithIsThenCast(values);

        FindSumWithAsThenHasThenValue(values);
        FindSumWithAsThenHasThenCast(values);

        FindSumWithManualAs(values);
        FindSumWithAsThenManualHasThenValue(values);



        Console.ReadLine();
    }

    static void FindSumWithIsThenCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            if (o is int)
            {
                int x = (int)o;
                sum += x;
            }
        }
        sw.Stop();
        Console.WriteLine("Is then Cast: {0} : {1}", sum,
                            (long)sw.ElapsedMilliseconds);
    }

    static void FindSumWithAsThenHasThenValue(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            int? x = o as int?;

            if (x.HasValue)
            {
                sum += x.Value;
            }
        }
        sw.Stop();
        Console.WriteLine("As then Has then Value: {0} : {1}", sum,
                            (long)sw.ElapsedMilliseconds);
    }

    static void FindSumWithAsThenHasThenCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            int? x = o as int?;

            if (x.HasValue)
            {
                sum += (int)o;
            }
        }
        sw.Stop();
        Console.WriteLine("As then Has then Cast: {0} : {1}", sum,
                            (long)sw.ElapsedMilliseconds);
    }

    static void FindSumWithManualAs(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            bool hasValue = o is int;
            int x = hasValue ? (int)o : 0;

            if (hasValue)
            {
                sum += x;
            }
        }
        sw.Stop();
        Console.WriteLine("Manual As: {0} : {1}", sum,
                            (long)sw.ElapsedMilliseconds);
    }

    static void FindSumWithAsThenManualHasThenValue(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            int? x = o as int?;

            if (o is int)
            {
                sum += x.Value;
            }
        }
        sw.Stop();
        Console.WriteLine("As then Manual Has then Value: {0} : {1}", sum,
                            (long)sw.ElapsedMilliseconds);
    }

}

Çıktı:

Is then Cast: 10000000 : 303
As then Has then Value: 10000000 : 3524
As then Has then Cast: 10000000 : 3272
Manual As: 10000000 : 395
As then Manual Has then Value: 10000000 : 3282

Bu rakamlardan ne çıkarabiliriz?

  • İlk olarak, bir-o-döküm yaklaşımı önemli ölçüde daha hızlı olduğu kadar yaklaşımı. 303 vs 3524
  • İkincisi, Değer dökümden biraz daha yavaştır. 3524 vs 3272
  • Üçüncüsü, .HasValue, manuel has (yani is kullanarak ) kullanmaktan çok az . 3524 vs 3282
  • Dördüncü olarak, bir elma-to-elma karşılaştırma (yani hem simüle HasValue ait atama ve simüle Değer birlikte olur dönüştürme) arasındaki yapıyor olarak simüle ve gerçek bir yaklaşımla, biz görebilirsiniz olarak simüle önemli ölçüde daha hızlı daha hala gerçek . 395 vs 3524
  • Son olarak, ilk ve dördüncü sonuca dayanarak, uygulama olarak yanlış bir şey var ^ _ ^

8

Denemek için zamanım yok, ama sahip olmak isteyebilirsiniz:

foreach (object o in values)
        {
            int? x = o as int?;

gibi

int? x;
foreach (object o in values)
        {
            x = o as int?;

Her seferinde sorunu tamamen açıklamayacak, ancak katkıda bulunabilecek yeni bir nesne oluşturuyorsunuz.


1
Hayır, koştum ve marjinal olarak daha yavaş.
Henk Holterman

2
Bir değişkenin farklı bir yerde bildirilmesi, üretilen kodu yalnızca değişken yakalandığında (bu noktada gerçek anlambilimi etkiler) önemli ölçüde etkiler. int?Yığında yeni bir nesne oluşturmadığını, ancak kesinlikle yığında yeni bir örneğini oluşturduğunu unutmayın unbox.any. Sorunun bu olduğundan şüpheliyim - tahminim, el yapımı IL'nin her iki seçeneği de yenebileceği ... ancak JIT'in is / cast durumunu tanımak ve sadece bir kez kontrol etmek için optimize edilmiş olması da mümkün.
Jon Skeet

Oyuncunun muhtemelen uzun zamandır olduğu için optimize edildiğini düşünüyordum.
James Black

1
/ cast, optimizasyon için kolay bir hedeftir, sinir bozucu bir şekilde yaygın bir deyimdir.
Anton Tykhyy

4
Yöntemin yığın çerçevesi oluşturulduğunda yığına yerel değişkenler ayrılır, böylece yöntemdeki değişkeni bildirdiğinizde hiçbir fark olmaz. (Tabii ki kapanışa girmedikçe, ama burada durum böyle değil.)
Guffa

8

Tam tip kontrol yapısını denedim

typeof(int) == item.GetType(), item is intsürüm kadar hızlı çalışır ve her zaman sayıyı döndürür (vurgu: Nullable<int>diziye bir yazmış olsanız bile , kullanmanız gerekir typeof(int)). Ayrıca null != itemburada ek bir kontrol yapmanız gerekir .

ancak

typeof(int?) == item.GetType()hızlı kalır (aksine item is int?), ancak her zaman false değerini döndürür.

RuntimeTypeHandle kullandığından, typeof-construct tam tip kontrol için en hızlı yol benim gözümde. Bu durumda kesin türler null değeriyle eşleşmediği için, tahminim, is/asburada aslında Null edilebilir türün bir örneği olmasını sağlamak için burada ek ağırlıklar yapmak zorundayım.

Ve dürüst olmak gerekirse: is Nullable<xxx> plus HasValueSizi ne satın alıyor? Hiçbir şey değil. Her zaman doğrudan temel (değer) türüne gidebilirsiniz (bu durumda). Değeri alırsınız veya "hayır, istediğiniz türde bir örneği değil". (int?)nullDiziye yazsanız bile , tür denetimi yanlış döndürür.


İlginç ... kullanma fikri "olarak" + HasValue (değil ise sadece tip onay performans gösterdiği artı HasValue, not) bir kez yerine iki kez arasında. Tek bir adımda "kontrol et ve kutuyu kaldır" işlemini yapıyor. Bu daha hızlı olmalı gibi hissettiriyor ... ama açıkça değil. Son cümle ile ne demek istediğinizden emin değilim, ama kutulu diye bir şey yok int?- bir int?değeri kutlarsanız kutulu int veya nullreferans olarak sonuçlanır .
Jon Skeet

7
using System;
using System.Diagnostics;
using System.Linq;

class Test
{
    const int Size = 30000000;

    static void Main()
    {
        object[] values = new object[Size];
        for (int i = 0; i < Size - 2; i += 3)
        {
            values[i] = null;
            values[i + 1] = "";
            values[i + 2] = 1;
        }

        FindSumWithCast(values);
        FindSumWithAsAndHas(values);
        FindSumWithAsAndIs(values);


        FindSumWithIsThenAs(values);
        FindSumWithIsThenConvert(values);

        FindSumWithLinq(values);



        Console.ReadLine();
    }

    static void FindSumWithCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            if (o is int)
            {
                int x = (int)o;
                sum += x;
            }
        }
        sw.Stop();
        Console.WriteLine("Cast: {0} : {1}", sum,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindSumWithAsAndHas(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            int? x = o as int?;
            if (x.HasValue)
            {
                sum += x.Value;
            }
        }
        sw.Stop();
        Console.WriteLine("As and Has: {0} : {1}", sum,
                          (long)sw.ElapsedMilliseconds);
    }


    static void FindSumWithAsAndIs(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            int? x = o as int?;
            if (o is int)
            {
                sum += x.Value;
            }
        }
        sw.Stop();
        Console.WriteLine("As and Is: {0} : {1}", sum,
                          (long)sw.ElapsedMilliseconds);
    }







    static void FindSumWithIsThenAs(object[] values)
    {
        // Apple-to-apple comparison with Cast routine above.
        // Using the similar steps in Cast routine above,
        // the AS here cannot be slower than Linq.



        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {

            if (o is int)
            {
                int? x = o as int?;
                sum += x.Value;
            }
        }
        sw.Stop();
        Console.WriteLine("Is then As: {0} : {1}", sum,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindSumWithIsThenConvert(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {            
            if (o is int)
            {
                int x = Convert.ToInt32(o);
                sum += x;
            }
        }
        sw.Stop();
        Console.WriteLine("Is then Convert: {0} : {1}", sum,
                          (long)sw.ElapsedMilliseconds);
    }



    static void FindSumWithLinq(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = values.OfType<int>().Sum();
        sw.Stop();
        Console.WriteLine("LINQ: {0} : {1}", sum,
                          (long)sw.ElapsedMilliseconds);
    }
}

Çıktılar:

Cast: 10000000 : 456
As and Has: 10000000 : 2103
As and Is: 10000000 : 2029
Is then As: 10000000 : 1376
Is then Convert: 10000000 : 566
LINQ: 10000000 : 1811

[DÜZENLEME: 2010-06-19]

Not: Önceki test VS içinde, yapılandırma hata ayıklaması, VS2009 kullanılarak, Core i7 (şirket geliştirme makinesi) kullanılarak yapıldı.

VS2010 kullanılarak Core 2 Duo kullanılarak makinemde aşağıdakiler yapıldı

Inside VS, Configuration: Debug

Cast: 10000000 : 309
As and Has: 10000000 : 3322
As and Is: 10000000 : 3249
Is then As: 10000000 : 1926
Is then Convert: 10000000 : 410
LINQ: 10000000 : 2018




Outside VS, Configuration: Debug

Cast: 10000000 : 303
As and Has: 10000000 : 3314
As and Is: 10000000 : 3230
Is then As: 10000000 : 1942
Is then Convert: 10000000 : 418
LINQ: 10000000 : 1944




Inside VS, Configuration: Release

Cast: 10000000 : 305
As and Has: 10000000 : 3327
As and Is: 10000000 : 3265
Is then As: 10000000 : 1942
Is then Convert: 10000000 : 414
LINQ: 10000000 : 1932




Outside VS, Configuration: Release

Cast: 10000000 : 301
As and Has: 10000000 : 3274
As and Is: 10000000 : 3240
Is then As: 10000000 : 1904
Is then Convert: 10000000 : 414
LINQ: 10000000 : 1936

Hangi çerçeve sürümünü kullanıyorsunuz? Netbook'umdaki sonuçlar (.NET 4RC kullanarak) daha da dramatik - As kullanan sürümler çok sonuçlarınızdan daha kötü. Belki de .NET 4 RTM için geliştirdiler? Hala daha hızlı olabileceğini düşünüyorum ...
Jon Skeet

@Michael: Optimize edilmemiş bir yapı mı çalıştırıyorsunuz, yoksa hata ayıklayıcıda mı çalışıyorsunuz?
Jon Skeet

@Jon: hata ayıklayıcı altında optimize edilmemiş yapı
Michael Buen

1
@Michael: Sağ - Bir hata ayıklayıcı altında performans sonuçlarını büyük ölçüde alakasız olarak görme eğilimindeyim :)
Jon Skeet

@Jon: Hata ayıklayıcı altındaysa, VS içindeki anlam; evet önceki kriter hata ayıklayıcı altında yapıldı. VS içinde ve dışında tekrar kıyaslama yapıyorum ve hata ayıklama olarak derlendi ve sürüm olarak derlendi. Düzenlemeyi kontrol et
Michael Buen
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.