50x के डिफ़ॉल्ट आकार के साथ एक धागा बनाते समय क्या खतरे हैं?


228

मैं वर्तमान में एक बहुत ही महत्वपूर्ण क्रिटिकल प्रोग्राम और एक पथ पर काम कर रहा हूँ, जिसका मैंने यह पता लगाने का निर्णय लिया है कि संसाधन की खपत को कम करने में मदद मिल सकती है, जिससे मेरे वर्कर थ्रेड्स का स्टैक साइज़ बढ़ रहा है, इसलिए मैं अधिकांश डेटा ( float[]ओं) को स्थानांतरित कर सकता हूँ, जिन पर मैं आरोप लगा रहा हूँ स्टैक (उपयोग करके stackalloc)।

मैंने पढ़ा है कि एक थ्रेड के लिए डिफ़ॉल्ट स्टैक का आकार 1 एमबी है, इसलिए अपने सभी float[]एस को स्थानांतरित करने के लिए मुझे स्टैक का लगभग 50 गुना (50 एमबी ~) तक विस्तार करना होगा।

मैं समझता हूं कि इसे आम तौर पर "असुरक्षित" माना जाता है और इसकी सिफारिश नहीं की जाती है, लेकिन इस पद्धति के खिलाफ मेरे वर्तमान कोड को बेंचमार्क करने के बाद, मैंने प्रसंस्करण गति में 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*
मार्क Gravell

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

6
मेरा संदेह: इनमें से एक तरीका हर लूप पुनरावृत्ति पर सीमा-जाँच को ट्रिगर करता है जबकि दूसरा नहीं करता है, या इसे दूर अनुकूलित किया जाता है।
pjc50

जवाबों:


45

सैम के साथ परीक्षण कोड की तुलना करने पर, मैंने निर्धारित किया कि हम दोनों सही हैं!
हालांकि, विभिन्न चीजों के बारे में:

  • मेमोरी तक पहुँचना (पढ़ना और लिखना) जहाँ कहीं भी है , उतनी ही तेज़ है - स्टैक, ग्लोबल या हीप।
  • का आवंटन यह, हालांकि, सबसे तेजी से ढेर पर ढेर पर सबसे धीमी है और।

यह इस प्रकार है: stack< global< heap। (आवंटन समय)
तकनीकी रूप से, स्टैक आवंटन वास्तव में एक आवंटन नहीं है, रनटाइम बस स्टैक (फ़्रेम) का एक हिस्सा सुनिश्चित करता है जो कि सरणी के लिए आरक्षित है।

मैं दृढ़ता से इस के साथ सावधान रहने की सलाह देता हूं, हालांकि।
मैं निम्नलिखित सलाह देता हूं:

  1. जब आपको अक्सर एरे बनाने की आवश्यकता होती है जो फ़ंक्शन को कभी नहीं छोड़ता (जैसे इसके संदर्भ को पास करके), स्टैक का उपयोग करना एक बहुत बड़ा सुधार होगा।
  2. यदि आप किसी सरणी को रीसायकल कर सकते हैं, तो जब भी आप ऐसा कर सकते हैं! लंबी अवधि के वस्तु भंडारण के लिए ढेर सबसे अच्छी जगह है। (प्रदूषणकारी वैश्विक मेमोरी अच्छी नहीं है; स्टैक फ्रेम गायब हो सकते हैं)

( नोट : 1. केवल मूल्य प्रकारों पर लागू होता है; संदर्भ प्रकार ढेर पर आवंटित किए जाएंगे और लाभ 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.

मैंने विंडोज 8.1 प्रो (अपडेट 1 के साथ) पर परीक्षण किया, एक i7 4700 एमक्यू का उपयोग करते हुए, .NET 4.5.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% से कम अंतर; उतार-चढ़ाव) हैं, जबकि आपका वास्तव में एक स्टैक के साथ बहुत तेज़ है। मुझे और परीक्षण करने की आवश्यकता है! संपादित करें : आपका SHOULD एक ढेर अतिप्रवाह अपवाद फेंक सकता है। आप केवल सरणी के लिए पर्याप्त आवंटित करते हैं। O_o
वर्सस

हाँ, मुझे पता है, यह करीब है। आपको कुछ बार बेंचमार्क दोहराने की आवश्यकता है, जैसे मैंने किया, शायद औसतन 5 या उससे अधिक रन लेने की कोशिश करें।
सैम

1
@Voo 1 रन ने मेरे लिए किसी भी टेस्ट के 100 वें रन के रूप में ज्यादा समय लिया। मेरे अनुभव से, यह जावा JIT बात .NET पर बिल्कुल भी लागू नहीं होती है। पहली बार उपयोग किए जाने वाले .NET "वार्म अप" को पहली बार उपयोग किए जाने पर कक्षाएं और असेंबली लोड कर रही हैं।
वरकस

2
@Voo मेरे बेंचमार्क का परीक्षण करें और इस उत्तर के लिए एक टिप्पणी में उन्होंने जो जिस्ट से जोड़ा है। कोडों को एक साथ इकट्ठा करें और कुछ सौ परीक्षण चलाएं। फिर वापस आकर अपने निष्कर्ष की रिपोर्ट करें। मैंने अपने परीक्षण बहुत अच्छी तरह से किए हैं, और मुझे अच्छी तरह से पता है कि मैं किस बारे में बात कर रहा हूं जब यह कह रहा हूँ कि .NET किसी भी बायोटेक की व्याख्या नहीं करता है जैसे कि जावा करता है, यह तुरंत JITs करता है।
वरकस

28

मैंने प्रसंस्करण गति में 530% वृद्धि की खोज की है!

यह अब तक का सबसे बड़ा खतरा है। आपके बेंचमार्क के साथ कुछ गंभीर रूप से गलत है, कोड जो इस अप्रत्याशित व्यवहार करता है आमतौर पर एक बुरा बग कहीं छिपा हुआ है।

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

स्टैक का आकार बढ़ाने से कुछ भी पूरा नहीं होता है, आप केवल पता स्थान का एक गुच्छा आरक्षित करेंगे जिसका उपयोग कभी नहीं किया जाएगा। ऐसा कोई तंत्र नहीं है जो पाठ्यक्रम की स्मृति का उपयोग न करने से एक परिपूर्ण वृद्धि की व्याख्या कर सके।

यह एक मूल कार्यक्रम के विपरीत है, विशेष रूप से सी में लिखा गया है, यह स्टैक फ्रेम पर सरणियों के लिए स्थान भी आरक्षित कर सकता है। स्टैक बफर ओवरफ्लो के पीछे मूल मैलवेयर अटैक वेक्टर। C # में भी संभव है, आपको stackallocकीवर्ड का उपयोग करना होगा । यदि आप ऐसा कर रहे हैं, तो स्पष्ट खतरा असुरक्षित कोड लिखने का है, जो इस तरह के हमलों के साथ-साथ यादृच्छिक स्टैक फ्रेम भ्रष्टाचार के अधीन है। कीड़े का निदान करने के लिए बहुत मुश्किल है। बाद के जिटर्स में इसके खिलाफ एक काउंटर-माप है, मुझे लगता है कि .NET 4.0 से शुरू हो रहा है, जहां घबराना स्टैक फ्रेम पर "कुकी" डालने के लिए कोड उत्पन्न करता है और जांचता है कि विधि वापस आने पर यह अभी भी बरकरार है या नहीं। बिना किसी अवरोध के डेस्कटॉप पर त्वरित दुर्घटना या दुर्घटना होने पर दुर्घटना की सूचना देना। यह ... उपयोगकर्ता की मानसिक स्थिति के लिए खतरनाक है।

आपके प्रोग्राम का मुख्य सूत्र, ऑपरेटिंग सिस्टम द्वारा शुरू किया गया, डिफ़ॉल्ट रूप से 1 एमबी स्टैक होगा, जब आप अपने प्रोग्राम को x 64 को लक्षित करते हुए संकलित करते हैं। पोस्ट बिल्ड ईवेंट में / STACK विकल्प के साथ Editbin.exe को चलाने की आवश्यकता बढ़ जाती है। 32-बिट मोड में चलने पर आपके प्रोग्राम को शुरू करने में परेशानी होने से पहले आप आमतौर पर 500 एमबी तक पूछ सकते हैं। थ्रेड्स, बहुत आसान हो सकता है, खतरे का क्षेत्र आमतौर पर 32-बिट प्रोग्राम के लिए लगभग 90 एमबी तक बढ़ जाता है। जब आपका प्रोग्राम लंबे समय से चल रहा हो और पता चला हो तो पिछले आवंटन से खंडित हो गया था। कुल पता स्थान का उपयोग इस विफलता मोड को प्राप्त करने के लिए पहले से ही एक टमटम पर उच्च होना चाहिए।

अपने कोड को ट्रिपल-चेक करें, कुछ बहुत गलत है। जब तक आप स्पष्ट रूप से इसका लाभ लेने के लिए अपना कोड नहीं लिखेंगे, आपको एक बड़े स्टैक के साथ x5 स्पीडअप नहीं मिल सकता है। जिसे हमेशा असुरक्षित कोड की आवश्यकता होती है। C # में पॉइंटर्स का उपयोग करना हमेशा तेज कोड बनाने के लिए एक आदत है, यह सरणी सीमा की जाँच के अधीन नहीं है।


21
बताया गया 5x स्पीडअप से जाने से float[]था float*। बड़े स्टैक बस था कि कैसे पूरा किया गया था। कुछ परिदृश्यों में x5 स्पीडअप उस परिवर्तन के लिए पूरी तरह से उचित है।
मार्क Gravell

3
ठीक है, मेरे पास कोड स्निपेट नहीं था फिर भी जब मैंने सवाल का जवाब देना शुरू किया। अभी भी काफी करीब है।
हंस पसंत

22

मेरा वहां एक आरक्षण होगा जो मुझे नहीं पता होगा कि इसका पूर्वानुमान कैसे लगाया जाए - अनुमतियाँ, जीसी (जिसे स्टैक को स्कैन करने की आवश्यकता है), आदि - सभी प्रभावित हो सकते हैं। मैं इसके बजाय अप्रबंधित स्मृति का उपयोग करने के लिए बहुत लुभाऊंगा:

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

1
साइड सवाल: जीसी को स्टैक को स्कैन करने की आवश्यकता क्यों होगी? द्वारा आवंटित मेमोरी stackallocकचरा संग्रहण के अधीन नहीं है।
dcastro

6
@ डस्ट्रो को केवल संदर्भ के लिए जाँच करने के लिए स्टैक को स्कैन करना होगा जो केवल स्टैक पर मौजूद है। मैं बस यह नहीं जानता कि यह क्या करने जा रहा है जब यह इतना बड़ा हो जाता है stackalloc- यह थोड़े इसे कूदने की जरूरत है, और आपको उम्मीद है कि यह इतनी सहजता से करेगा - लेकिन मैं जो बनाने की कोशिश कर रहा हूं वह यह है कि यह परिचय देता है अनावश्यक जटिलताओं / चिंताओं। IMO, stackallocएक खरोंच-बफर के रूप में महान है, लेकिन एक समर्पित कार्यक्षेत्र के लिए, इसे और अधिक होने की संभावना है सिर्फ एक हिस्सा-ओ-स्मृति कहीं आवंटित, बल्कि कोस / ढेर भ्रमित से,
मार्क Gravell

8

एक बात जो गलत हो सकती है वह यह है कि आपको ऐसा करने की अनुमति नहीं मिल सकती है। जब तक पूर्ण-विश्वास मोड में नहीं चल रहा है, फ्रेमवर्क केवल एक बड़े स्टैक आकार के अनुरोध को अनदेखा करेगा (देखें MSDN पर Thread Constructor (ParameterizedThreadStart, Int32))

सिस्टम स्टैक आकार को इतनी बड़ी संख्या में बढ़ाने के बजाय, मैं आपके कोड को फिर से लिखने का सुझाव दूंगा ताकि यह Iteration और ढेर पर एक मैनुअल स्टैक कार्यान्वयन का उपयोग करे।


1
अच्छा विचार है, मैं इसके बजाय के माध्यम से iterate करेंगे। इसके अलावा, मेरा कोड पूर्ण विश्वास मोड में चल रहा है, इसलिए क्या कोई अन्य चीजें हैं जिन्हें मुझे देखना चाहिए?
सैम

6

उच्च निष्पादनकर्ता सरणियाँ सामान्य C # एक की तरह ही सुलभ हो सकती हैं, लेकिन यह परेशानी की शुरुआत हो सकती है: निम्नलिखित कोड पर विचार करें:

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

आप बाध्य अपवाद से बाहर निकलने की उम्मीद करते हैं और यह पूरी तरह से समझ में आता है क्योंकि आप तत्व 200 तक पहुंचने की कोशिश कर रहे हैं, लेकिन अधिकतम अनुमत मूल्य 99 है। यदि आप स्टैकलॉक मार्ग पर जाते हैं, तो आपके सरणी के चारों ओर लिपटे हुए चेक और कोई वस्तु नहीं होगी निम्नलिखित कोई अपवाद नहीं दिखाएगा:

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

ऊपर आप 100 फ़्लैट रखने के लिए पर्याप्त मेमोरी आवंटित कर रहे हैं और आप अपने फ़्लोट मान को रखने के लिए साइज़ोफ़ (फ़्लोट) मेमोरी लोकेशन शुरू कर रहे हैं जो इस मेमोरी + 200 * साइज़ोफ़ (फ़्लोट) के स्थान पर शुरू होती है। अनजाने में यह मेमोरी बाहर है झांकियों के लिए आवंटित स्मृति और किसी को नहीं पता होगा कि उस पते में क्या संग्रहीत किया जा सकता है। यदि आप भाग्यशाली हैं तो आपने कुछ वर्तमान में अप्रयुक्त मेमोरी का उपयोग किया हो सकता है, लेकिन साथ ही यह संभावना है कि आप कुछ स्थान को अधिलेखित कर सकते हैं जो अन्य चर को संग्रहीत करने के लिए उपयोग किया गया था। संक्षेप में: अप्रत्याशित रनटाइम व्यवहार।


तथ्यात्मक रूप से गलत है। रनटाइम और कंपाइलर टेस्ट अभी बाकी हैं।
टॉमटॉम

9
@TomTom erm, नहीं; उत्तर में योग्यता है; प्रश्न इस बारे stackallocमें बात float*करता है कि हम किस मामले में बात कर रहे हैं - जिसके पास समान चेक नहीं हैं। यह unsafeएक बहुत अच्छे कारण के लिए कहा जाता है । व्यक्तिगत रूप से मैं unsafeएक अच्छा कारण होने पर उपयोग करने के लिए पूरी तरह से खुश हूं , लेकिन सुकरात कुछ उचित बिंदु बनाता है।
मार्क ग्रेवेल

@Marc दिखाए गए कोड के लिए (JIT चलने के बाद) कोई और अधिक बाउंड चेक नहीं हैं क्योंकि यह कंपाइलर के लिए तुच्छ है कि सभी एक्सेस इन-सीमा हैं। सामान्य तौर पर हालांकि यह निश्चित रूप से फर्क कर सकता है।
वू

6

JIT और GC जैसे जावा या C # के साथ माइक्रोबेन्चमार्किंग भाषा थोड़ी जटिल हो सकती है, इसलिए आमतौर पर मौजूदा ढांचे का उपयोग करना एक अच्छा विचार है - जावा mhf या कैलीपर प्रदान करता है जो मेरे ज्ञान के सर्वश्रेष्ठ # के लिए उत्कृष्ट, दुख की बात है # प्रदान नहीं करता है उन लोगों के पास कुछ भी। जॉन स्कीट ने इसे यहां लिखा था, जिसे मैं आँख बंद करके मानूंगा कि सबसे महत्वपूर्ण चीजों पर ध्यान दिया जाता है (जॉन को पता है कि वह उस क्षेत्र में क्या कर रहा है; यह भी कोई चिंता नहीं कि मैंने वास्तव में जांच की थी)। मैंने समय को थोड़ा कम किया क्योंकि वार्मअप के बाद 30 सेकंड प्रति परीक्षण मेरे धैर्य के लिए बहुत अधिक था (5 सेकंड के लिए करना चाहिए)।

तो पहले परिणाम, .NET 4.5.1 विंडोज 7 x64 के तहत - संख्याएं पुनरावृत्तियों को निरूपित करती हैं जो इसे 5 सेकंड में चला सकती है इसलिए उच्चतर बेहतर है।

x64 JIT:

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 JIT (हाँ, यह अभी भी दुख की बात है):

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
@Sam जब 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

हम निर्देश के उपयोग की जांच कर सकते हैं और इससे भी महत्वपूर्ण बात यह है कि अपवाद वे ECMA कल्पना में फेंक देते हैं :

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

संपादित करें: (कोड में और माप में छोटा परिवर्तन परिणाम में बड़ा परिवर्तन पैदा करता है)

सबसे पहले मैंने डीबगर (F5) में अनुकूलित कोड चलाया लेकिन वह गलत था। इसे डिबगर (Ctrl + F5) के बिना चलाया जाना चाहिए। दूसरा, कोड को पूरी तरह से अनुकूलित किया जा सकता है, इसलिए हमें इसे जटिल करना चाहिए ताकि अनुकूलक हमारे मापन के साथ खिलवाड़ न करे। मैंने सभी तरीकों को सरणी में एक अंतिम आइटम लौटाया, और सरणी अलग तरह से आबाद है। इसके अलावा ओपी में एक अतिरिक्त शून्य है TestMethod2जो हमेशा इसे दस गुना धीमा बनाता है।

मैंने आपके द्वारा प्रदान किए गए दो के अलावा कुछ अन्य तरीकों की कोशिश की। विधि 3 में आपकी विधि 2 के समान कोड है, लेकिन फ़ंक्शन घोषित किया गया है unsafe। विधि 4 नियमित रूप से बनाई गई सरणी के लिए पॉइंटर एक्सेस का उपयोग कर रही है। विधि 5, अप्रबंधित मेमोरी के लिए पॉइंटर एक्सेस का उपयोग कर रही है, जैसा कि मार्क ग्रेवेल द्वारा वर्णित है। सभी पाँच विधियाँ बहुत समान समय में चलती हैं। M5 सबसे तेज़ है (और M1 करीब दूसरा है)। सबसे तेज़ और सबसे धीमी के बीच का अंतर कुछ 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);
        }
    }

तो M3 केवल M2 के समान है जो "असुरक्षित" के साथ चिह्नित है? बल्कि संदेह है कि यह कोई तेज़ होगा ... क्या आप सुनिश्चित हैं?
रोमन स्टार्कोव

@romkyns मैंने अभी एक बेंचमार्क (एम 2 बनाम एम 3) चलाया है, और आश्चर्यजनक रूप से एम 3 वास्तव में एम 2 की तुलना में 2.14% तेज है।
सैम

" निष्कर्ष यह है कि स्टैक का उपयोग करने की आवश्यकता नहीं है। " जब मैं अपने पोस्ट में दिए गए बड़े ब्लॉकों को आवंटित करता हूं, तो मैं सहमत हूं, लेकिन, एम 2 बनाम एम 2 के कुछ और बेंचमार्क को पूरा करने के बाद ( दोनों तरीकों के लिए पीएफएम के विचार का उपयोग करके ) मैं निश्चित रूप से करूंगा। असहमत होना चाहिए, क्योंकि एम 1 अब एम 2 की तुलना में 135% तेज है।
सैम

1
@ सलाम लेकिन आप अभी भी सरणी एक्सेस के लिए सूचक पहुंच की तुलना कर रहे हैं! कि मुख्य रूप से यह बनाता है क्या तेज है। TestMethod4बनाम के TestMethod1लिए एक बेहतर तुलना है stackalloc
रोमन स्टार्कोव

@romkyns आह हाँ अच्छी बात है, मैं उस बारे में भूल गया; मैंने बेंचमार्क फिर से चलाया है , अब केवल 8% का अंतर है (एम 1 दोनों में सबसे तेज है)।
सैम
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.