50x ডিফল্ট স্ট্যাক আকারের একটি থ্রেড তৈরি করার সময় কী কী বিপদ হবে?


228

আমি বর্তমানে খুব কার্য সম্পাদনমূলক সমালোচনামূলক প্রোগ্রামে কাজ করছি এবং আমি যে পথটি অন্বেষণ করার সিদ্ধান্ত নিয়েছি তা আমার কর্মী থ্রেডের স্ট্যাকের আকার বাড়িয়ে তুলতে সাহায্য করতে পারে যা আমার কর্মী থ্রেডের আকার বাড়িয়ে তুলছে যাতে আমি বেশিরভাগ ডেটা ( float[]গুলি) সরিয়ে নিতে পারি যা আমি অ্যাক্সেস করব স্ট্যাক (ব্যবহার করে stackalloc)

আমি পড়েছি যে কোনও থ্রেডের জন্য ডিফল্ট স্ট্যাকের আকার 1 এমবি, সুতরাং আমার সমস্ত float[]গুলি সরাতে আমাকে এই স্ট্যাকটি প্রায় 50 বার (50 এমবি to) প্রসারিত করতে হবে।

আমি বুঝতে পারি এটি সাধারণত "অনিরাপদ" হিসাবে বিবেচিত হয় এবং এটি প্রস্তাবিত নয়, তবে এই পদ্ধতির বিপরীতে আমার বর্তমান কোডটি চিহ্নিত করার পরে, আমি প্রক্রিয়াজাতকরণের গতিতে 530% বৃদ্ধি আবিষ্কার করেছি ! সুতরাং আমি আরও তদন্ত ছাড়াই কেবল এই বিকল্পটি দিয়ে যেতে পারি না, যা আমাকে আমার প্রশ্নের দিকে নিয়ে যায়; এত বড় আকারের স্ট্যাক বাড়ানোর সাথে কী কী ঝুঁকি রয়েছে (কী ভুল হতে পারে), এবং এই জাতীয় বিপদগুলি হ্রাস করতে আমার কী সাবধানতা অবলম্বন করা উচিত?

আমার পরীক্ষার কোড,

public static unsafe void TestMethod1()
{
    float* samples = stackalloc float[12500000];

    for (var ii = 0; ii < 12500000; ii++)
    {
        samples[ii] = 32768;
    }
}

public static void TestMethod2()
{
    var samples = new float[12500000];

    for (var i = 0; i < 12500000; i++)
    {
        samples[i] = 32768;
    }
}

98
+1 টি। সিরিয়াসলি। আদর্শ থেকে কোন বুদ্ধিমান প্রশ্নের মতো আপনি কী চান তা জিজ্ঞাসা করেন এবং তারপরে আপনি একটি খুব ভাল কেস তৈরি করেন যে আপনার নির্দিষ্ট দৃশ্যে এটি বিবেচনা করা একটি বুদ্ধিমান বিষয় কারণ আপনি নিজের গৃহকর্ম তৈরি করেছেন এবং ফলাফলটি পরিমাপ করেছেন। এটি খুব ভাল - আমি অনেক প্রশ্নের সাথে মিস করছি। খুব সুন্দর - ভাল আপনি এরকম কিছু বিবেচনা করেন, দুঃখের বিষয় অনেকগুলি সি # প্রোগ্রামার সেই অপ্টিমাইজেশনের সুযোগগুলি সম্পর্কে অবগত নন। হ্যাঁ, প্রায়শই প্রয়োজন হয় না - তবে কখনও কখনও এটি সমালোচনা করে এবং একটি হাগ পার্থক্য করে।
টমটম

5
আমি দুটি কোড দেখতে আগ্রহী যেগুলি কেবল প্রক্রিয়াজাতকরণের গতিতে 530% পার্থক্য রাখে, কেবলমাত্র অ্যারে স্ট্যাকে স্থানান্তরিত করার কারণে। এটা ঠিক ঠিক মনে হয় না।
ডায়ালেক্টিকাস

13
এই রাস্তাটি ঝাপ দেওয়ার আগে: আপনি Marshal.AllocHGlobalকি ম্যানেজড মেমোরির বাইরেFreeHGlobal ডেটা বরাদ্দ করতে ( খুব ভুলে যাবেন না ) ব্যবহার করে চেষ্টা করেছেন ? তারপরে একটিতে পয়েন্টারটি কাস্ট করুন এবং আপনার বাছাই করা উচিত। float*
মার্ক গ্র্যাভেল

2
আপনি যদি অনেকগুলি বরাদ্দ করেন তবে এটি সঠিক বোধ করে। স্ট্যাক্যালোক সমস্ত জিসি ইস্যুগুলিকে বাইপাস করে যা প্রসেসর স্তরে একটি শক্তিশালী লোকেশন তৈরি / তৈরি করতে পারে। মাইক্রো অপ্টিমাইজেশনের মতো টুপি দেখতে এটি এমন একটি জিনিস - যদি আপনি উচ্চ পারফরম্যান্সের গাণিতিক প্রোগ্রাম না লিখে থাকেন এবং ঠিক এই আচরণটি না দেখায় এবং এতে কোনও পার্থক্য আসে না;)
টমটম

6
আমার সন্দেহ: এই পদ্ধতিগুলির মধ্যে একটির প্রতিটি লুপ পুনরাবৃত্তির সীমা পরীক্ষা করা ট্রিগার করে যখন অন্যটি না করে বা এটি অপ্টিমাইজড হয়।
pjc50

উত্তর:


45

স্যামের সাথে পরীক্ষার কোডের তুলনা করার পরে, আমি নির্ধারণ করেছি যে আমরা দুজনেই ঠিক আছি!
তবে বিভিন্ন বিষয়ে:

  • মেমরি অ্যাক্সেস (পড়া এবং লেখার) যেখানেই তা যেমন দ্রুত - স্ট্যাক, গ্লোবাল বা গাদা
  • এটি বরাদ্দ করা, তবে, স্ট্যাকের চেয়ে দ্রুত এবং গাদা থেকে ধীরতম।

এটা তোলে ভালো যায়: stack< global< heap। (বরাদ্দের সময়)
প্রযুক্তিগতভাবে, স্ট্যাক বরাদ্দটি আসলে বরাদ্দ নয়, রানটাইমটি নিশ্চিত করে তোলে স্ট্যাকের একটি অংশ (ফ্রেম?) অ্যারের জন্য সংরক্ষিত আছে।

আমি দৃ strongly়ভাবে এই সঙ্গে সতর্কতা অবলম্বন করার পরামর্শ দিচ্ছি, যদিও।
আমি নিম্নলিখিত সুপারিশ:

  1. যখন আপনাকে ঘন ঘন অ্যারে তৈরি করতে হবে যা কখনই ফাংশনটি ছাড়বে না (যেমন এর রেফারেন্সটি পাস করে), স্ট্যাকটি ব্যবহার করা এক বিরাট উন্নতি হবে।
  2. আপনি যদি একটি অ্যারের পুনর্ব্যবহার করতে পারেন, আপনি যখনই পারেন তা করুন! দীর্ঘমেয়াদী অবজেক্ট স্টোরেজ করার জন্য গাদাটি সেরা জায়গা। (বৈশ্বিক স্মৃতি দূষণকারী ভাল নয়; স্ট্যাক ফ্রেমগুলি অদৃশ্য হয়ে যেতে পারে)

( দ্রষ্টব্য : ১. শুধুমাত্র মান ধরণের ক্ষেত্রে প্রযোজ্য; রেফারেন্সের ধরণগুলি গাদাতে বরাদ্দ দেওয়া হবে এবং সুবিধাটি 0 এ কমিয়ে দেওয়া হবে)

প্রশ্নের নিজেই উত্তর দেওয়ার জন্য: কোনও বড় স্ট্যাক পরীক্ষা দিয়ে আমি কোনও সমস্যায় পড়িনি।
আমি বিশ্বাস করি যে সম্ভাব্য সমস্যাগুলি হ'ল স্ট্যাক ওভারফ্লো, যদি আপনি সিস্টেমটি কম চলমান থাকে তবে আপনার থ্রেডগুলি তৈরি করার সময় আপনার ফাংশন কলগুলি এবং স্মৃতিশক্তি শেষ হয়ে না যাওয়া সম্পর্কে সতর্ক হন না।

নীচের বিভাগটি আমার প্রাথমিক উত্তর। এটি ভুল-ইশ এবং পরীক্ষাগুলি সঠিক নয়। এটি কেবল রেফারেন্সের জন্য রাখা হয়েছে।


আমার পরীক্ষাটি স্ট্যাক-বরাদ্দ মেমরিটিকে নির্দেশ করে এবং বিশ্বব্যাপী মেমরিটি অ্যারে ব্যবহারের জন্য হ্যাপ-বরাদ্দ মেমরির চেয়ে কমপক্ষে 15% ধীরে ধীরে (120% সময় লাগে)!

এটি আমার পরীক্ষার কোড এবং এটি একটি নমুনা আউটপুট:

Stack-allocated array time: 00:00:00.2224429
Globally-allocated array time: 00:00:00.2206767
Heap-allocated array time: 00:00:00.1842670
------------------------------------------
Fastest: Heap.

  |    S    |    G    |    H    |
--+---------+---------+---------+
S |    -    | 100.80 %| 120.72 %|
--+---------+---------+---------+
G |  99.21 %|    -    | 119.76 %|
--+---------+---------+---------+
H |  82.84 %|  83.50 %|    -    |
--+---------+---------+---------+
Rates are calculated by dividing the row's value to the column's.

নেট Windows.৪.১ এর অধীনে
আমি আই 47 4700 এমকি ব্যবহার করে উইন্ডোজ 8.1 প্রোতে (আপডেট 1 সহ) পরীক্ষা করেছি এবং আমি x86 এবং x64 উভয়ই পরীক্ষা করেছি এবং ফলাফলগুলি অভিন্ন।

সম্পাদনা করুন : আমি সমস্ত থ্রেডের স্ট্যাকের আকার 201 এমবি, নমুনার আকার 50 মিলিয়ন করে এবং পুনরাবৃত্তিকে হ্রাস করে 5 করে রেখেছি
ফলাফলগুলি উপরের মতই :

Stack-allocated array time: 00:00:00.4504903
Globally-allocated array time: 00:00:00.4020328
Heap-allocated array time: 00:00:00.3439016
------------------------------------------
Fastest: Heap.

  |    S    |    G    |    H    |
--+---------+---------+---------+
S |    -    | 112.05 %| 130.99 %|
--+---------+---------+---------+
G |  89.24 %|    -    | 116.90 %|
--+---------+---------+---------+
H |  76.34 %|  85.54 %|    -    |
--+---------+---------+---------+
Rates are calculated by dividing the row's value to the column's.

যদিও, মনে হচ্ছে স্ট্যাকটি আসলে ধীর হয়ে যাচ্ছে


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

আমি খুব বেমানান ফলাফল পাচ্ছি। পূর্ণ আস্থার সাথে, x64, রিলিজ কনফিগারেশন, কোনও ডিবাগার নেই, তারা সবাই সমান দ্রুত (1% এর চেয়ে কম পার্থক্য; ওঠানামা করছে) যখন আপনার স্ট্যাকের সাথে সত্যই দ্রুততর। আমার আরও পরীক্ষা করা দরকার! সম্পাদনা করুন : আপনার স্ট্যাকের ওভারফ্লো ব্যতিক্রম ছোঁড়া উচিত। আপনি কেবল অ্যারের জন্য পর্যাপ্ত পরিমাণ বরাদ্দ করেন। O_o
Vercas

হ্যাঁ আমি জানি, এটি নিকটবর্তী। আপনার মতো কয়েক বার মানদণ্ড পুনরাবৃত্তি করতে হবে, যেমন আমিও করেছি, সম্ভবত গড়ে ৫ বা তার বেশি রান নেওয়ার চেষ্টা করুন।
স্যাম

1
@ ভু 1 ম রানটি আমার জন্য যে কোনও পরীক্ষার 100 তম রান হিসাবে যতটা সময় নিয়েছিল। আমার অভিজ্ঞতা থেকে, এই জাভা জেআইটি জিনিস .NET মোটেও প্রযোজ্য নয়। একমাত্র "ওয়ার্ম আপ"। নেট প্রথমবারের জন্য ব্যবহৃত হয় যখন ক্লাস এবং সমাবেশগুলি লোড করে।
ভেরাকাস

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

28

আমি প্রক্রিয়াজাতকরণের গতিতে 530% বৃদ্ধি আবিষ্কার করেছি!

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

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

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

এটি একটি নেটিভ প্রোগ্রামের মতো নয়, বিশেষত সিতে লেখা একটি এটি স্ট্যাক ফ্রেমে অ্যারেগুলির জন্য স্থান সংরক্ষণ করতে পারে। স্ট্যাক বাফার ওভারফ্লোসের পিছনে বুনিয়াদি ম্যালওয়্যার আক্রমণ আক্রমণকারী। সি # তেও সম্ভব, আপনাকে stackallocকীওয়ার্ডটি ব্যবহার করতে হবে । যদি আপনি এটি করে থাকেন তবে সুস্পষ্ট বিপদটি এমন অনিরাপদ কোডটি লিখতে হবে যা এই জাতীয় আক্রমণগুলির সাথে সাথে এলোমেলো স্ট্যাক ফ্রেমের দুর্নীতির বিষয়। বাগগুলি নির্ণয় করা খুব শক্ত। পরবর্তী জিটটারগুলিতে এর বিরুদ্ধে একটি পাল্টা ব্যবস্থা রয়েছে, আমি মনে করি .NET 4.0 থেকে শুরু করে, যেখানে জিটারটি স্ট্যাক ফ্রেমে একটি "কুকি" রাখার কোড উত্পন্ন করে এবং পদ্ধতিটি ফিরে আসার পরে এটি এখনও অক্ষত আছে কিনা তা পরীক্ষা করে দেখায়। ডেস্কটপে তাত্ক্ষণিকভাবে ক্র্যাশ হয়ে কোনও উপায় ছাড়াই বা ঘটতে থাকলে দুর্ঘটনার খবর দেয় report এটি ... ব্যবহারকারীর মানসিক অবস্থার পক্ষে বিপজ্জনক।

অপারেটিং সিস্টেম দ্বারা শুরু করা আপনার প্রোগ্রামের মূল থ্রেডটিতে 1 এমবি স্ট্যাক থাকবে ডিফল্টরূপে, 4 এমবি যখন আপনি আপনার প্রোগ্রাম টার্গেট করে x64 সংকলন করেন। পোস্ট বিল্ড ইভেন্টে / স্ট্যাক বিকল্পের সাথে editbin.exe চালানো দরকার এমন বাড়ছে। 32-বিট মোডে চলাকালীন আপনার প্রোগ্রামটি শুরু হতে সমস্যা হতে পারে তার আগে আপনি সাধারণত 500 এমবি পর্যন্ত জিজ্ঞাসা করতে পারেন। থ্রেডগুলি খুব সহজ, অবশ্যই, বিপদ অঞ্চলটি 32-বিট প্রোগ্রামের জন্য 90 এমবি প্রায় ঘুরে বেড়াতে পারে। আপনার প্রোগ্রামটি দীর্ঘ সময় ধরে চলতে থাকে এবং ট্রিগ্রেশন করা হয়েছে পূর্ববর্তী বরাদ্দগুলি থেকে ঠিকানার স্থান খণ্ডিত হয়ে যায়। এই ব্যর্থতা মোডটি পেতে মোট ঠিকানা জায়গার ব্যবহার ইতিমধ্যে একটি গিগের বেশি হতে হবে।

আপনার কোডটি ট্রিপল-চেক করুন, খুব ভুল কিছু আছে। আপনি যদি আপনার কোডটি সুস্পষ্টভাবে এর সুবিধা নিতে না লিখে থাকেন তবে আপনি বড় স্ট্যাকের সাথে একটি এক্স 5 স্পিডআপ পেতে পারবেন না। যার সর্বদা অনিরাপদ কোড প্রয়োজন। সি # তে পয়েন্টার ব্যবহারের ক্ষেত্রে দ্রুত কোড তৈরি করার জন্য সর্বদা একটি নকশ থাকে, এটি অ্যারে বাউন্ডের চেকের অধীন হয় না।


21
রিপোর্ট করা 5x স্পিডআপ থেকে অন্য দিকে চলে যাওয়া float[]ছিল float*। বড় স্ট্যাকটি কেবল এটি কীভাবে সম্পন্ন হয়েছিল। কিছু পরিস্থিতিতে একটি এক্স 5 স্পিডআপ পুরোপুরি যুক্তিযুক্ত যে পরিবর্তনের জন্য।
মার্ক গ্রাভেল

3
ঠিক আছে, আমি প্রশ্নের উত্তর দেওয়া শুরু করার পরে আমার কাছে কোড স্নিপেট ছিল না। এখনও যথেষ্ট কাছাকাছি।
হান্স প্যাস্যান্ট

22

আমার সেখানে একটি রিজার্ভেশন থাকবে যে আমি কেবল এটি কীভাবে ভবিষ্যদ্বাণী করতে জানি না - অনুমতি, জিসি (যা স্ট্যাকটি স্ক্যান করা প্রয়োজন), ইত্যাদি - সমস্ত কিছুই প্রভাবিত হতে পারে। পরিবর্তে আমি পরিচালনা না করা মেমরিটি ব্যবহার করতে খুব প্ররোচিত হব:

var ptr = Marshal.AllocHGlobal(sizeBytes);
try
{
    float* x = (float*)ptr;
    DoWork(x);
}
finally
{
    Marshal.FreeHGlobal(ptr);
}

1
পার্শ্ব প্রশ্ন: জিসির স্ট্যাকটি স্ক্যান করার দরকার পড়বে কেন? বরাদ্দ করা মেমরি stackallocআবর্জনা সংগ্রহের সাপেক্ষে নয়।
ডাস্ট্রো

6
@ ডকাস্ট্রো কেবলমাত্র স্ট্যাকের মধ্যে বিদ্যমান রেফারেন্সগুলি পরীক্ষা করার জন্য এটি স্ট্যাকটি স্ক্যান করতে হবে। আমি এতটুকু জানি না যে এটি এত বড় stackallocআকারের হয়ে উঠলে কী করতে চলেছে - এটিকে দৌড়াদৌড়ি করা দরকার, এবং আপনি আশা করতেন যে এটি এতটা অনায়াসেই করবে - তবে আমি যে বিষয়টি তৈরি করতে চাইছি তা হ'ল এটি পরিচয় করিয়ে দেয় অপ্রয়োজনীয় জটিলতা / উদ্বেগ। আইএমও, stackallocস্ক্র্যাচ-বাফার হিসাবে দুর্দান্ত, তবে একটি উত্সর্গীকৃত কর্মক্ষেত্রের জন্য, স্ট্যাকটিকে গালি দেওয়া / বিভ্রান্ত করার পরিবর্তে কোথাও কেবল একটি অংশ-ও-মেমরি বরাদ্দ করা বেশি প্রত্যাশিত,
মার্ক গ্র্যাভেল

8

একটি জিনিস যা ভুল হতে পারে তা হ'ল আপনি এটি করার অনুমতি নাও পেতে পারেন। পূর্ণ-বিশ্বাস মোডে না চালানো না হলে ফ্রেমওয়ার্কটি কেবল বৃহত্তর স্ট্যাক আকারের অনুরোধটিকে অগ্রাহ্য করবে (এমএসডিএন দেখুন Thread Constructor (ParameterizedThreadStart, Int32))

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


1
ভাল ধারণা, আমি এর পরিবর্তে পুনরাবৃত্তি করব। তদ্ব্যতীত, আমার কোডটি পুরো ভরস মোডে চলছে, সুতরাং আমার অন্য কোনও জিনিস সন্ধান করা উচিত?
স্যাম

6

উচ্চ পারফরম্যান্ট অ্যারেগুলি সাধারণ সি # একের মতো একইভাবে অ্যাক্সেসযোগ্য হতে পারে তবে এটি সমস্যার শুরু হতে পারে: নিম্নলিখিত কোডটি বিবেচনা করুন:

float[] someArray = new float[100]
someArray[200] = 10.0;

আপনি আবদ্ধ ব্যতিক্রম প্রত্যাশার প্রত্যাশা করেন এবং এটি সম্পূর্ণরূপে বোধগম্য হয় কারণ আপনি 200 উপাদানটি অ্যাক্সেস করার চেষ্টা করছেন তবে সর্বাধিক অনুমোদিত মানটি 99 If আপনি যদি স্ট্যাক্যালোক রুটে যান তবে আপনার অ্যারের চারপাশে আবদ্ধ আবরণ কোনও আবশ্যক থাকবে না এবং আবদ্ধ চেক নিম্নলিখিত কোনও ব্যতিক্রম দেখায় না:

Float* pFloat =  stackalloc float[100];
fFloat[200]= 10.0;

উপরে আপনি 100 টি ফ্লোট ধারণ করার জন্য পর্যাপ্ত মেমরি বরাদ্দ করছেন এবং আপনি আপনার ফ্লোটের মান ধরে রাখার জন্য এই মেমরিটির শুরু হওয়া অবস্থান থেকে শুরু হওয়া মাপের (ফ্লোট) মেমরি অবস্থান নির্ধারণ করছেন 10. ​​সন্দেহ নেই যে এই স্মৃতিটি বাইরে রয়েছে ভাসমানদের জন্য মেমরি বরাদ্দ করা হয় এবং কেউই জানতে পারে না যে ঠিকানায় কী সংরক্ষণ করা যায়। আপনি যদি ভাগ্যবান হন তবে আপনি বর্তমানে কিছু অব্যবহৃত মেমরি ব্যবহার করতে পারেন তবে একই সময়ে সম্ভবত আপনি এমন কিছু স্থান ওভাররাইটও করতে পারেন যা অন্যান্য ভেরিয়েবলগুলি সংরক্ষণের জন্য ব্যবহৃত হয়েছিল। সংক্ষেপে: অপ্রত্যাশিত রানটাইম আচরণ।


আসলে ভুল। রানটাইম এবং সংকলক পরীক্ষা এখনও আছে।
টমটম

9
@ টমটম এরম, না; উত্তরের যোগ্যতা রয়েছে; প্রশ্নটি আলোচনা করে stackalloc, কোন ক্ষেত্রে আমরা float*ইত্যাদি সম্পর্কিত কথা বলছি - যার একই চেক নেই। এটি unsafeএকটি খুব ভাল কারণে বলা হয়। ব্যক্তিগত কারণে আমি unsafeযখন পুরোপুরি খুশি থাকি তখন কোনও যুক্তিসঙ্গত কারণ থাকলেও সক্রেটিস কিছু যুক্তিযুক্ত বিষয় তুলে ধরে।
মার্ক গ্র্যাভেল

@ মার্ক দেখানো কোডের জন্য (জেআইটি পরিচালিত হওয়ার পরে) আর কোনও বাউন্ডস চেক নেই কারণ সংকলকটির পক্ষে এটি তুচ্ছ কারণ এই কারণে যে সমস্ত প্রবেশাধিকার সীমাবদ্ধ রয়েছে। সাধারণভাবে যদিও এটি অবশ্যই একটি পার্থক্য করতে পারে।
ভু

6

জাভা বা সি # এর মতো জেআইটি এবং জিসি-র সাথে মাইক্রোবেঞ্চমার্কিং ভাষাগুলি কিছুটা জটিল হতে পারে, সুতরাং বিদ্যমান কাঠামোটি ব্যবহার করা সাধারণত ভাল ধারণা - জাভা এমএইচএফ বা ক্যালিপার অফার করে যা আমার জ্ঞানের সবচেয়ে ভাল সি # অফার করে না যারা কাছে পৌঁছে কিছু। জন স্কিট লিখেছিলেন এই এখানে যা আমি অন্ধ অনুমান করব সবচেয়ে গুরুত্বপূর্ণ জিনিস যত্ন নেয় (জন জানে কি যে এলাকায় করছে; এছাড়াও হ্যাঁ কোন উদ্বেগ আমি আসলে চেক করেনি)। আমি সময়টি কিছুটা টুইট করেছিলাম কারণ ওয়ার্মআপের পরে পরীক্ষা প্রতি 30 সেকেন্ড আমার ধৈর্য্যের জন্য খুব বেশি ছিল (5 সেকেন্ড করা উচিত)।

উইন্ডোজ x x64 এর আওতায় প্রথমে ফলাফলগুলি .NET 4.5.1 - সংখ্যাগুলি পুনরাবৃত্তিকে বোঝায় যে এটি 5 সেকেন্ডে চালানো যেতে পারে তাই আরও ভাল।

x64 জেআইটি:

Standard       10,589.00  (1.00)
UnsafeStandard 10,612.00  (1.00)
Stackalloc     12,088.00  (1.14)
FixedStandard  10,715.00  (1.01)
GlobalAlloc    12,547.00  (1.18)

x86 জেআইটি (হ্যাঁ এটি এখনও দুঃখের মতো):

Standard       14,787.00   (1.02)
UnsafeStandard 14,549.00   (1.00)
Stackalloc     15,830.00   (1.09)
FixedStandard  14,824.00   (1.02)
GlobalAlloc    18,744.00   (1.29)

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

এবং এখানে কোড:

public static float Standard(int size) {
    float[] samples = new float[size];
    for (var ii = 0; ii < size; ii++) {
        samples[ii] = 32768 + (ii != 0 ? samples[ii - 1] : 0);
    }
    return samples[size - 1];
}

public static unsafe float UnsafeStandard(int size) {
    float[] samples = new float[size];
    for (var ii = 0; ii < size; ii++) {
        samples[ii] = 32768 + (ii != 0 ? samples[ii - 1] : 0);
    }
    return samples[size - 1];
}

public static unsafe float Stackalloc(int size) {
    float* samples = stackalloc float[size];
    for (var ii = 0; ii < size; ii++) {
        samples[ii] = 32768 + (ii != 0 ? samples[ii - 1] : 0);
    }
    return samples[size - 1];
}

public static unsafe float FixedStandard(int size) {
    float[] prev = new float[size];
    fixed (float* samples = &prev[0]) {
        for (var ii = 0; ii < size; ii++) {
            samples[ii] = 32768 + (ii != 0 ? samples[ii - 1] : 0);
        }
        return samples[size - 1];
    }
}

public static unsafe float GlobalAlloc(int size) {
    var ptr = Marshal.AllocHGlobal(size * sizeof(float));
    try {
        float* samples = (float*)ptr;
        for (var ii = 0; ii < size; ii++) {
            samples[ii] = 32768 + (ii != 0 ? samples[ii - 1] : 0);
        }
        return samples[size - 1];
    } finally {
        Marshal.FreeHGlobal(ptr);
    }
}

static void Main(string[] args) {
    int inputSize = 100000;
    var results = TestSuite.Create("Tests", inputSize, Standard(inputSize)).
        Add(Standard).
        Add(UnsafeStandard).
        Add(Stackalloc).
        Add(FixedStandard).
        Add(GlobalAlloc).
        RunTests();
    results.Display(ResultColumns.NameAndIterations);
}

একটি আকর্ষণীয় পর্যবেক্ষণ, আমাকে আমার মানদণ্ডগুলি আবার যাচাই করতে হবে। যদিও এটি এখনও আমার প্রশ্নের উত্তর দেয় না, " ... স্ট্যাককে এত বড় আকারে বাড়ানোর সাথে কী কী বিপদ রয়েছে ... "। আমার ফলাফলগুলি ভুল হলেও, প্রশ্নটি এখনও বৈধ; তবুও আমি চেষ্টাটির প্রশংসা করি
স্যাম

1
@ সাম 12500000আকার হিসাবে ব্যবহার করার সময় আমি আসলে একটি স্ট্যাকওভারফ্লো ব্যতিক্রম পাই। তবে বেশিরভাগ ক্ষেত্রে এটি অন্তর্নিহিত ভিত্তিটি প্রত্যাখ্যান করার বিষয়ে ছিল যে স্ট্যাক বরাদ্দকৃত কোডটি ব্যবহার করা হ'ল দ্রুততার কয়েকটি আদেশ। আমরা অন্যথায় এখানে খুব কম পরিমাণে কাজ সম্ভব সম্পন্ন করছি এবং পার্থক্যটি ইতিমধ্যে প্রায় 10-15% এর মধ্যে রয়েছে - বাস্তবে এটি আরও কম হবে .. আমার মতে এটি অবশ্যই পুরো আলোচনার পরিবর্তন করে।
ভু

5

যেহেতু পারফরম্যান্সের পার্থক্য খুব বিশাল, সমস্যা সবেমাত্র বরাদ্দ সম্পর্কিত। এটি অ্যারে অ্যাক্সেসের কারণে ঘটে।

আমি ফাংশনগুলির লুপ বডি আলাদা করেছিলাম:

TestMethod1:

IL_0011:  ldloc.0 
IL_0012:  ldloc.1 
IL_0013:  ldc.i4.4 
IL_0014:  mul 
IL_0015:  add 
IL_0016:  ldc.r4 32768.
IL_001b:  stind.r4 // <----------- This one
IL_001c:  ldloc.1 
IL_001d:  ldc.i4.1 
IL_001e:  add 
IL_001f:  stloc.1 
IL_0020:  ldloc.1 
IL_0021:  ldc.i4 12500000
IL_0026:  blt IL_0011

TestMethod2:

IL_0012:  ldloc.0 
IL_0013:  ldloc.1 
IL_0014:  ldc.r4 32768.
IL_0019:  stelem.r4 // <----------- This one
IL_001a:  ldloc.1 
IL_001b:  ldc.i4.1 
IL_001c:  add 
IL_001d:  stloc.1 
IL_001e:  ldloc.1 
IL_001f:  ldc.i4 12500000
IL_0024:  blt IL_0012

আমরা নির্দেশাবলীর ব্যবহার পরীক্ষা করতে পারি এবং আরও গুরুত্বপূর্ণভাবে, তারা ইসিএমএ বিশেষত ব্যতিক্রম করে :

stind.r4: Store value of type float32 into memory at address

ব্যতিক্রমগুলি এটি ছুড়ে ফেলে:

System.NullReferenceException

এবং

stelem.r4: Replace array element at index with the float32 value on the stack.

ব্যতিক্রম এটি ছুড়ে:

System.NullReferenceException
System.IndexOutOfRangeException
System.ArrayTypeMismatchException

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

এবং এটি আপনার প্রশ্নেরও উত্তর দেয়: বিপদটি অ্যারে রেঞ্জ এবং টাইপ চেকিংয়ের অনুপস্থিত। এটি অনিরাপদ (ফাংশন ঘোষণায় উল্লিখিত হিসাবে; ডি)।


4

সম্পাদনা: (কোড এবং পরিমাপে ছোট পরিবর্তন ফলাফলের মধ্যে বড় পরিবর্তন আনবে)

প্রথমত আমি ডিবাগারে অপ্টিমাইজড কোডটি চালিয়েছি (এফ 5) তবে এটি ভুল ছিল। এটি ডিবাগার ছাড়াই চালানো উচিত (Ctrl + F5)। দ্বিতীয়ত, কোডটি পুঙ্খানুপুঙ্খভাবে অনুকূলিত হতে পারে, সুতরাং আমাদের অবশ্যই এটি জটিল করতে হবে যাতে অপ্টিমাইজারটি আমাদের পরিমাপের সাথে গোলমাল না করে। আমি সমস্ত পদ্ধতি অ্যারেতে একটি শেষ আইটেম ফিরিয়ে দিয়েছি এবং অ্যারেটি আলাদাভাবে পপুলেট করা হয়েছে। এছাড়াও ওপিতে অতিরিক্ত শূন্য রয়েছে TestMethod2যা সর্বদা এটি দশগুণ কম করে তোলে।

আপনি যে দুটি সরবরাহ করেছিলেন সেগুলি ছাড়াও আমি আরও কয়েকটি পদ্ধতি চেষ্টা করেছি। পদ্ধতি 3 তে আপনার পদ্ধতি 2 এর মতোই কোড রয়েছে তবে ফাংশনটি ঘোষিত হয়েছে unsafe। পদ্ধতি 4 নিয়মিত তৈরি অ্যারে পয়েন্টার অ্যাক্সেস ব্যবহার করে। পদ্ধতি 5 ম্যানেজড মেমরিটিতে পয়েন্টার অ্যাক্সেস ব্যবহার করছে, যা মার্ক গ্র্যাভেলের বর্ণিত। সমস্ত পাঁচটি পদ্ধতি খুব একই সময়ে চালিত হয়। এম 5 দ্রুততম (এবং এম 1 নিকটে দ্বিতীয়)। দ্রুততম এবং ধীরের মধ্যে পার্থক্যটি প্রায় 5%, যা আমার যত্ন নেওয়ার মতো নয়।

    public static unsafe float TestMethod3()
    {
        float[] samples = new float[5000000];

        for (var ii = 0; ii < 5000000; ii++)
        {
            samples[ii] = 32768 + (ii != 0 ? samples[ii - 1] : 0);
        }

        return samples[5000000 - 1];
    }

    public static unsafe float TestMethod4()
    {
        float[] prev = new float[5000000];
        fixed (float* samples = &prev[0])
        {
            for (var ii = 0; ii < 5000000; ii++)
            {
                samples[ii] = 32768 + (ii != 0 ? samples[ii - 1] : 0);
            }

            return samples[5000000 - 1];
        }
    }

    public static unsafe float TestMethod5()
    {
        var ptr = Marshal.AllocHGlobal(5000000 * sizeof(float));
        try
        {
            float* samples = (float*)ptr;

            for (var ii = 0; ii < 5000000; ii++)
            {
                samples[ii] = 32768 + (ii != 0 ? samples[ii - 1] : 0);
            }

            return samples[5000000 - 1];
        }
        finally
        {
            Marshal.FreeHGlobal(ptr);
        }
    }

সুতরাং এম 3 এম 2 এর মতোই কেবল "অনিরাপদ" হিসাবে চিহ্নিত? বরং সন্দেহজনক যে এটি আরও দ্রুততর হবে ... আপনি কি নিশ্চিত?
রোমান স্টারকভ

@ ক্রমকিন্স আমি সবেমাত্র একটি বেঞ্চমার্ক চালিয়েছি (এম 2 বনাম এম 3), এবং আশ্চর্যজনকভাবে এম 3 এম 2 এর তুলনায় আসলে 2.14% দ্রুত।
স্যাম

" উপসংহারটি হ'ল স্ট্যাকটি ব্যবহার করার দরকার নেই " "যখন আমি আমার পোস্টে দিয়েছিলাম তার মতো বড় ব্লকগুলি বরাদ্দ করার সময় আমি সম্মত হয়েছি, তবে, এম 2 বনাম এম 2 এর আরও কিছু বেঞ্চমার্ক শেষ করার পরে ( উভয় পদ্ধতির জন্য পিএফএমের ধারণাটি ব্যবহার করে) আমি অবশ্যই করব একমত হতে হবে, যেহেতু এম 1 এখন এম 2 এর চেয়ে 135% দ্রুত faster
স্যাম

1
@ সাম কিন্তু আপনি এখনও অ্যারে অ্যাক্সেসের পয়েন্টার অ্যাক্সেসের সাথে তুলনা করছেন! যে primarly এটা তোলে কি দ্রুততর। TestMethod4বনাম TestMethod1একটি আরও ভাল তুলনা হয় stackalloc
রোমান স্টারকভ

@ ক্রমিন্স হ্যাঁ হ্যাঁ ভাল কথা, আমি সে সম্পর্কে ভুলে গেছি; আমি মাপদণ্ডটি পুনরায় চালিয়েছি , এখন মাত্র 8% পার্থক্য রয়েছে (এম 1 দুটির চেয়ে দ্রুততর হচ্ছে)।
স্যাম
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.