c# 32 এবং 64 বিটের জন্য সংকলন করার সময় বিশাল পারফরম্যান্স পার্থক্য(26x দ্রুত)




performance 32bit-64bit (4)

আমি 4.5.4 এ এটি পুনরুত্পাদন করতে পারি। এখানে কোন রিউজিআইটি নেই। উভয় x86 এবং x64 বিচ্ছিন্নতা যুক্তিসঙ্গত দেখাচ্ছে। রেঞ্জের চেকগুলি এবং একই রকম। একই বেসিক কাঠামো। কোনও লুপ আনরোলিং করা হয়নি।

x86 বিভিন্ন ভাসমান নির্দেশের সেট ব্যবহার করে। এই নির্দেশাবলীর কার্যকারিতা বিভাগ বাদে x64 নির্দেশাবলীর সাথে তুলনীয় বলে মনে হচ্ছে:

  1. 32 বিট x87 ফ্লোট নির্দেশাবলী অভ্যন্তরীণভাবে 10 বাইট স্পষ্টতা ব্যবহার করে।
  2. প্রসারিত নির্ভুলতা বিভাগ অত্যন্ত ধীর।

বিভাগ অপারেশন 32 বিট সংস্করণ অত্যন্ত ধীর করে তোলে। বিভাগটি কমেন্ট না করা পারফরম্যান্সকে একটি বৃহত্তর ডিগ্রির সাথে সমান করে (32 বিট ডাউন 430 মিমি থেকে 3.25 মিমি)।

পিটার কর্ডস উল্লেখ করেছেন যে দুটি ভাসমান পয়েন্ট ইউনিটের নির্দেশনা বিলম্বগুলি ততটা ভিন্ন নয়। হতে পারে মধ্যবর্তী ফলাফলগুলির মধ্যে কিছু হ'ল অস্বীকৃতিযুক্ত সংখ্যা বা এনএএন। এগুলি ইউনিটগুলির মধ্যে একটিতে একটি ধীর পাথকে ট্রিগার করতে পারে। অথবা, 10 বাইট বনাম 8 বাইট ফ্লোট যথার্থতার কারণে মানগুলি দুটি বাস্তবায়নের মধ্যে বিভক্ত হয়।

পিটার কর্ডস আরও উল্লেখ করেছেন যে সমস্ত মধ্যবর্তী ফলাফলগুলি valueList.Add(i + 1) ... এই সমস্যাটি সরিয়ে দেওয়া হচ্ছে ( valueList.Add(i + 1) যাতে কোনও বিভাজক শূন্য হয় না) বেশিরভাগ ফলাফলকে সমান করে দেয়। স্পষ্টতই, 32 বিট কোডটি NaN অপারেন্ডগুলিকে মোটেই পছন্দ করে না। আসুন কিছু মধ্যবর্তী মান মুদ্রণ করা যাক: if (i % 1000 == 0) Console.WriteLine(result); । এটি নিশ্চিত করে যে ডেটা এখন সংবেদনশীল।

বেঞ্চমার্ক করার সময় আপনাকে একটি বাস্তবসম্মত কাজের চাপকে বেঞ্চমার্ক করতে হবে। তবে কে ভাববে যে একটি নির্দোষ বিভাগ আপনার মানদণ্ডকে জড়িয়ে ফেলতে পারে ?!

আরও ভাল বেঞ্চমার্ক পেতে কেবল সংখ্যার সংমিশ্রনের চেষ্টা করুন।

বিভাগ এবং মডুলো সর্বদা খুব ধীর হয়। আপনি যদি বিসিএল Dictionary কোডটি পরিবর্তন করে থাকেন তবে কেবল বালতি সূচকের কর্মক্ষমতা পরিমাপযোগ্য পরিমার্জন করতে মডুলো অপারেটরটি ব্যবহার করবেন না। এভাবেই ধীর বিভাজন হয়।

এখানে 32 বিট কোড রয়েছে:

Bit৪ বিট কোড (একই কাঠামো, দ্রুত বিভাগ):

এসএসই নির্দেশাবলী ব্যবহার করা সত্ত্বেও এটি ভেক্টরাইজড নয়

আমি মান ধরণের এবং রেফারেন্সের ধরণের তালিকার অ্যাক্সেস করার সময় for এবং foreach ব্যবহারের পার্থক্যটি পরিমাপ করার চেষ্টা করছিলাম।

আমি প্রোফাইলিং করতে নিম্নলিখিত ক্লাসটি ব্যবহার করেছি।

public static class Benchmarker
{
    public static void Profile(string description, int iterations, Action func)
    {
        Console.Write(description);

        // Warm up
        func();

        Stopwatch watch = new Stopwatch();

        // Clean up
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();

        watch.Start();
        for (int i = 0; i < iterations; i++)
        {
            func();
        }
        watch.Stop();

        Console.WriteLine(" average time: {0} ms", watch.Elapsed.TotalMilliseconds / iterations);
    }
}

আমি আমার মান ধরণের জন্য double ব্যবহার করেছি। এবং আমি রেফারেন্স ধরণের পরীক্ষার জন্য এই 'ভুয়া ক্লাস' তৈরি করেছি:

class DoubleWrapper
{
    public double Value { get; set; }

    public DoubleWrapper(double value)
    {
        Value = value;
    }
}

অবশেষে আমি এই কোডটি চালিয়েছি এবং সময়ের পার্থক্য তুলনা করেছি।

static void Main(string[] args)
{
    int size = 1000000;
    int iterationCount = 100;

    var valueList = new List<double>(size);
    for (int i = 0; i < size; i++) 
        valueList.Add(i);

    var refList = new List<DoubleWrapper>(size);
    for (int i = 0; i < size; i++) 
        refList.Add(new DoubleWrapper(i));

    double dummy;

    Benchmarker.Profile("valueList for: ", iterationCount, () =>
    {
        double result = 0;
        for (int i = 0; i < valueList.Count; i++)
        {
             unchecked
             {
                 var temp = valueList[i];
                 result *= temp;
                 result += temp;
                 result /= temp;
                 result -= temp;
             }
        }
        dummy = result;
    });

    Benchmarker.Profile("valueList foreach: ", iterationCount, () =>
    {
        double result = 0;
        foreach (var v in valueList)
        {
            var temp = v;
            result *= temp;
            result += temp;
            result /= temp;
            result -= temp;
        }
        dummy = result;
    });

    Benchmarker.Profile("refList for: ", iterationCount, () =>
    {
        double result = 0;
        for (int i = 0; i < refList.Count; i++)
        {
            unchecked
            {
                var temp = refList[i].Value;
                result *= temp;
                result += temp;
                result /= temp;
                result -= temp;
            }
        }
        dummy = result;
    });

    Benchmarker.Profile("refList foreach: ", iterationCount, () =>
    {
        double result = 0;
        foreach (var v in refList)
        {
            unchecked
            {
                var temp = v.Value;
                result *= temp;
                result += temp;
                result /= temp;
                result -= temp;
            }
        }

        dummy = result;
    });

    SafeExit();
}

আমি Release এবং যে Any CPU বিকল্পগুলি নির্বাচন করেছি, প্রোগ্রামটি চালিয়েছি এবং নিম্নলিখিত সময়গুলি পেয়েছি:

valueList for:  average time: 483,967938 ms
valueList foreach:  average time: 477,873079 ms
refList for:  average time: 490,524197 ms
refList foreach:  average time: 485,659557 ms
Done!

তারপরে আমি প্রকাশ এবং x64 বিকল্পগুলি নির্বাচন করেছি, প্রোগ্রামটি চালিয়েছি এবং নিম্নলিখিত সময়গুলি পেয়েছি:

valueList for:  average time: 16,720209 ms
valueList foreach:  average time: 15,953483 ms
refList for:  average time: 19,381077 ms
refList foreach:  average time: 18,636781 ms
Done!

এক্স 64 বিট সংস্করণটি এত দ্রুত কেন? আমি কিছু পার্থক্য আশা করেছিলাম, কিন্তু এই বড় কিছু না।

অন্যান্য কম্পিউটারে আমার অ্যাক্সেস নেই। আপনি কি দয়া করে এটি আপনার মেশিনে চালাতে পারেন এবং আমাকে ফলাফলগুলি বলতে পারেন? আমি ভিজুয়াল স্টুডিও 2015 ব্যবহার করছি এবং আমার একটি ইন্টেল Core i7 730 আছে।

এখানে SafeExit() পদ্ধতিটি রয়েছে, যাতে আপনি SafeExit() সংকলন / পরিচালনা করতে পারেন:

private static void SafeExit()
{
    Console.WriteLine("Done!");
    Console.ReadLine();
    System.Environment.Exit(1);
}

অনুরোধ হিসাবে, double? ব্যবহার করছেন double? আমার DoubleWrapper পরিবর্তে:

যে কোনও সিপিইউ

valueList for:  average time: 482,98116 ms
valueList foreach:  average time: 478,837701 ms
refList for:  average time: 491,075915 ms
refList foreach:  average time: 483,206072 ms
Done!

x64

valueList for:  average time: 16,393947 ms
valueList foreach:  average time: 15,87007 ms
refList for:  average time: 18,267736 ms
refList foreach:  average time: 16,496038 ms
Done!

সর্বশেষে তবে সর্বনিম্ন নয়: একটি x86 প্রোফাইল তৈরি করা আমাকে যে Any CPU ব্যবহারের প্রায় একই ফলাফল দেয়


এটি আপনার মেশিনে bit৪ বিবিটে দ্রুত চালিত করার বিভিন্ন কারণ থাকতে পারে। আপনি কোন সিপিইউ ব্যবহার করছেন তা আমি জিজ্ঞাসার কারণ হ'ল কারণ যখন bit৪ বিট সিপিইউগুলি প্রথম উপস্থিত হয়েছিল, তখন এএমডি এবং ইন্টেলের bit৪ বিট কোড হ্যান্ডেল করার জন্য বিভিন্ন পদ্ধতি ছিল।

প্রসেসরের আর্কিটেকচার:

ইন্টেলের সিপিইউ আর্কিটেকচারটি বিশুদ্ধভাবে 64 বিট ছিল। 32 বিট কোডটি কার্যকর করার জন্য, 32 বিট নির্দেশাবলী কার্যকর করার আগে (সিপিইউর ভিতরে) 64৪ বিট নির্দেশিকায় রূপান্তর করা দরকার।

এএমডির সিপিইউ আর্কিটেকচারটি ছিল তাদের 32 বিবিট আর্কিটেকচারের ঠিক উপরে 64 বিট তৈরি করা; এটি হ'ল এটি মূলত bit৪ বিট প্রসারিত একটি 32 বিট আর্কিটেকচার ছিল - কোনও কোড রূপান্তর প্রক্রিয়া ছিল না।

এটি এখন কয়েক বছর আগে স্পষ্টতই ছিল, সুতরাং / প্রযুক্তিটি কীভাবে পরিবর্তিত হয়েছে সে সম্পর্কে আমার কোনও ধারণা নেই তবে মূলত, আপনি 64 বিট মেশিনে code৪ বিট কোডটি আরও ভাল পারফর্ম করতে পারবেন বলে সিপিইউ দ্বিগুণ পরিমাণে কাজ করতে সক্ষম হবে বিট প্রতি নির্দেশ

.নাইট জেআইটি

এটি যুক্তিযুক্ত যে জেট সংকলক আপনার প্রসেসরের আর্কিটেকচার অনুসারে আপনার কোডটি অনুকূলকরণ করতে সক্ষম হওয়ায় নেট (এবং জাভা-র মতো অন্যান্য পরিচালিত ভাষা) সি ++ এর মতো ভাষাগুলি ছাড়িয়ে যেতে সক্ষম। এই বিষয়ে, আপনি দেখতে পাবেন যে জেআইটি সংকলক bit৪ বিট আর্কিটেকচারে এমন কিছু ব্যবহার করছে যা সম্ভবত 32 বিবিতে মৃত্যুদন্ড কার্যকর করার সময় উপলব্ধ ছিল না বা কোনও কাজের প্রয়োজন ছিল না।

বিঃদ্রঃ:

DoubleWrapper ব্যবহার করার পরিবর্তে, আপনি নুলযোগ্য Nullable<double> বা শর্টহ্যান্ড সিনট্যাক্স: double? ব্যবহারের কথা বিবেচনা করেছেন double? - এটি আপনার পরীক্ষাগুলিতে কোনও প্রভাব ফেলছে কিনা তা জানতে আগ্রহী।

দ্রষ্টব্য 2: কিছু লোক মনে হচ্ছে bit৪ বিট আর্কিটেকচার সম্পর্কে আমার মন্তব্যগুলি আইএ -৪৪ এর সাথে বিভ্রান্ত করছে। কেবল পরিষ্কার করতে, আমার উত্তরে, 64 বিট x86-64 এবং 32 বিট x86-32 বোঝায়। এখানে IA-64 রেফারেন্স কিছুই!


আমাদের পর্যবেক্ষণ আছে যে সমস্ত ভাসমান পয়েন্ট অপারেশনগুলির 99.9% NaN এর সাথে জড়িত থাকে যা অন্তত অত্যন্ত অস্বাভাবিক (পিটার কর্ডেসের দ্বারা পাওয়া প্রথম)। আমাদের সাথে আরেকটি পরীক্ষা হয়েছে, যা দেখেছিল যে বিভাগের নির্দেশাবলী সরিয়ে ফেললে সময়ের পার্থক্য প্রায় সম্পূর্ণ দূরে চলে যায়।

তবে তথাপি এই যে NaN কেবলমাত্র উত্পন্ন হয় কারণ খুব প্রথম বিভাগটি 0.0 / 0.0 গণনা করে যা প্রাথমিক NaN দেয়। বিভাগগুলি সম্পাদন করা না হলে ফলাফল সর্বদা 0.0 হয় এবং আমরা সর্বদা 0.0 * টেম্পল -> 0.0, 0.0 + টেম্প -> টেম্প, টেম্পে - টেম্পে = 0.0 গণনা করব। সুতরাং বিভাগ অপসারণ না শুধুমাত্র বিভাগগুলি সরাতে পারে, কিন্তু NaNs সরানো। আমি প্রত্যাশা করব যে NaN আসলেই সমস্যা এবং এটি একটি বাস্তবায়ন NaN এর খুব ধীরে ধীরে পরিচালনা করে, অন্যটিতে সমস্যা নেই।

I = 1 এ লুপটি শুরু করা এবং আবার পরিমাপ করা সার্থক হবে। চারটি অপারেশন ফলাফল * টেম্প, + টেম্প, / টেম্প - কার্যকরভাবে কার্যকর করুন (1 - টেম্প) যাতে আমাদের বেশিরভাগ ক্রিয়াকলাপের জন্য কোনও অস্বাভাবিক সংখ্যা (0, ইনফিনিটি, এনএএন) না থাকে।

একমাত্র সমস্যাটি হ'ল বিভাগটি সর্বদা একটি পূর্ণসংখ্যার ফলাফল দেয় এবং সঠিক ফলাফলের বিটগুলি ব্যবহার না করে কিছু বিভাগ বাস্তবায়নের শর্টকাট থাকে। উদাহরণস্বরূপ, 310.0 / 31.0 বিভক্ত করা বাকী 0.0 এর বাকী 10 টি প্রথম বিট হিসাবে 10.0 দেয় এবং কিছু বাস্তবায়ন বাকী 50 বা তত বিটের মূল্যায়ন বন্ধ করতে পারে অন্যরা না পারলে। যদি কোনও তাত্পর্যপূর্ণ পার্থক্য থাকে, তবে ফলাফল = 1.0 / 3.0 দিয়ে লুপটি শুরু করা একটি পার্থক্য তৈরি করবে।


valueList[i] = i , i=0 থেকে শুরু করে, তাই প্রথম লুপ পুনরাবৃত্তি 0.0 / 0.0 সুতরাং আপনার পুরো মাপদণ্ডের প্রতিটি ক্রিয়াকলাপ NaN গুলি দ্বারা সম্পন্ন হয়।

@ অ্যাসার যেমন ডিসসেস্পল আউটপুট দেখিয়েছেন , 32 বিট সংস্করণে x87 ফ্লোটিং পয়েন্ট ব্যবহার করা হয়েছে, যখন 64৪ বিট এসএসই ভাসমান পয়েন্ট ব্যবহার করেছে।

আমি NaN এর সাথে পারফরম্যান্সে বিশেষজ্ঞ নই, বা এর জন্য x87 এবং এসএসইর মধ্যে পার্থক্য রাখি না তবে আমি মনে করি এটি 26x পারফেক্ট পার্থক্য ব্যাখ্যা করে। আমি বাজি valueList[i] = i+1 যদি আপনি valueList[i] = i+1 শুরু valueList[i] = i+1 আপনার ফলাফলগুলি 32 এবং 64 valueList[i] = i+1 মধ্যে অনেক কাছাকাছি থাকবে। (আপডেট: usr নিশ্চিত করেছে যে এটি 32 এবং 64 বিট পারফরম্যান্স মোটামুটি কাছাকাছি করেছে))

অন্যান্য ক্রিয়াকলাপের তুলনায় বিভাগ খুব ধীর। @ Usr এর উত্তরে আমার মন্তব্য দেখুন। এছাড়াও হার্ডওয়্যার, এবং এএসএম এবং সি / সি ++ অনুকূলকরণের জন্য প্রচুর পরিমাণে দুর্দান্ত agner.org/optimize জন্য agner.org/optimize দেখুন it সমস্ত সাম্প্রতিক x86 সিপিইউয়ের জন্য বেশিরভাগ নির্দেশনার জন্য তাঁর কাছে বিলম্বের নির্দেশাবলী এবং থ্রুপুট রয়েছে।

তবে সাধারণ মানগুলির জন্য 10 বি x87 fdiv এর 8 বি ডাবল যথার্থ divsd তুলনায় খুব ধীর নয়। NaN, অসম্পূর্ণতা বা ডেনোরমালের সাথে পারফেক্ট পার্থক্য সম্পর্কে IDK।

যদিও এনএএন এবং অন্যান্য এফপিইউ ব্যতিক্রম নিয়ে যা ঘটে তার জন্য তাদের আলাদা নিয়ন্ত্রণ রয়েছে। X87 এফপিইউ নিয়ন্ত্রণ শব্দটি এসএসই রাউন্ডিং / ব্যতিক্রম নিয়ন্ত্রণ রেজিস্ট্রার (এমএক্সসিএসআর) থেকে পৃথক। যদি x87 প্রতিটি বিভাগের জন্য একটি সিপিইউ ব্যতিক্রম পেয়ে থাকে, তবে এসএসই নয়, এটি সহজেই 26 এর ফ্যাক্টরটি ব্যাখ্যা করে Or বা এনএএনএস পরিচালনা করার সময় কেবল পারফরম্যান্সের পার্থক্য রয়েছে। হার্ডওয়্যার NaN পরে NaN মাধ্যমে মন্থনের জন্য অনুকূলিত হয় না

আইডিকে যদি ডেনারমালসের সাথে ধীরগতি এড়ানোর জন্য এসএসই নিয়ন্ত্রণগুলি এখানে কার্যকর হয় তবে আমি বিশ্বাস করি যে result সর্বদা NaN হবে। সি # যদি এমএক্সসিএসআর-তে ডেনারমালগুলি হ'ল শূন্য পতাকা নির্ধারণ করে, বা ফ্লাশ-টু-শূন্য-পতাকা (যা পিছনে পড়ার সময় ডেনোরামালগুলি শূন্য হিসাবে গণ্য করার পরিবর্তে প্রথমে শূন্যগুলি লেখে) সেট করে IDK if

আমি এসএসই ভাসমান পয়েন্ট নিয়ন্ত্রণগুলি সম্পর্কে একটি ইন্টেল নিবন্ধ পেয়েছি, এটি x87 এফপিইউ নিয়ন্ত্রণ শব্দের সাথে বিপরীতে। যদিও NaN সম্পর্কে এটি বেশি বলার নেই। এটি এখানেই শেষ হয়:

উপসংহার

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

আইডিকে যদি এটি শূন্যের বিভাজনে কোনওরকমকে সহায়তা করে।

বনাম বনাম পূর্বাভাস জন্য

লুপ বডি যা কেবলমাত্র একটি একক লুপ বহনশীল নির্ভরতা শৃঙ্খলার পরিবর্তে থ্রুপুট-সীমাবদ্ধ, এটি পরীক্ষা করা আকর্ষণীয় হতে পারে। যেমনটি হয়, সমস্ত কাজ পূর্ববর্তী ফলাফলের উপর নির্ভর করে; সমান্তরালে সিপিইউয়ের জন্য কিছুই করার নেই (মুল / ডিভ চেইন চলাকালীন পরবর্তী অ্যারে লোডটি সীমা-পরীক্ষা করে দেখুন) other

আপনি যদি "সত্যিকারের কাজ" আরও বেশি সিপিইউ এক্সিকিউশন রিসোর্স দখল করে থাকেন তবে আপনি পদ্ধতিগুলির মধ্যে আরও পার্থক্য দেখতে পাবেন। এছাড়াও, প্রি-স্যান্ডিব্রিজে ইন্টেলের উপর, 28 লুপ লুপ বাফারে একটি লুপ ফিটিংয়ের মধ্যে একটি বড় পার্থক্য রয়েছে। আপনি যদি নির্দেশনা ডিকোড বাধা না পেয়ে থাকেন তবে, এসএসপি। যখন গড় নির্দেশের দৈর্ঘ্য দীর্ঘ হয় (যা এসএসইতে ঘটে)। একাধিক উওপকে ডিকোড করার নির্দেশনাগুলি ডিকোডার থ্রুপুটও সীমাবদ্ধ রাখবে, যদি না তারা ডিকোডারগুলির জন্য উপযুক্ত (যেমন 2-1-1) আকারে আসে। সুতরাং লুপ ওভারহেডের আরও নির্দেশাবলী সহ একটি লুপটি 28-এন্ট্রি উওপ ক্যাশে একটি লুপ ফিটিংয়ের মধ্যে পার্থক্য তৈরি করতে পারে যা নেহালেমের পক্ষে একটি বড় চুক্তি, এবং কখনও কখনও স্যান্ডিব্রিজে এবং পরে সহায়ক।







32bit-64bit