"के रूप में" और अशक्त प्रकार के साथ प्रदर्शन आश्चर्य


330

मैं केवल गहराई में C # के अध्याय 4 को संशोधित कर रहा हूं, जो अशक्त प्रकारों से संबंधित है, और मैं "के रूप में" ऑपरेटर का उपयोग करने के बारे में एक खंड जोड़ रहा हूं, जो आपको लिखने की अनुमति देता है:

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

मैंने सोचा था कि यह वास्तव में साफ-सुथरा था, और यह C # 1 के बराबर प्रदर्शन में सुधार कर सकता था, "का उपयोग करके" कास्ट द्वारा पीछा किया जाता है - आखिरकार, इस तरह से हमें केवल एक बार गतिशील प्रकार की जांच के लिए पूछना होगा, और फिर एक साधारण मूल्य की जांच ।

हालाँकि ऐसा प्रतीत नहीं होता है। मैंने नीचे एक नमूना परीक्षण ऐप शामिल किया है, जो मूल रूप से एक ऑब्जेक्ट सरणी के भीतर सभी पूर्णांकों को sums करता है - लेकिन सरणी में बहुत सारे शून्य संदर्भ और स्ट्रिंग संदर्भ के साथ-साथ बॉक्सिंग पूर्णांक शामिल हैं। बेंचमार्क उस कोड को मापता है जिसका उपयोग आपको C # 1 में करना होगा, "as" ऑपरेटर का उपयोग कर कोड, और सिर्फ एक LINK समाधान के लिए। मेरे विस्मय के लिए, C # 1 कोड इस मामले में 20 गुना तेज है - और यहां तक ​​कि LINQ कोड (जो कि मुझे धीमी होने की उम्मीद थी, इसमें शामिल होने वाले को देखते हुए) "कोड" के रूप में धड़कता है।

क्या isinstnullable प्रकारों के लिए .NET कार्यान्वयन वास्तव में धीमा है? क्या यह अतिरिक्त unbox.anyहै जो समस्या का कारण बनता है? क्या इसके लिए कोई और स्पष्टीकरण है? फिलहाल ऐसा लगता है कि मुझे प्रदर्शन संवेदनशील स्थितियों में इसका उपयोग करने के खिलाफ चेतावनी शामिल करना है ...

परिणाम:

कास्ट: 10000000: 121
अस: 10000000: 2211
LINQ: 10000000: 2143

कोड:

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
क्यों नहीं लगे कोड पर नजर? यहां तक ​​कि वीएस डिबगर भी इसे दिखा सकते हैं।
एंटोन टायखी

2
मैं बस उत्सुक हूं, क्या आपने सीएलआर 4.0 के साथ भी परीक्षण किया है?
डर्क वोल्मार

1
@ एटन: अच्छी बात है। कुछ बिंदु पर करेंगे (हालांकि यह इस समय वीएस में नहीं है :) @ डिवो: हाँ, और यह सभी दौर में बदतर है। लेकिन फिर वह बीटा में है, इसलिए वहां बहुत डिबगिंग कोड हो सकता है।
जॉन स्कीट

1
आज मैंने सीखा कि आप asअशक्त प्रकारों पर उपयोग कर सकते हैं । दिलचस्प है, क्योंकि इसका उपयोग अन्य मूल्य प्रकारों पर नहीं किया जा सकता है। दरअसल, अधिक आश्चर्य की बात है।
लेप्पी

3
@ इसे टाइप प्रकारों पर काम नहीं करने के लिए यह सही समझ में आता है। इसके बारे में सोचो, asएक प्रकार के लिए कास्ट करने का प्रयास करता है और अगर यह विफल हो जाता है तो यह अशक्त हो जाता है। आप मान प्रकार को अशक्त करने के लिए सेट नहीं कर सकते हैं
Earlz

जवाबों:


209

स्पष्ट रूप से मशीन कोड जेआईटी संकलक पहले मामले के लिए उत्पन्न कर सकता है बहुत अधिक कुशल है। एक नियम जो वास्तव में मदद करता है, वह यह है कि एक वस्तु को केवल एक चर के लिए अनबॉक्स किया जा सकता है, जिसमें उसी तरह का बॉक्स मूल्य है। यह जेआईटी संकलक को बहुत कुशल कोड उत्पन्न करने की अनुमति देता है, किसी भी मूल्य रूपांतरण पर विचार नहीं किया जाता है।

है ऑपरेटर परीक्षण आसान है, बस अगर वस्तु अशक्त नहीं है और उम्मीद प्रकार का है, लगता है लेकिन कुछ मशीन कोड निर्देश जाँच कर रहा है। कास्ट भी आसान है, जेआईटी कंपाइलर ऑब्जेक्ट में मूल्य बिट्स का स्थान जानता है और उन्हें सीधे उपयोग करता है। कोई प्रतिलिपि या रूपांतरण नहीं होता है, सभी मशीन कोड इनलाइन होते हैं और एक दर्जन निर्देशों के बारे में बताते हैं। जब बॉक्सिंग सामान्य था, तो यह .NET 1.0 में वास्तव में कुशल होना चाहिए।

इंट करने के लिए कास्टिंग? बहुत अधिक काम लेता है। बॉक्सिंग पूर्णांक का मान प्रतिनिधित्व मेमोरी लेआउट के अनुरूप नहीं है Nullable<int>। एक रूपांतरण की आवश्यकता होती है और कोड संभव बॉक्सिंग एनम प्रकार के कारण मुश्किल है। JIT कंपाइलर JIT_Unbox_Nullable नामक CLR हेल्पर फ़ंक्शन को कॉल करता है ताकि वह काम कर सके। यह किसी भी प्रकार के मूल्य के लिए एक सामान्य उद्देश्य फ़ंक्शन है, बहुत सारे कोड प्रकारों की जांच करने के लिए। और मूल्य की नकल की जाती है। लागत का अनुमान लगाना मुश्किल है क्योंकि यह कोड mscorwks.dll के अंदर बंद है, लेकिन मशीन कोड के सैकड़ों निर्देश की संभावना है।

LinTType () एक्सटेंशन विधि भी ऑपरेटर और कलाकारों का उपयोग करता है । हालांकि यह एक सामान्य प्रकार का एक कलाकार है। JIT संकलक एक हेल्पर फ़ंक्शन, JIT_Unbox () के लिए एक कॉल उत्पन्न करता है जो एक कलाकारों को एक मनमाना मूल्य प्रकार का प्रदर्शन कर सकता है। मेरे पास इस बात की बहुत व्याख्या नहीं है कि यह कलाकारों के लिए उतना ही धीमा क्यों है, यह Nullable<int>देखते हुए कि कम काम जरूरी है। मुझे संदेह है कि यहाँ ngen.exe परेशानी पैदा कर सकती है।


16
ठीक है, मैं आश्वस्त हूं। मुझे लगता है कि मैं "के बारे में सोच रहा हूँ" एक विरासत पदानुक्रम चलने की संभावनाओं के कारण संभावित रूप से महंगा है - लेकिन एक मूल्य प्रकार के मामले में, एक पदानुक्रम की कोई संभावना नहीं है, इसलिए यह एक साधारण बिटवाइर तुलना हो सकती है । मुझे अभी भी लगता है कि अशक्त मामले के लिए JIT कोड JIT द्वारा अनुकूलित किया जा सकता है, हालांकि यह बहुत अधिक भारी है।
जॉन स्कीट

26

यह मुझे लगता है कि isinstवास्तव में अशक्त प्रकारों पर धीमा है। विधि में FindSumWithCastमैं बदल गया

if (o is int)

सेवा

if (o is int?)

जो निष्पादन को भी काफी धीमा कर देता है। IL में एकमात्र अंतर मैं देख सकता हूं

isinst     [mscorlib]System.Int32

में बदल जाता है

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

1
यह उससे अधिक है; "डाली" मामले में isinstतुच्छता के लिए एक परीक्षण के बाद और फिर सशर्त एक unbox.any। अशक्त मामले में एक बिना शर्त है unbox.any
जॉन स्कीट

हां, दोनों को बाहर कर दिया जाता है isinstऔर unbox.anyअशक्त प्रकारों पर धीमी होती है।
डर्क वोल्मार

@Jon: आप मेरे जवाब की समीक्षा कर सकते हैं कि कलाकारों की आवश्यकता क्यों है। (मुझे पता है कि यह पुराना है, लेकिन मैंने अभी इस q की खोज की और सोचा कि मुझे अपने 2c प्रदान करना चाहिए जो मुझे सीएलआर के बारे में पता है)।
जोहान्स रुडोल्फ

22

यह मूल रूप से हंस पैसेंट के उत्कृष्ट उत्तर के लिए एक टिप्पणी के रूप में शुरू हुआ था, लेकिन यह बहुत लंबा हो गया इसलिए मैं यहां कुछ बिट्स जोड़ना चाहता हूं:

सबसे पहले, सी # asऑपरेटर एक isinstआईएल निर्देश (ताकि isऑपरेटर करता है ) का उत्सर्जन करेगा । (एक और दिलचस्प निर्देश है castclass, जब आप डायरेक्ट कास्ट करते हैं तब उत्सर्जित होता है और कंपाइलर जानता है कि रनटाइम चेकिंग को ommited नहीं किया जा सकता है।)

यहाँ क्या isinstहै ( ECMA 335 विभाजन III, 4.6 ):

स्वरूप: आइंस्ट टाइपटॉक

टाइपटॉक एक मेटाडेटा टोकन (ए typeref, typedefया typespec) है, जो वांछित वर्ग को दर्शाता है।

अगर टाइपटॉक एक गैर-शून्य मान प्रकार या सामान्य पैरामीटर प्रकार है, तो इसे "बॉक्सिंग" टाइपटॉक के रूप में व्याख्या की जाती है

अगर टाइपटॉक एक अशक्त प्रकार है Nullable<T>, तो इसकी व्याख्या "बॉक्सिंग" के रूप में की जाती है।T

सबसे महत्वपूर्ण बात:

यदि obj का वास्तविक प्रकार (सत्यापनकर्ता ट्रैक किया हुआ प्रकार नहीं) है, तो सत्यापनकर्ता-असाइन करने योग्य- प्रकार typeTok है, तब isinstसफल होता है और obj ( परिणाम के रूप में ) अपरिवर्तित लौटा दिया जाता है, जबकि सत्यापन टाइप टाइप के रूप में इसके प्रकार को ट्रैक करता हैजबरदस्ती (§1.6) और रूपांतरण (73.27) के विपरीत, isinstकभी भी वस्तु का वास्तविक प्रकार नहीं बदलता है और वस्तु पहचान को संरक्षित करता है (विभाजन I देखें)।

इसलिए, प्रदर्शन हत्यारा isinstइस मामले में नहीं है, लेकिन अतिरिक्त unbox.any। यह हंस के उत्तर से स्पष्ट नहीं था, क्योंकि वह केवल JITed कोड को देखता था। सामान्य तौर पर, C # संकलक एक का उत्सर्जन करेगाunbox.any बाद एक काisinst T? (लेकिन यदि आप एक संदर्भ प्रकार है isinst T, तो इसे छोड़ देंगे T

वह ऐसा क्यों करता है? isinst T?कभी भी ऐसा प्रभाव नहीं पड़ता जो स्पष्ट होता, यानी आपको वापस मिल जाता है T?। इसके बजाय, ये सभी निर्देश यह सुनिश्चित करते हैं कि आपके पास एक है "boxed T"जिसे अनबॉक्स किया जा सकता है T?। वास्तविक प्राप्त करने के लिए T?, हमें अभी भी अपने को अनबॉक्स करने की आवश्यकता "boxed T"है T?, यही वजह है कि कंपाइलर एक के unbox.anyबाद निकलता है isinst। यदि आप इसके बारे में सोचते हैं, तो यह समझ में आता है क्योंकि "बॉक्स प्रारूप" के लिए T?सिर्फ एक "boxed T"और बनाना castclassऔर isinstप्रदर्शन करना असंगत होगा।

मानक की कुछ जानकारी के साथ हंस का समर्थन करना , यहाँ यह जाता है:

(ECMA 335 विभाजन III, 4.33): unbox.any

जब एक मूल्य प्रकार के बॉक्सिंग फॉर्म पर लागू किया जाता है, तो unbox.anyनिर्देश ओब्ज (प्रकार के O) में निहित मूल्य को निकालता है । (यह unboxइसके बाद के बराबर है ldobj।) जब संदर्भ प्रकार पर लागू किया जाता है, तो unbox.anyनिर्देश का castclassटाइपटॉक के समान प्रभाव होता है।

(ECMA 335 विभाजन III, 4.32): unbox

आमतौर पर, unboxबस बॉक्स प्रकार के अंदर मौजूद मूल्य प्रकार के पते की गणना करता है। अशक्त मान प्रकारों को अनबॉक्स करने पर यह दृष्टिकोण संभव नहीं है। क्योंकि बॉक्स ऑपरेशन के दौरान Nullable<T>मूल्यों को बॉक्सिंग में बदल दिया जाता है Ts, एक कार्यान्वयन को अक्सर Nullable<T>ढेर पर एक नया निर्माण करना चाहिए और नए आवंटित ऑब्जेक्ट के पते की गणना करनी चाहिए ।


मुझे लगता है कि बहुत अंतिम उद्धृत वाक्य में टाइपो हो सकता है; निष्पादन ढेर पर "... ढेर पर" नहीं होना चाहिए ? कुछ नए जीसी हीप उदाहरण में वापस unboxing की तरह लगता है एक लगभग समान नए के लिए मूल समस्या स्वैप।
ग्लेन स्लेडेन

19

दिलचस्प बात यह है कि, मैं इस शुरुआती परीक्षण के समान ( dynamicऑर्डर के लिए Nullable<T>) धीमी गति से चलने के माध्यम से ऑपरेटर समर्थन के बारे में प्रतिक्रिया पर पारित हुआ ) के - मुझे बहुत समान कारणों से संदेह है।

प्यार करना होगा Nullable<T>। एक और मजेदार बात यह है कि भले ही nullगैर-अशक्त संरचनाओं के लिए जेआईटी स्पॉट (और हटाए ) गए हों, लेकिन यह इसके लिए इसे नीचे देता है 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। यह वास्तव में दर्दनाक अंतर है। EEK।
जॉन स्कीट

अगर इस सब से अच्छा कोई दूसरा नहीं निकला है, तो इसने मुझे मेरे मूल कोड और इस :) के लिए चेतावनी को शामिल करने का नेतृत्व किया :)
जॉन स्कीट

मुझे पता है कि यह एक पुराना प्रश्न है, लेकिन क्या आप बता सकते हैं कि "JIT स्पॉट्स (और हटाए जाने वाले) nullगैर-अशक्त संरचनाओं के लिए" क्या मतलब है ? क्या आपका मतलब है कि यह nullडिफ़ॉल्ट मान या रनटाइम के दौरान कुछ के साथ बदलता है?
जस्टिन मॉर्गन

2
@ जस्टिन - जेनेरिक-मापदंडों ( Tआदि) के किसी भी क्रमपरिवर्तन के साथ एक सामान्य विधि को रनटाइम पर उपयोग किया जा सकता है । स्टैक आदि की आवश्यकताएं आर्ग्स (स्थानीय, आदि के लिए स्टैक स्पेस की मात्रा) पर निर्भर करती हैं, इसलिए आपको मूल्य प्रकार से जुड़े किसी भी अनूठे क्रमचय के लिए एक जेआईटी मिलता है। हालाँकि, संदर्भ सभी एक ही आकार के हैं इसलिए एक JIT साझा करें। प्रति-मूल्य-प्रकार JIT करते समय, यह कुछ स्पष्ट परिदृश्यों की जांच कर सकता है, और असंभव नल जैसी चीजों के कारण अनुपयोगी कोड को बाहर करने की कोशिश करता है। यह सही नहीं है, ध्यान दें। इसके अलावा, मैं उपरोक्त के लिए एओटी की अनदेखी कर रहा हूं।
मार्क Gravell

अप्रतिबंधित अशक्त परीक्षण अभी भी परिमाण धीमी के 2.5 आदेश हैं, लेकिन जब आप countचर का उपयोग नहीं करते हैं तो कुछ अनुकूलन चल रहा है । दोनों मामलों में जोड़ने के Console.Write(count.ToString()+" ");बाद watch.Stop();केवल परिमाण के एक आदेश के तहत अन्य परीक्षणों को धीमा कर देता है, लेकिन अप्रतिबंधित अशक्त परीक्षण नहीं बदला जाता है। ध्यान दें कि जब आप पास किए गए मामलों का परीक्षण nullकरते हैं तो मूल कोड की पुष्टि करते हुए अन्य परीक्षणों के लिए वास्तव में अशक्त जांच और वेतन वृद्धि नहीं होती है। लाइनपैड
मार्क हर्ड

12

यह ऊपर FindSumWithAsAndHas का परिणाम है: वैकल्पिक शब्द

यह FindSumWithCast का परिणाम है: वैकल्पिक शब्द

जाँच - परिणाम:

  • asयदि ऑब्जेक्ट किसी Int32 का उदाहरण है, तो इसका उपयोग करते हुए पहले परीक्षण करें; हुड के तहत यह isinst Int32(जो हाथ से लिखे गए कोड के समान है: if (o int) है। और उपयोग करते हुए as, यह बिना शर्त वस्तु को अनबॉक्स भी करता है। और यह एक संपत्ति को कॉल करने के लिए एक वास्तविक प्रदर्शन-हत्यारा है (यह अभी भी हुड के तहत एक समारोह है), IL_0027

  • कास्ट का उपयोग करते हुए, आप पहले परीक्षण करते हैं यदि ऑब्जेक्ट ए है int if (o is int); हुड के तहत यह उपयोग कर रहा है isinst Int32। यदि यह इंट का उदाहरण है, तो आप सुरक्षित रूप से मान, IL_002D को अनबॉक्स कर सकते हैं

सीधे शब्दों में कहें, यह asदृष्टिकोण का उपयोग करने का छद्म कोड है :

int? x;

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

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

और यह कास्ट दृष्टिकोण का उपयोग करने का छद्म कोड है:

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

तो कास्ट ( (int)a[i]अच्छी तरह से, सिंटैक्स एक कास्ट की तरह दिखता है, लेकिन यह वास्तव में अनबॉक्सिंग, कास्ट और अनबॉक्सिंग एक ही सिंटैक्स साझा करता है, अगली बार मैं सही शब्दावली के साथ पांडित्य होगा) दृष्टिकोण वास्तव में तेज़ है, आपको केवल एक मान अनबॉक्स करने की आवश्यकता है जब कोई वस्तु निश्चित रूप से ए int। एक ही बात को एक asदृष्टिकोण का उपयोग करने के लिए नहीं कहा जा सकता है ।


11

इस उत्तर को अप-टू-डेट रखने के लिए, यह ध्यान देने योग्य है कि इस पृष्ठ पर सबसे अधिक चर्चा अब C # 7.1 और .NET 4.7 के साथ हो रही है जो एक पतली वाक्यविन्यास का समर्थन करता है जो सर्वश्रेष्ठ IL कोड भी तैयार करता है।

ओपी का मूल उदाहरण ...

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

बस हो जाता है ...

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

मैं पाया है नई वाक्य रचना के लिए एक आम उपयोग है कि जब आप एक नेट लिख रहे हैं मान प्रकार (यानी structमें सी # ) कि औजार IEquatable<MyStruct>(के रूप में सबसे चाहिए)। दृढ़ता से टाइप की गई Equals(MyStruct other)विधि को लागू करने के बाद , आप अब अनचाहे Equals(Object obj)ओवरराइड ( इनसे विरासत में मिली Object) को पुनर्निर्देशित कर सकते हैं :

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

 


परिशिष्ट:Release निर्माण आईएल इस जवाब (क्रमशः) में ऊपर दिखाए पहले दो उदाहरण कार्यों के लिए कोड यहाँ दिया जाता है। जबकि नए सिंटैक्स के लिए IL कोड वास्तव में 1 बाइट छोटा है, यह ज्यादातर जीरो कॉल (बनाम दो) करके और unboxसंभव होने पर ऑपरेशन को पूरी तरह से टालकर जीतता है।

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

आगे के परीक्षण के लिए जो पहले उपलब्ध विकल्पों को पार करते हुए नए C # 7 सिंटैक्स के प्रदर्शन के बारे में मेरी टिप्पणी की पुष्टि करता है , यहां देखें (विशेष रूप से, उदाहरण 'डी')।


9

आगे की रूपरेखा:

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

}

आउटपुट:

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

हम इन आंकड़ों से क्या अनुमान लगा सकते हैं?

  • पहले, दृष्टिकोण के रूप में तब-कास्ट दृष्टिकोण काफी तेज है । 303 बनाम 3524
  • दूसरा, .Value कास्टिंग की तुलना में थोड़ा धीमा है। 3524 बनाम 3272
  • तीसरा, .HasValue मैन्युअल है (यानी का उपयोग कर रहा है ) की तुलना में थोड़ा धीमा है । 3524 बनाम 3282
  • चौथा, ऐप्पल-टू-ऐप्पल तुलना करना (यानी दोनों सिम्युलेटेड HasValue को असाइन करना और सिम्युलेटेड वैल्यू को एक साथ परिवर्तित करना) के रूप में सिम्युलेटेड के रूप में और वास्तविक के बीच में , हम नकली को देख सकते हैं जो अभी भी वास्तविक की तुलना में काफी तेज है । 395 बनाम 3524
  • अन्त में, पहले और चौथे निष्कर्ष के आधार पर, वहाँ कुछ गड़बड़ के साथ है के रूप में कार्यान्वयन ^ _ ^

8

मेरे पास इसे आज़माने का समय नहीं है, लेकिन आप चाहते हैं:

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

जैसा

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

आप हर बार एक नई वस्तु बना रहे हैं, जो पूरी तरह से समस्या की व्याख्या नहीं करेगी, लेकिन इसमें योगदान दे सकती है।


1
नहीं, मैंने इसे चलाया और यह थोड़ा धीमा है।
हेनक होल्टरमैन

2
किसी भिन्न स्थान पर किसी चर की घोषणा करना केवल तब उत्पन्न कोड को महत्वपूर्ण रूप से प्रभावित करता है जब मेरे अनुभव में चर को पकड़ा जाता है (जिस बिंदु पर यह वास्तविक शब्दार्थ को प्रभावित करता है)। ध्यान दें कि यह ढेर पर एक नई वस्तु नहीं बना रहा है, हालांकि यह निश्चित रूप int?से स्टैक पर एक नया उदाहरण बना रहा है unbox.any। मुझे संदेह है कि यह मुद्दा है - मेरा अनुमान है कि हाथ से तैयार किए गए आईएल दोनों विकल्पों को यहां हरा सकते हैं ... हालांकि यह भी संभव है कि जेआईटी को / कास्ट मामले के लिए मान्यता प्राप्त है और केवल एक बार जांच करें।
जॉन स्कीट

मैं सोच रहा था कि कलाकारों को शायद अनुकूलित किया गया है क्योंकि यह इतने लंबे समय से है।
जेम्स ब्लैक

1
है / कास्ट अनुकूलन के लिए एक आसान लक्ष्य है, यह इस तरह के एक कष्टप्रद सामान्य मुहावरा है।
एंटोन टायखी

4
विधि के लिए स्टैक फ्रेम बनाए जाने पर स्टैक पर स्थानीय चर आवंटित किए जाते हैं, इसलिए जहाँ आप विधि में चर घोषित करते हैं, उससे कोई फर्क नहीं पड़ता। (जब तक कि यह निश्चित रूप से बंद न हो जाए, लेकिन यहां ऐसा नहीं है।)
गुफ़ा

8

मैंने सटीक प्रकार के चेक निर्माण की कोशिश की

typeof(int) == item.GetType(), जो item is intसंस्करण के रूप में तेजी से प्रदर्शन करता है , और हमेशा नंबर देता है (जोर: भले ही आपने Nullable<int>सरणी के लिए लिखा था , आपको उपयोग करने की आवश्यकता होगी typeof(int))। आपको null != itemयहां एक अतिरिक्त जांच की भी आवश्यकता है।

तथापि

typeof(int?) == item.GetType()तेज (विपरीत में item is int?) रहता है , लेकिन हमेशा झूठा लौटता है।

टाइपऑफ़-निर्माण मेरी आँखों में सटीक प्रकार की जाँच के लिए सबसे तेज़ तरीका है , क्योंकि यह RuntimeTypeHandle का उपयोग करता है। चूंकि इस मामले में सटीक प्रकार is/asअशक्त के साथ मेल नहीं खाते हैं, मेरा अनुमान है, यह सुनिश्चित करने के लिए यहां अतिरिक्त हेवीलिफ्टिंग करना है कि यह वास्तव में एक अशक्त प्रकार का उदाहरण है।

और ईमानदारी से: आपका क्या is Nullable<xxx> plus HasValueआपको खरीदता है? कुछ भी तो नहीं। आप हमेशा अंतर्निहित (मान) प्रकार (इस मामले में) पर सीधे जा सकते हैं। आप या तो मान प्राप्त करते हैं या "नहीं, उस प्रकार का उदाहरण नहीं जो आप पूछ रहे थे"। यहां तक ​​कि अगर आपने (int?)nullसरणी पर लिखा है, तो टाइप चेक गलत वापस आ जाएगा।


दिलचस्प ... प्रयोग का विचार "के रूप में" + HasValue (नहीं है कि यह केवल प्रकार की जांच प्रदर्शन कर रहा है प्लस HasValue, टिप्पणी) एक बार दो बार के बजाय। यह एक ही चरण में "चेक और अनबॉक्स" कर रहा है। ऐसा लगता है कि यह तेज होना चाहिए ... लेकिन यह स्पष्ट रूप से नहीं है। मुझे यकीन नहीं है कि अंतिम वाक्य से आपका क्या मतलब है, लेकिन एक बॉक्सिंग जैसी कोई चीज नहीं है int?- यदि आप एक int?मूल्य बॉक्स करते हैं तो यह एक बॉक्सिंग इंट या एक nullसंदर्भ के रूप में समाप्त होता है ।
जॉन स्कीट

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

आउटपुट:

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

[EDIT: 2010-06-19]

नोट: पिछला परीक्षण कोर i7 (कंपनी विकास मशीन) का उपयोग करके VS2009 का उपयोग करके VS, कॉन्फ़िगरेशन डिबग के अंदर किया गया था।

निम्नलिखित कोर 2 डुओ का उपयोग करते हुए मेरी मशीन पर किया गया था, वीएस2010 का उपयोग करके

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

आप किस रूपरेखा संस्करण का उपयोग कर रहे हैं, ब्याज से बाहर? मेरी नेटबुक (.NET 4RC का उपयोग करके) के परिणाम और भी नाटकीय हैं - आपके द्वारा उपयोग किए गए संस्करण आपके परिणामों की तुलना में बहुत खराब हैं । शायद उन्होंने इसे .NET 4 RTM के लिए सुधार लिया है? मुझे अब भी लगता है कि यह और तेज़ हो सकता है ...
जॉन स्कीट

@ मिचेल: क्या आप एक अडॉप्टर्ड बिल्ड चला रहे थे, या डीबगर में चल रहे थे?
जॉन स्कीट

@Jon: unoptimized निर्माण, डीबगर के तहत
माइकल बयन

1
@ मिचेल: राइट - मैं एक डिबगर के तहत प्रदर्शन के परिणामों को काफी हद तक अप्रासंगिक देखता हूं:
जॉन स्कीट

@ जौन: यदि डिबगर के तहत, वीएस के अंदर अर्थ; हाँ पिछले बेंचमार्क डिबगर के तहत किया गया था। मैं फिर से बेंचमार्क, वीएस के अंदर और उसके बाहर, और डिबग के रूप में संकलित किया गया और रिलीज के रूप में संकलित किया गया। संपादित की जाँच करें
माइकल ब्यूं
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.