c# - "As" और nullable प्रकारों के साथ प्रदर्शन आश्चर्य




performance clr (7)

आगे प्रोफाइलिंग:

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
  • दूसरा,। वॉल्यू कास्टिंग से थोड़ा धीमा है। 3524 बनाम 3272
  • तीसरा, हैसवेल्यू मैनुअल का उपयोग करने से थोड़ा धीमा है (यानी इसका उपयोग कर रहा है )। 3524 बनाम 3282
  • चौथा, एक सेब-टू-सेब तुलना करना (यानी सिम्युलेटेड हैस्वाल्लू को आवंटित करना और सिम्युलेटेड वैल्यू को एक साथ करना) दृष्टिकोण के रूप में अनुरूपित और वास्तविक के बीच, हम सिमुलेट के रूप में वास्तविक के मुकाबले काफी तेजी से देख सकते हैं। 3 9 5 बनाम 3524
  • अंत में, पहले और चौथे निष्कर्ष के आधार पर, कार्यान्वयन ^ _ ^ के रूप में कुछ गड़बड़ है

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

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

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

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

क्या शून्य के प्रकार के लिए isinst का .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);
    }
}

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

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

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

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

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

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

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

परिशिष्ट: इस उत्तर (क्रमशः) में ऊपर दिखाए गए पहले दो उदाहरण कार्यों के लिए Release बिल्ड आईएल कोड यहां दिया गया है। जबकि नए सिंटैक्स के लिए आईएल कोड वास्तव में 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

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


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

प्यार को Nullable<T> । एक और मजेदार यह है कि यद्यपि जेआईटी गैर-नलिकात्मक structs के लिए null (और हटा देता है) 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;
    }
}

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

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

जैसा

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

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


यह FindSumWithAsAndHas का परिणाम है: alt text http://www.freeimagehosting.net/uploads/9e3c0bfb75.png

यह FindSumWithCast का परिणाम है: alt text http://www.freeimagehosting.net/uploads/ce8a5a3934.png

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

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

  • कास्ट का उपयोग करके, यदि आप ऑब्जेक्ट एक int if (o is int) तो आप पहले परीक्षण करते हैं; हुड के तहत यह isinst Int32 का उपयोग कर isinst Int32 । यदि यह int का एक उदाहरण है, तो आप मूल्य को सुरक्षित रूप से अनबॉक्स कर सकते हैं, 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] , सिंटैक्स एक कलाकार की तरह दिखता है, लेकिन वास्तव में यह अनबॉक्सिंग, कास्ट और अनबॉक्सिंग समान वाक्यविन्यास साझा करता है, अगली बार जब मैं सही शब्दावली के साथ pedantic होगा) दृष्टिकोण वास्तव में तेज़ है, जब आपको ऑब्जेक्ट निश्चित रूप से एक int होता है तो आपको केवल उस मान को अनबॉक्स करने की आवश्यकता होती है। दृष्टिकोण के as उपयोग करने के लिए एक ही बात नहीं कहा जा सकता है।


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

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

यहां बताया गया है कि क्या है ( ईसीएमए 335 विभाजन III, 4.6 ):

प्रारूप: isinst typeTok

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

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

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

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

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

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

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

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

(ईसीएमए 335 विभाजन III, 4.33): unbox.any

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

(ईसीएमए 335 विभाजन III, 4.32): unbox

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


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

[संपादित करें: 2010-06-19]

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

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

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




unboxing