c# - আমার কোড দ্রুত গতিতে চেষ্টা?




.net clr (4)

আমি চেষ্টা-ধরা প্রভাব পরীক্ষা করার জন্য কিছু কোড লিখেছি, কিন্তু কিছু বিস্ময়কর ফলাফল দেখেছি।

static void Main(string[] args)
{
    Thread.CurrentThread.Priority = ThreadPriority.Highest;
    Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.RealTime;

    long start = 0, stop = 0, elapsed = 0;
    double avg = 0.0;

    long temp = Fibo(1);

    for (int i = 1; i < 100000000; i++)
    {
        start = Stopwatch.GetTimestamp();
        temp = Fibo(100);
        stop = Stopwatch.GetTimestamp();

        elapsed = stop - start;
        avg = avg + ((double)elapsed - avg) / i;
    }

    Console.WriteLine("Elapsed: " + avg);
    Console.ReadKey();
}

static long Fibo(int n)
{
    long n1 = 0, n2 = 1, fibo = 0;
    n++;

    for (int i = 1; i < n; i++)
    {
        n1 = n2;
        n2 = fibo;
        fibo = n1 + n2;
    }

    return fibo;
}

আমার কম্পিউটারে, এই ধারাবাহিকভাবে 0.96 কাছাকাছি একটি মান প্রিন্ট ..

যখন আমি Fibo () এর ভিতরে লুপের জন্য মোড়ানো অবস্থায় এটি ব্যবহার করি তখন এটির মতো:

static long Fibo(int n)
{
    long n1 = 0, n2 = 1, fibo = 0;
    n++;

    try
    {
        for (int i = 1; i < n; i++)
        {
            n1 = n2;
            n2 = fibo;
            fibo = n1 + n2;
        }
    }
    catch {}

    return fibo;
}

এখন এটি ধারাবাহিকভাবে 0.69 আউট প্রিন্ট ... - এটি আসলে দ্রুত রান! কিন্তু কেন?

দ্রষ্টব্য: আমি রিলিজ কনফিগারেশন ব্যবহার করে এটি সংকলন করেছি এবং সরাসরি EXE ফাইলটি চালাচ্ছিলাম (ভিজ্যুয়াল স্টুডিওর বাইরে)।

সম্পাদন করুন: জন স্কিটের চমৎকার বিশ্লেষণটি দেখায় যে কোনওভাবেই এই বিশেষ ক্ষেত্রে x86 সিএলআর সিপিইউ নিবন্ধকদের আরও উপযুক্ত ভাবে ব্যবহার করতে পারে (এবং আমি মনে করি আমরা এখনো বুঝতে পারছি না কেন)। আমি জনিকে নিশ্চিত করেছিলাম যে x64 CLR এর মধ্যে পার্থক্য নেই এবং এটি x86 CLR এর চেয়ে দ্রুত ছিল। আমি long ধরনের পরিবর্তে Fibo পদ্ধতির ভিতরে int প্রকার ব্যবহার করে পরীক্ষা করেছি, এবং তারপর x86 CLR x64 CLR হিসাবে সমানভাবে দ্রুত ছিল।

আপডেট: দেখে মনে হচ্ছে এই সমস্যাটি রোস্লিন দ্বারা ঠিক করা হয়েছে। একই মেশিন, একই সিএলআর সংস্করণ - যখন VS 2013 এর সাথে সংকলিত হয় তখন সমস্যা উপরের হিসাবে রয়ে যায় তবে VS 2015 এর সাথে সংকলিত হওয়ার সময় সমস্যাটি চলে যায়।


আচ্ছা, আপনি সময় সময় করছেন উপায় আমার কাছে বেশ কদর্য দেখাচ্ছে। এটা শুধু পুরো লুপ সময় অনেক বেশি বুদ্ধিমান হবে:

var stopwatch = Stopwatch.StartNew();
for (int i = 1; i < 100000000; i++)
{
    Fibo(100);
}
stopwatch.Stop();
Console.WriteLine("Elapsed time: {0}", stopwatch.Elapsed);

এই ভাবে আপনি ক্ষুদ্র সময়, ভাসমান বিন্দু গাণিতিক এবং সংশ্লেষিত ত্রুটি রহমত হয় না।

যে পরিবর্তন করা, "ধরা" সংস্করণ "ধরা" সংস্করণ চেয়ে এখনও ধীর কিনা তা দেখুন।

সম্পাদনা করুন: ঠিক আছে, আমি নিজে চেষ্টা করেছি - এবং আমি একই ফলাফল দেখছি। খুব অদ্ভুত. আমি ভাবছিলাম যে চেষ্টা / ধরা কিছু খারাপ ইনলাইনিং অক্ষম করা হয়েছে কিনা, তবে [MethodImpl(MethodImplOptions.NoInlining)] ব্যবহার করে [MethodImpl(MethodImplOptions.NoInlining)] পরিবর্তে সাহায্য করেনি ...

মূলত আপনি cordbg অধীনে অপ্টিমাইজড JITted কোড তাকান করতে হবে, আমি সন্দেহ ...

সম্পাদনা করুন: আরও কিছু বিট তথ্য:

  • শুধু n++; কাছাকাছি চেষ্টা / ধরা নির্বাণ n++; লাইন এখনও কর্মক্ষমতা উন্নত, কিন্তু পুরো ব্লক প্রায় নির্বাণ যতটা না
  • যদি আপনি একটি নির্দিষ্ট ব্যতিক্রম ধরেন (আমার পরীক্ষায় ArgumentException ) এটি এখনও দ্রুত
  • আপনি যদি ক্যাচ ব্লকের ব্যতিক্রমটি মুদ্রণ করেন তবে এটি এখনও দ্রুত
  • আপনি যদি ক্যাচ ব্লকের ব্যতিক্রমটি পুনঃস্থাপন করেন তবে এটি আবার ধীর হয়ে যায়
  • আপনি যদি ক্যাচ ব্লকের পরিবর্তে অবশেষে ব্লকটি ব্যবহার করেন তবে এটি আবার ধীর হয়ে পড়ে
  • আপনি অবশেষে ব্লক এবং একটি ক্যাচ ব্লক ব্যবহার করলে, এটি দ্রুত

অদ্ভুত ...

সম্পাদনা করুন: ঠিক আছে, আমরা disassembly আছে ...

এটি সি # 2 কম্পাইলার এবং .NET 2 (32-বিট) সিএলআর ব্যবহার করছে, এমডিবিজি (যেমন আমার মেশিনে কর্ডবগ নেই) সহ disassembling। আমি এখনও একই কর্মক্ষমতা প্রভাব, ডিবাগার অধীনে এমনকি দেখতে। দ্রুত সংস্করণটি কেবলমাত্র একটি catch{} হ্যান্ডলারের সাথে পরিবর্তনশীল ঘোষণা এবং ফেরত বিবৃতির মধ্যে সবকিছু কাছাকাছি try । স্পষ্টভাবে ধীর সংস্করণ ছাড়া চেষ্টা / ধরা ছাড়া একই। কলিং কোড (অর্থাত্ মুখ্য) উভয় ক্ষেত্রেই একই, এবং একই অ্যাসেম্বলি উপস্থাপনা (তাই এটি একটি ইনলাইনিং সমস্যা নয়)।

দ্রুত সংস্করণের জন্য বিচ্ছিন্ন কোড:

 [0000] push        ebp
 [0001] mov         ebp,esp
 [0003] push        edi
 [0004] push        esi
 [0005] push        ebx
 [0006] sub         esp,1Ch
 [0009] xor         eax,eax
 [000b] mov         dword ptr [ebp-20h],eax
 [000e] mov         dword ptr [ebp-1Ch],eax
 [0011] mov         dword ptr [ebp-18h],eax
 [0014] mov         dword ptr [ebp-14h],eax
 [0017] xor         eax,eax
 [0019] mov         dword ptr [ebp-18h],eax
*[001c] mov         esi,1
 [0021] xor         edi,edi
 [0023] mov         dword ptr [ebp-28h],1
 [002a] mov         dword ptr [ebp-24h],0
 [0031] inc         ecx
 [0032] mov         ebx,2
 [0037] cmp         ecx,2
 [003a] jle         00000024
 [003c] mov         eax,esi
 [003e] mov         edx,edi
 [0040] mov         esi,dword ptr [ebp-28h]
 [0043] mov         edi,dword ptr [ebp-24h]
 [0046] add         eax,dword ptr [ebp-28h]
 [0049] adc         edx,dword ptr [ebp-24h]
 [004c] mov         dword ptr [ebp-28h],eax
 [004f] mov         dword ptr [ebp-24h],edx
 [0052] inc         ebx
 [0053] cmp         ebx,ecx
 [0055] jl          FFFFFFE7
 [0057] jmp         00000007
 [0059] call        64571ACB
 [005e] mov         eax,dword ptr [ebp-28h]
 [0061] mov         edx,dword ptr [ebp-24h]
 [0064] lea         esp,[ebp-0Ch]
 [0067] pop         ebx
 [0068] pop         esi
 [0069] pop         edi
 [006a] pop         ebp
 [006b] ret

ধীর সংস্করণ জন্য বিচ্ছিন্ন কোড:

 [0000] push        ebp
 [0001] mov         ebp,esp
 [0003] push        esi
 [0004] sub         esp,18h
*[0007] mov         dword ptr [ebp-14h],1
 [000e] mov         dword ptr [ebp-10h],0
 [0015] mov         dword ptr [ebp-1Ch],1
 [001c] mov         dword ptr [ebp-18h],0
 [0023] inc         ecx
 [0024] mov         esi,2
 [0029] cmp         ecx,2
 [002c] jle         00000031
 [002e] mov         eax,dword ptr [ebp-14h]
 [0031] mov         edx,dword ptr [ebp-10h]
 [0034] mov         dword ptr [ebp-0Ch],eax
 [0037] mov         dword ptr [ebp-8],edx
 [003a] mov         eax,dword ptr [ebp-1Ch]
 [003d] mov         edx,dword ptr [ebp-18h]
 [0040] mov         dword ptr [ebp-14h],eax
 [0043] mov         dword ptr [ebp-10h],edx
 [0046] mov         eax,dword ptr [ebp-0Ch]
 [0049] mov         edx,dword ptr [ebp-8]
 [004c] add         eax,dword ptr [ebp-1Ch]
 [004f] adc         edx,dword ptr [ebp-18h]
 [0052] mov         dword ptr [ebp-1Ch],eax
 [0055] mov         dword ptr [ebp-18h],edx
 [0058] inc         esi
 [0059] cmp         esi,ecx
 [005b] jl          FFFFFFD3
 [005d] mov         eax,dword ptr [ebp-1Ch]
 [0060] mov         edx,dword ptr [ebp-18h]
 [0063] lea         esp,[ebp-4]
 [0066] pop         esi
 [0067] pop         ebp
 [0068] ret

প্রতিটি ক্ষেত্রে * দেখায় যেখানে ডিবাগার একটি সহজ "ধাপে" প্রবেশ করেছে।

সম্পাদনা করুন: ঠিক আছে, আমি এখন কোডটি দেখেছি এবং আমি মনে করি আমি প্রতিটি সংস্করণ কিভাবে কাজ করে দেখতে পাচ্ছি ... এবং আমি বিশ্বাস করি ধীরে ধীরে সংস্করণটি ধীরে ধীরে ধীর হয়ে পড়ে কারণ এটি কম নিবন্ধক এবং আরো স্ট্যাক স্থান ব্যবহার করে। n এর ছোট মানগুলির জন্য এটি সম্ভবত দ্রুত - কিন্তু যখন লুপটি প্রচুর সময় নেয় তখন এটি ধীরে ধীরে হয়।

সম্ভবত চেষ্টা / ধরা ব্লক আরো নিবন্ধকদের সংরক্ষণ এবং পুনরুদ্ধার করতে বাধ্য করে, তাই JIT লুপের জন্যও সেইগুলি ব্যবহার করে ... যা সামগ্রিকভাবে কর্মক্ষমতা উন্নত করতে পারে। "স্বাভাবিক" কোডটিতে অনেক নিবন্ধকের ব্যবহার না করার জন্য এটি JIT- এর পক্ষে যুক্তিসঙ্গত সিদ্ধান্ত কিনা তা স্পষ্ট নয়।

সম্পাদনা করুন: শুধু আমার x64 মেশিনে এই চেষ্টা। এই কোডে x86 CLR এর চেয়ে x64 CLR অনেক দ্রুত (প্রায় 3-4 গুণ দ্রুত) এবং x64 এর অধীনে try / catch ব্লকটি একটি উল্লেখযোগ্য পার্থক্য তৈরি করে না।


আমি এটি একটি মন্তব্য হিসাবে রেখেছি কারণ আমি নিশ্চিত নই যে এটি সম্ভবত ঘটতে পারে, কিন্তু আমি মনে করি এটি একটি চেষ্টা করে না / বাদ দেওয়ার বিবৃতির মধ্যে আবর্জনা নিষ্পত্তি পদ্ধতির পরিবর্তন কম্পাইলার কাজ করে, এটি স্ট্যাক বন্ধ একটি recursive ভাবে বস্তুর মেমরি বরাদ্দ আপ সাফ। এই ক্ষেত্রে সাফ করা কোনও বস্তু হতে পারে না বা ফোর লুপটি এমন একটি বন্ধকরণ গঠন করতে পারে যা গার্বেজ সংগ্রহ প্রক্রিয়াটি একটি ভিন্ন সংগ্রহ পদ্ধতি প্রয়োগ করার জন্য পর্যাপ্ত স্বীকৃতি দেয়। সম্ভবত না, কিন্তু আমি মনে করি এটি উল্লেখযোগ্য যেহেতু আমি এটি অন্য কোথাও আলোচনা করে দেখিনি।


জোনের ডিস্যাসেম্লেলিটিগুলি দেখায় যে, দুটি সংস্করণের মধ্যে পার্থক্যটি হল যে দ্রুত সংস্করণে একটি জোড়ের নিবন্ধক ( esi,edi ) ব্যবহার করে স্থানীয় ভেরিয়েবলগুলির একটি সঞ্চয় করে যেখানে ধীর সংস্করণটি না থাকে।

JIT কম্পাইলার এমন কোডের জন্য নিবন্ধ ব্যবহার সম্পর্কিত বিভিন্ন ধারনাগুলি তৈরি করে যা একটি ট্র্যা-ক্যাচ ব্লক বনাম কোড থাকে যা নেই। এটি বিভিন্ন নিবন্ধ বরাদ্দ পছন্দ করতে কারণ। এই ক্ষেত্রে, এটি try-catch ব্লকের সাথে কোডটিকে সমর্থন করে। বিভিন্ন কোড বিপরীত প্রভাব হতে পারে, তাই আমি একটি সাধারণ উদ্দেশ্য গতি আপ কৌশল হিসাবে এই গণনা করা হবে না।

শেষ পর্যন্ত, এটি জানা খুব কঠিন যে কোন কোড দ্রুততম চলমান হবে। নিবন্ধীকরণের মতো কিছু এবং এটি প্রভাব বিস্তারকারী বিষয়গুলি নিম্ন স্তরের বাস্তবায়ন বিশদ যা আমি দেখি না যে কোনও নির্দিষ্ট কৌশল নির্ভরযোগ্যভাবে দ্রুত কোড তৈরি করতে পারে।

উদাহরণস্বরূপ, নিম্নলিখিত দুটি পদ্ধতি বিবেচনা করুন। তারা বাস্তব জীবনের উদাহরণ থেকে অভিযোজিত ছিল:

interface IIndexed { int this[int index] { get; set; } }
struct StructArray : IIndexed { 
    public int[] Array;
    public int this[int index] {
        get { return Array[index]; }
        set { Array[index] = value; }
    }
}

static int Generic<T>(int length, T a, T b) where T : IIndexed {
    int sum = 0;
    for (int i = 0; i < length; i++)
        sum += a[i] * b[i];
    return sum;
}
static int Specialized(int length, StructArray a, StructArray b) {
    int sum = 0;
    for (int i = 0; i < length; i++)
        sum += a[i] * b[i];
    return sum;
}

এক অন্য একটি জেনেরিক সংস্করণ। StructArray সঙ্গে জেনেরিক টাইপ প্রতিস্থাপন পদ্ধতি অনুরূপ করা হবে। কারন StructArray একটি মান টাইপ, এটি জেনেরিক পদ্ধতির নিজস্ব কম্পাইল সংস্করণ পায়। তবুও প্রকৃত চলমান সময় বিশেষ পদ্ধতির তুলনায় উল্লেখযোগ্যভাবে দীর্ঘ, কিন্তু শুধুমাত্র x86 এর জন্য। X64 জন্য, সময় বেশ অনেক অভিন্ন। অন্য ক্ষেত্রে, আমি x64 এর জন্য পার্থক্য দেখেছি।


স্টল ব্যবহারের অপ্টিমাইজেশান বুঝতে বিশেষজ্ঞ Roslyn প্রকৌশলীগুলির মধ্যে একজন এটি Roslyn এবং আমার কাছে রিপোর্ট করেছেন যে C # কম্পাইলার স্থানীয় পরিবর্তনশীল স্টোরগুলি তৈরি করে এবং JIT কম্পাইলার কীভাবে নিবন্ধন করে সেগুলির মধ্যে পারস্পরিক যোগাযোগের সমস্যা দেখা দেয়। সংশ্লিষ্ট x86 কোড সময় নির্ধারণ। ফলাফল স্থানীয় লোড এবং দোকান উপর suboptimal কোড প্রজন্মের হয়।

কোনও কারণে আমাদের সকলের কাছে অস্পষ্ট, জিটটারটি যখন এটি একটি সুরক্ষিত-সুরক্ষিত অঞ্চলে থাকে তখন জানে সমস্যাযুক্ত কোড প্রজন্মের পথ এড়ানো যায়।

এটি বেশ অদ্ভুত। আমরা JITTER টিমের সাথে ফলো করব এবং দেখতে পারি যে আমরা একটি বাগ প্রবেশ করতে পারি যাতে তারা এটি ঠিক করতে পারে।

এছাড়াও, স্থানীয়দেরকে "ক্ষণস্থায়ী" করা যেতে পারে তা নির্ধারণ করার জন্য আমরা Roslyn- এর C # এবং VB কম্পাইলারের অ্যালগরিদমগুলিতে উন্নতির জন্য কাজ করছি - যা স্ট্যাকে নির্দিষ্ট অবস্থান বরাদ্দ করার পরিবর্তে স্ট্যাকে ধাক্কা দেওয়া এবং পপ করা হয়েছে। অ্যাক্টিভেশন সময়কাল। আমরা বিশ্বাস করি যে জিআইটাররা রেজিস্ট্রেশন বরাদ্দের ভাল কাজ করতে পারবে এবং যদি আমরা স্থানীয়দের "মৃত" হওয়ার আগে এটি সম্পর্কে আরও ভালো ইঙ্গিত দিই তবে কী হবে।

আমাদের মনোযোগে এনে দেওয়ার জন্য ধন্যবাদ, এবং বিদ্বেষপূর্ণ আচরণের জন্য ক্ষমা।





performance-testing