सरल बेंचमार्क में अजीब प्रदर्शन में वृद्धि


97

कल मुझे क्रिस्टोफ़ नाहर द्वारा ".NET स्ट्रक्चर परफॉर्मेंस" शीर्षक से एक लेख मिला, जिसने कई भाषाओं (C ++, C #, Java, जावास्क्रिप्ट) को एक विधि के लिए बेंचमार्क किया, जिसमें दो बिंदु संरचनाएं ( doubleटुपल्स) शामिल हैं।

जैसा कि यह निकला, C ++ संस्करण निष्पादित करने के लिए लगभग 1000ms लेता है (1e9 पुनरावृत्तियों), जबकि C # एक ही मशीन पर ~ 3000ms के तहत नहीं मिल सकता है (और x64 में और भी खराब प्रदर्शन करता है)।

इसे स्वयं जांचने के लिए, मैंने C # कोड लिया (और केवल उस विधि को कॉल करने के लिए थोड़ा सरल किया गया, जहाँ मान को मानकर पास किया गया है), और इसे i7-3610QM मशीन (सिंगल कोर के लिए 3.1Ghz बूस्ट), 8GB RAM, Win8 पर चलाया। 1, .NET 4.5.2 का उपयोग करते हुए, मेरे ओएस 64-बिट के बाद से 32-बिट (x86 WoW64) का निर्माण करें। यह सरलीकृत संस्करण है:

public static class CSharpTest
{
    private const int ITERATIONS = 1000000000;

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static Point AddByVal(Point a, Point b)
    {
        return new Point(a.X + b.Y, a.Y + b.X);
    }

    public static void Main()
    {
        Point a = new Point(1, 1), b = new Point(1, 1);

        Stopwatch sw = Stopwatch.StartNew();
        for (int i = 0; i < ITERATIONS; i++)
            a = AddByVal(a, b);
        sw.Stop();

        Console.WriteLine("Result: x={0} y={1}, Time elapsed: {2} ms", 
            a.X, a.Y, sw.ElapsedMilliseconds);
    }
}

साथ Pointबस के रूप में परिभाषित:

public struct Point 
{
    private readonly double _x, _y;

    public Point(double x, double y) { _x = x; _y = y; }

    public double X { get { return _x; } }

    public double Y { get { return _y; } }
}

इसे चलाने से लेख में इसके समान परिणाम मिलते हैं:

Result: x=1000000001 y=1000000001, Time elapsed: 3159 ms

पहला अजीब अवलोकन

चूंकि विधि को इनलाइन किया जाना चाहिए, मैंने सोचा कि अगर मैं पूरी तरह से संरचना को हटा देता हूं और पूरी चीज को एक साथ सम्मिलित करता हूं तो कोड कैसे प्रदर्शन करेगा?

public static class CSharpTest
{
    private const int ITERATIONS = 1000000000;

    public static void Main()
    {
        // not using structs at all here
        double ax = 1, ay = 1, bx = 1, by = 1;

        Stopwatch sw = Stopwatch.StartNew();
        for (int i = 0; i < ITERATIONS; i++)
        {
            ax = ax + by;
            ay = ay + bx;
        }
        sw.Stop();

        Console.WriteLine("Result: x={0} y={1}, Time elapsed: {2} ms", 
            ax, ay, sw.ElapsedMilliseconds);
    }
}

और व्यावहारिक रूप से एक ही परिणाम मिला (वास्तव में कई रिट्रीट के बाद 1% धीमा), जिसका अर्थ है कि JIT-ter एक अच्छा काम कर रहा है जो सभी फ़ंक्शन कॉल का अनुकूलन कर रहा है:

Result: x=1000000001 y=1000000001, Time elapsed: 3200 ms

इसका यह भी अर्थ है कि बेंचमार्क किसी भी structप्रदर्शन को मापने के लिए नहीं लगता है और वास्तव में केवल मूल doubleअंकगणित को मापने के लिए लगता है (बाकी सब कुछ दूर अनुकूलित होने के बाद)।

अजीब सामान

अब आया अजीब हिस्सा। यदि मैं केवल लूप के बाहर एक और स्टॉपवॉच जोड़ता हूं (हां, मैंने इसे कई रिट्रीट के बाद इस पागल कदम तक सीमित कर दिया है), कोड तीन बार तेजी से चलता है :

public static void Main()
{
    var outerSw = Stopwatch.StartNew();     // <-- added

    {
        Point a = new Point(1, 1), b = new Point(1, 1);

        var sw = Stopwatch.StartNew();
        for (int i = 0; i < ITERATIONS; i++)
            a = AddByVal(a, b);
        sw.Stop();

        Console.WriteLine("Result: x={0} y={1}, Time elapsed: {2} ms",
            a.X, a.Y, sw.ElapsedMilliseconds);
    }

    outerSw.Stop();                         // <-- added
}

Result: x=1000000001 y=1000000001, Time elapsed: 961 ms

क्या बकवास है! और ऐसा नहीं Stopwatchहै कि मुझे गलत परिणाम मिल रहे हैं क्योंकि मैं स्पष्ट रूप से देख सकता हूं कि यह एक सेकंड के बाद समाप्त होता है।

क्या कोई मुझे बता सकता है कि यहां क्या हो रहा है?

(अपडेट करें)

यहाँ एक ही कार्यक्रम में दो विधियाँ दी गई हैं, जिससे पता चलता है कि इसका कारण JITting नहीं है:

public static class CSharpTest
{
    private const int ITERATIONS = 1000000000;

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static Point AddByVal(Point a, Point b)
    {
        return new Point(a.X + b.Y, a.Y + b.X);
    }

    public static void Main()
    {
        Test1();
        Test2();

        Console.WriteLine();

        Test1();
        Test2();
    }

    private static void Test1()
    {
        Point a = new Point(1, 1), b = new Point(1, 1);

        var sw = Stopwatch.StartNew();
        for (int i = 0; i < ITERATIONS; i++)
            a = AddByVal(a, b);
        sw.Stop();

        Console.WriteLine("Test1: x={0} y={1}, Time elapsed: {2} ms", 
            a.X, a.Y, sw.ElapsedMilliseconds);
    }

    private static void Test2()
    {
        var swOuter = Stopwatch.StartNew();

        Point a = new Point(1, 1), b = new Point(1, 1);

        var sw = Stopwatch.StartNew();
        for (int i = 0; i < ITERATIONS; i++)
            a = AddByVal(a, b);
        sw.Stop();

        Console.WriteLine("Test2: x={0} y={1}, Time elapsed: {2} ms", 
            a.X, a.Y, sw.ElapsedMilliseconds);

        swOuter.Stop();
    }
}

आउटपुट:

Test1: x=1000000001 y=1000000001, Time elapsed: 3242 ms
Test2: x=1000000001 y=1000000001, Time elapsed: 974 ms

Test1: x=1000000001 y=1000000001, Time elapsed: 3251 ms
Test2: x=1000000001 y=1000000001, Time elapsed: 972 ms

यहाँ एक पास्टबिन है। आपको इसे .NET 4.x पर 32-बिट रिलीज़ के रूप में चलाने की आवश्यकता है (यह सुनिश्चित करने के लिए कोड में कुछ चेक हैं)।

(अपडेट 4)

@ हंस के जवाब पर @ usr की टिप्पणियों के बाद, मैंने दोनों तरीकों के लिए अनुकूलित डिसएफ़ीड की जाँच की, और वे अलग-अलग हैं:

बाईं ओर Test1, दाईं ओर Test2

ऐसा प्रतीत होता है कि अंतर दोहरे क्षेत्र संरेखण के बजाय पहले मामले में संकलक अभिनय के कारण हो सकता है?

इसके अलावा, अगर मैं दो चर (कुल 8 बाइट की ऑफसेट) जोड़ देता हूं, तो मुझे अभी भी वही गति मिलती है - और अब ऐसा नहीं लगता कि यह हंस पैसेंट द्वारा फील्ड अलाइनमेंट उल्लेख से संबंधित है:

// this is still fast?
private static void Test3()
{
    var magical_speed_booster_1 = "whatever";
    var magical_speed_booster_2 = "whatever";

    {
        Point a = new Point(1, 1), b = new Point(1, 1);

        var sw = Stopwatch.StartNew();
        for (int i = 0; i < ITERATIONS; i++)
            a = AddByVal(a, b);
        sw.Stop();

        Console.WriteLine("Test2: x={0} y={1}, Time elapsed: {2} ms",
            a.X, a.Y, sw.ElapsedMilliseconds);
    }

    GC.KeepAlive(magical_speed_booster_1);
    GC.KeepAlive(magical_speed_booster_2);
}

1
JIT बात के अलावा यह संकलक के अनुकूलन पर भी निर्भर करता है, नवीनतम Ryujit अधिक अनुकूलन करता है और यहां तक ​​कि सीमित SIMD निर्देशों का समर्थन भी करता है।
फेलिक्स के।

3
जॉन स्कीट ने संरचना में पठनीय क्षेत्रों के साथ एक प्रदर्शन समस्या पाई: माइक्रो-ऑप्टिमाइज़ेशन: पठनीय क्षेत्रों की आश्चर्यजनक अक्षमता । निजी क्षेत्रों को गैर-पठनीय बनाने का प्रयास करें।
dbc

2
@dbc: मैंने केवल स्थानीय doubleचरों के साथ एक परीक्षण किया , कोई structएस नहीं , इसलिए मैंने स्ट्रक्चर लेआउट / विधि कॉल अक्षमताओं को खारिज कर दिया है।
ग्रू

3
लगता है कि केवल 32-बिट पर होता है, RyuJIT के साथ, मुझे दोनों समय 1600ms मिलते हैं।
लेप्पी

2
मैंने दोनों विधियों के डिसएफ़ीड को देखा है। देखने के लिए कुछ भी दिलचस्प नहीं है। टेस्ट 1 स्पष्ट कारण के बिना अक्षम कोड उत्पन्न करता है। JIT बग या डिजाइन द्वारा। टेस्ट 1 में जेआईटी स्टैक के लिए प्रत्येक पुनरावृत्ति के लिए डबल्स को लोड और संग्रहीत करता है। यह सटीक सटीकता सुनिश्चित करने के लिए हो सकता है क्योंकि x86 फ्लोट यूनिट 80 बिट आंतरिक परिशुद्धता का उपयोग करता है। मैंने पाया कि फ़ंक्शन के शीर्ष पर कोई भी गैर-इनबिल्ट फ़ंक्शन कॉल इसे फिर से तेज करता है।
usr

जवाबों:


10

अपडेट 4 समस्या की व्याख्या करता है: पहले मामले में, जेआईटी स्टैक पर परिकलित मान ( a, b) रखता है ; दूसरे मामले में, JIT इसे रजिस्टरों में रखता है।

वास्तव में, की Test1वजह से धीरे-धीरे काम करता है Stopwatch। मैं के आधार पर निम्न न्यूनतम बेंचमार्क लिखा BenchmarkDotNet :

[BenchmarkTask(platform: BenchmarkPlatform.X86)]
public class Jit_RegistersVsStack
{
    private const int IterationCount = 100001;

    [Benchmark]
    [OperationsPerInvoke(IterationCount)]
    public string WithoutStopwatch()
    {
        double a = 1, b = 1;
        for (int i = 0; i < IterationCount; i++)
        {
            // fld1  
            // faddp       st(1),st
            a = a + b;
        }
        return string.Format("{0}", a);
    }

    [Benchmark]
    [OperationsPerInvoke(IterationCount)]
    public string WithStopwatch()
    {
        double a = 1, b = 1;
        var sw = new Stopwatch();
        for (int i = 0; i < IterationCount; i++)
        {
            // fld1  
            // fadd        qword ptr [ebp-14h]
            // fstp        qword ptr [ebp-14h]
            a = a + b;
        }
        return string.Format("{0}{1}", a, sw.ElapsedMilliseconds);
    }

    [Benchmark]
    [OperationsPerInvoke(IterationCount)]
    public string WithTwoStopwatches()
    {
        var outerSw = new Stopwatch();
        double a = 1, b = 1;
        var sw = new Stopwatch();
        for (int i = 0; i < IterationCount; i++)
        {
            // fld1  
            // faddp       st(1),st
            a = a + b;
        }
        return string.Format("{0}{1}", a, sw.ElapsedMilliseconds);
    }
}

मेरे कंप्यूटर पर परिणाम:

BenchmarkDotNet=v0.7.7.0
OS=Microsoft Windows NT 6.2.9200.0
Processor=Intel(R) Core(TM) i7-4702MQ CPU @ 2.20GHz, ProcessorCount=8
HostCLR=MS.NET 4.0.30319.42000, Arch=64-bit  [RyuJIT]
Type=Jit_RegistersVsStack  Mode=Throughput  Platform=X86  Jit=HostJit  .NET=HostFramework

             Method |   AvrTime |    StdDev |       op/s |
------------------- |---------- |---------- |----------- |
   WithoutStopwatch | 1.0333 ns | 0.0028 ns | 967,773.78 |
      WithStopwatch | 3.4453 ns | 0.0492 ns | 290,247.33 |
 WithTwoStopwatches | 1.0435 ns | 0.0341 ns | 958,302.81 |

जैसा कि हम देख सकते हैं:

  • WithoutStopwatchजल्दी काम करता है (क्योंकि a = a + bरजिस्टरों का उपयोग करता है)
  • WithStopwatchधीरे-धीरे काम करता है (क्योंकि a = a + bस्टैक का उपयोग करता है)
  • WithTwoStopwatchesजल्दी से फिर से काम करता है (क्योंकि a = a + bरजिस्टरों का उपयोग करता है)

JIT-x86 का व्यवहार विभिन्न स्थितियों की बड़ी मात्रा पर निर्भर करता है। किसी कारण के लिए, पहली स्टॉपवॉच JIT-x86 को स्टैक का उपयोग करने के लिए मजबूर करती है, और दूसरा स्टॉपवॉच इसे फिर से रजिस्टरों का उपयोग करने की अनुमति देता है।


यह वास्तव में कारण नहीं बताता है। यदि आप मेरे परीक्षण की जाँच करते हैं, तो ऐसा प्रतीत होता है कि परीक्षण में जो अतिरिक्त है वह Stopwatchवास्तव में तेजी से चलता है । लेकिन यदि आप उस आदेश को स्वैप करते हैं जिसमें उन्हें Mainविधि में लागू किया जाता है , तो दूसरी विधि अनुकूलित हो जाती है।
ग्रू

75

आपके प्रोग्राम का "तेज़" संस्करण हमेशा प्राप्त करने का एक बहुत ही सरल तरीका है। प्रोजेक्ट> गुण> बिल्ड टैब, "प्राथमिकता 32-बिट" विकल्प को अनचेक करें, यह सुनिश्चित करें कि प्लेटफ़ॉर्म लक्ष्य चयन AnyCPU है।

आप वास्तव में 32-बिट पसंद नहीं करते हैं, दुर्भाग्य से हमेशा सी # परियोजनाओं के लिए डिफ़ॉल्ट रूप से चालू होता है। ऐतिहासिक रूप से, विज़ुअल स्टूडियो टूलसेट ने 32-बिट प्रक्रियाओं के साथ बहुत बेहतर काम किया, एक पुरानी समस्या जिसे Microsoft दूर कर रहा है। उस विकल्प को हटाने का समय, VS2015 ने विशेष रूप से पिछले कुछ वास्तविक सड़क-ब्लॉकों को 64-बिट कोड के लिए एक नए-नए x64 घबराना और संपादन + जारी रखने के लिए सार्वभौमिक समर्थन के साथ संबोधित किया।

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

doubleऔर longप्रकार के एक 32-बिट प्रक्रिया में मुसीबत निर्माताओं रहे हैं। वे आकार में 64-बिट हैं। और इस प्रकार 4 से गलत हो सकता है, सीएलआर केवल 32-बिट संरेखण की गारंटी दे सकता है। 64-बिट प्रक्रिया में कोई समस्या नहीं है, सभी चर को 8 में संरेखित करने की गारंटी है। इसके अलावा अंतर्निहित कारण कि सी # भाषा उन्हें परमाणु होने का वादा नहीं कर सकती है । और बड़े ऑब्जेक्ट हीप में डबल के एरे को क्यों आवंटित किया जाता है जब उनके पास 1000 से अधिक तत्व होते हैं। LOH 8. की एक संरेखण गारंटी प्रदान करता है और यह बताता है कि समस्या को हल करने के लिए एक स्थानीय चर क्यों जोड़ा गया है, एक वस्तु संदर्भ 4 बाइट्स है, इसलिए यह डबल चर 4 से स्थानांतरित हो गया , अब इसे संरेखित करना। गलती से।

32-बिट C या C ++ कंपाइलर अतिरिक्त कार्य करता है ताकि यह सुनिश्चित किया जा सके कि डबल का गलत उपयोग नहीं किया जा सकता है। हल करने के लिए बिल्कुल आसान समस्या नहीं है, किसी फ़ंक्शन को दर्ज करने पर स्टैक को गलत माना जा सकता है, यह देखते हुए कि एकमात्र गारंटी यह है कि यह 4 से जुड़ा हुआ है। इस तरह के फ़ंक्शन के प्रस्ताव को 8 में संरेखित करने के लिए अतिरिक्त कार्य करने की आवश्यकता होती है। एक प्रबंधित कार्यक्रम में एक ही चाल काम नहीं करती है, कचरा इकट्ठा करने वाले व्यक्ति को इस बात की बहुत परवाह होती है कि स्मृति में स्थानीय चर कहाँ स्थित है। आवश्यक है ताकि यह पता चल सके कि जीसी के ढेर में एक वस्तु अभी भी संदर्भित है। यह इस तरह के एक चर 4 से स्थानांतरित होने के साथ ठीक से सौदा नहीं कर सकता क्योंकि विधि में प्रवेश करने पर स्टैक को गलत बताया गया था।

यह आसानी से SIMD निर्देशों का समर्थन न करने वाले .NET जिटर्स के साथ अंतर्निहित समस्या भी है। उनके पास बहुत मजबूत संरेखण आवश्यकताएं हैं, जिस तरह से प्रोसेसर खुद को हल नहीं कर सकता है। SSE2 को 16 के संरेखण की आवश्यकता होती है, AVX को 32 के संरेखण की आवश्यकता होती है। प्रबंधित कोड में इसे प्राप्त नहीं किया जा सकता है।

अंतिम लेकिन कम से कम, यह भी ध्यान दें कि यह एक C # प्रोग्राम की पूर्णता बनाता है जो 32-बिट मोड में बहुत अप्रत्याशित है। जब आप एक डबल या लंबे समय तक पहुंचते हैं जो एक ऑब्जेक्ट के रूप में एक क्षेत्र के रूप में संग्रहीत होता है, तो पूर्ण रूप से काफी बदल सकता है जब कचरा कलेक्टर ढेर को संकुचित करता है। जो वस्तुओं को स्मृति में ले जाता है, ऐसा क्षेत्र अब अचानक गलत हो सकता है / संरेखित हो सकता है। बहुत ही बेतरतीब ढंग से, काफी सिर-खरोंच हो सकता है :)

खैर, कोई साधारण सुधार नहीं है लेकिन एक, 64-बिट कोड भविष्य है। जब तक Microsoft प्रोजेक्ट टेम्प्लेट को नहीं बदलेगा, तब तक घबराने के लिए मजबूर करें। हो सकता है कि अगले संस्करण में जब वे Ryujit के बारे में अधिक आश्वस्त महसूस करें।


1
यह सुनिश्चित नहीं है कि जब डबल वेरिएबल (और टेस्ट 2 में हैं) को अपंजीकृत किया जा सकता है तो संरेखण इसमें कैसे निभाता है। Test1 स्टैक का उपयोग करता है, Test2 नहीं करता है।
यूएसआर

2
यह सवाल मेरे लिए बहुत तेजी से बदल रहा है कि मैं कैसे नज़र रखूं। आपको परीक्षण के परिणाम को प्रभावित करने वाले परीक्षण के लिए बाहर देखना होगा। आपको सेब से संतरे की तुलना करने के लिए परीक्षण विधियों पर [MethodImpl (MethodImplOptions.NoInlining)] लगाने की आवश्यकता है। अब आप देखेंगे कि अनुकूलक दोनों मामलों में FPU स्टैक पर चर रख सकता है।
हंस पैसेंट

4
ओमग, यह सच है। क्यों विधि संरेखण उत्पन्न निर्देशों पर कोई प्रभाव पड़ता है ?! लूप बॉडी के लिए कोई अंतर नहीं होना चाहिए। सभी रजिस्टर में होना चाहिए। संरेखण प्रस्ताव अप्रासंगिक होना चाहिए। अभी भी जेआईटी बग जैसा लगता है।
usr

3
मुझे उत्तर, बमर को काफी संशोधित करना है। मैं कल तक मिल जाएगा।
हंस पसंत

2
@ हंसपैंट क्या आप जेआईटी स्रोतों के माध्यम से खुदाई करने जा रहे हैं? वो मजेदार होगा। इस बिंदु पर मुझे पता है कि यह एक यादृच्छिक JIT बग है।
यूएसआर

5

इसे कुछ नीचे गिरा दिया (केवल 32-बिट सीएलआर 4.0 रनटाइम को प्रभावित करता है)।

var f = Stopwatch.Frequency;सभी फर्क पड़ता है की नियुक्ति पर ध्यान दें ।

धीमा (2700ms):

static void Test1()
{
  Point a = new Point(1, 1), b = new Point(1, 1);
  var f = Stopwatch.Frequency;

  var sw = Stopwatch.StartNew();
  for (int i = 0; i < ITERATIONS; i++)
    a = AddByVal(a, b);
  sw.Stop();

  Console.WriteLine("Test1: x={0} y={1}, Time elapsed: {2} ms",
      a.X, a.Y, sw.ElapsedMilliseconds);
}

तेज़ (800ms):

static void Test1()
{
  var f = Stopwatch.Frequency;
  Point a = new Point(1, 1), b = new Point(1, 1);

  var sw = Stopwatch.StartNew();
  for (int i = 0; i < ITERATIONS; i++)
    a = AddByVal(a, b);
  sw.Stop();

  Console.WriteLine("Test1: x={0} y={1}, Time elapsed: {2} ms",
      a.X, a.Y, sw.ElapsedMilliseconds);
}

बिना स्पर्श किए कोड को संशोधित करना Stopwatchभी काफी तेजी से बदलता है। आउटपुट Test1(bool warmup)में एक सशर्त को जोड़ने और जोड़ने के लिए विधि के हस्ताक्षर को बदलना Console: इसका if (!warmup) { Console.WriteLine(...); }भी एक ही प्रभाव है (इस मामले को रोकने के लिए मेरे परीक्षण का निर्माण करते समय इस पर ठोकर खाई)।
इनटाइब

@InBetween: मैंने देखा, कुछ गड़बड़ है। इसके अलावा केवल संरचनाओं पर होता है।
लेप्पी

4

ऐसा लगता है कि जटर में कुछ बग है क्योंकि व्यवहार और भी अजीब है। निम्नलिखित कोड पर विचार करें:

public static void Main()
{
    Test1(true);
    Test1(false);
    Console.ReadLine();
}

public static void Test1(bool warmup)
{
    Point a = new Point(1, 1), b = new Point(1, 1);

    Stopwatch sw = Stopwatch.StartNew();
    for (int i = 0; i < ITERATIONS; i++)
        a = AddByVal(a, b);
    sw.Stop();

    if (!warmup)
    {
        Console.WriteLine("Result: x={0} y={1}, Time elapsed: {2} ms",
            a.X, a.Y, sw.ElapsedMilliseconds);
    }
}

यह 900एमएस में चलेगा , बाहरी स्टॉपवॉच मामले के समान है। हालाँकि, यदि हम if (!warmup)शर्त हटाते हैं, तो यह 3000ms में चलेगा । यहां तक ​​कि अजनबी भी है, निम्नलिखित कोड 900एमएस में भी चलेगा :

public static void Test1()
{
    Point a = new Point(1, 1), b = new Point(1, 1);

    Stopwatch sw = Stopwatch.StartNew();
    for (int i = 0; i < ITERATIONS; i++)
        a = AddByVal(a, b);
    sw.Stop();

    Console.WriteLine("Result: x={0} y={1}, Time elapsed: {2} ms",
        0, 0, sw.ElapsedMilliseconds);
}

नोट मैंने निकाल दिया है a.Xऔर आउटपुट a.Yसे संदर्भ Console

मुझे पता नहीं है कि व्हाट्सएप चल रहा है, लेकिन इससे मेरे लिए बहुत छोटी सी गंध आती है और इसके बाहरी Stopwatchया नहीं होने से संबंधित नहीं है, यह मुद्दा थोड़ा अधिक सामान्यीकृत लगता है।


जब आप कॉल को हटाते हैं a.Xऔर a.Y, संकलक संभवतः लूप के अंदर सब कुछ दूर करने के लिए अनुकूलित करने के लिए स्वतंत्र है, क्योंकि ऑपरेशन के परिणाम अप्रयुक्त हैं।
ग्रू

@Groo: हाँ, यह उचित प्रतीत होता है लेकिन तब नहीं जब आप हमारे द्वारा देखे जा रहे अन्य अजीब व्यवहार को ध्यान में रखते हैं। जब आप स्थिति या ओपी को शामिल करते हैं, तो इसे हटाने a.Xऔर a.Yइसे किसी भी तेजी से आगे बढ़ाने में मदद नहीं करता है, जिसका अर्थ है कि इसके कुछ भी दूर नहीं होने का अनुकूलन, इसके बस जो भी बग को कोड बना रहा है वह सबऑप्टीमल गति ( एमएस के बजाय एमएस) चला रहा है। if (!warmup)outerSw3000900
inBetween

2
ओह, ठीक है, मैंने सोचा था कि गति में सुधार तब हुआ warmupथा जब यह सच था, लेकिन उस मामले में लाइन भी नहीं छपी है, इसलिए जिस मामले में यह मुद्रित होता है वह वास्तव में संदर्भ होता है a। फिर भी मैं यह सुनिश्चित करना पसंद करता हूं कि जब भी मैं सामान बेंच रहा हूं, मैं हमेशा विधि के अंत में कहीं न कहीं गणना परिणामों को संदर्भित कर रहा हूं।
ग्रू
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.