मैं एक सूची में बहुत अधिक डेटा जोड़ने की कोशिश कर रहा हूं, लेकिन ऐसा लगता है कि यह एक सरणी की तुलना में बहुत अधिक रैम का उपयोग करता है। मैं सोच रहा था कि ऐसा क्यों है और यदि कोई बेहतर समाधान है।

सरणी के साथ यह समाधान लगभग 78 एमबी रैम लेता है। 4 बाइट * 20000000 ~ = 76 एमबी के बाद से समझ में आता है:

float[] arrayValues = new float[20000000];

for (int i = 0; i < 20000000; i++)
     arrayValues[i] = i;

लेकिन सूची के साथ यह समाधान 206 एमबी (!!) लेता है:

List<float> listValues = new();

for (int i = 0; i < 20000000; i++)
     listValues.Add(i);

ऐसे कैसे हो सकता है? यह मूल रूप से वही काम कर रहा है - 20000000 फ्लोट मानों को सहेज रहा है। वह अतिरिक्त 128 एमबी कहां से आ रहा है? और क्या कोई बेहतर तरीका है जो इतना अधिक उत्पादन नहीं करता है?

2
Zelos 26 नवम्बर 2021, 14:47
6
आप सूची के स्मृति उपयोग को कैसे माप रहे हैं? आपकी सूची को कई बार आकार बदलने की आवश्यकता होगी क्योंकि यह गतिशील रूप से बढ़ती है - क्या यह संभव है कि आप उन पुराने, बहुत छोटे सरणियों को शामिल कर रहे हैं जिन्हें आपके माप में छोड़ दिया गया है (लेकिन अभी तक कचरा एकत्र नहीं किया गया है)? क्या आपने सूची को प्रारंभिक क्षमता, यानी new(20000000) देने का प्रयास किया है?
 – 
canton7
26 नवम्बर 2021, 14:48
7
संकेत var listValues = new List<float>(20000000)
 – 
Guru Stron
26 नवम्बर 2021, 14:49
1
a List use almost 3x the memory of an array? ऐसा नहीं है। एक List एक सरणी का उपयोग करता है। आपका कोड हालांकि आंतरिक सरणी के एक टन के पुनर्वितरण का कारण बनता है
 – 
Panagiotis Kanavos
26 नवम्बर 2021, 14:51
1
एक बॉलपार्क के रूप में, List<T> आकार 4 की एक सरणी के साथ शुरू होता है और हर बार अंतरिक्ष से बाहर होने पर इसे दोगुना कर देता है, इसलिए 20000000 तत्वों के लिए यह अपने आंतरिक सरणी को लगभग 23 बार आकार दे रहा है। तो वहाँ 22-ईश पुराने सरणियाँ बैठे हैं, जिनका आकार 4 बाइट्स से लेकर 16.8M तक है, जिन्हें अभी तक पुनः प्राप्त नहीं किया गया है। सूची को प्रारंभिक क्षमता देने से उन सभी से बचा जाता है, और यह शुरुआत में सही आकार की एक सरणी आवंटित करता है।
 – 
canton7
26 नवम्बर 2021, 14:54
1
खुद देखें: source.dot.net/#System.Private। CoreLib/List.cs,cf7f4095e4de7646, या दस्तावेज़: docs.microsoft.com/en-us/dotnet/api/… -- "यह IList<T> जेनेरिक इंटरफेस को किसके द्वारा लागू करता है एक सरणी का उपयोग करना जिसका आकार आवश्यकतानुसार गतिशील रूप से बढ़ाया जाता है।" । यह एक सूची है, लेकिन यह एक लिंक की गई सूची नहीं है, अगर आप इससे भ्रमित हो रहे हैं।
 – 
canton7
26 नवम्बर 2021, 14:57

1 उत्तर

सबसे बढ़िया उत्तर

जब आप Add नए आइटम को List<T> में बदलते हैं तो उसे इन नए आइटम के लिए पर्याप्त जगह रखने के लिए मेमोरी रीलोकेशन करना पड़ता है। आइए प्रक्रिया पर एक नजर डालते हैं:

  List<float> listValues = new();

  int capacity = listValues.Capacity;

  for (int i = 0; i < 20000000; i++) {
    listValues.Add(i);

    if (capacity != listValues.Capacity) {
      capacity = listValues.Capacity;

      Console.WriteLine(capacity);
    }
  }

परिणाम:

4
8
16
32
64
128
256
512
1024
2048
4096
8192
16384
32768
65536
131072
262144
524288
1048576
2097152
4194304
8388608
16777216
33554432 // <- Finally, list allocates memory for 33554432 items

जैसा कि आप देख सकते हैं, अब 33554432 आइटम आवंटित किए गए हैं और 4 + 8 + 16 + ... + 16777216 कचरा हैं। हमारे पास सबसे खराब स्थिति है 33554432 आवंटित आइटम और 33554432 कचरा आइटम; कुल 33554432 + 33554432 = 67108864 ~ 3 * 20000000 और आप यह 3 कारक देख सकते हैं।

तुम क्या कर सकते हो?

पुनः आवंटन (सामान्य समाधान) से बचने के लिए Capacity निर्दिष्ट करें:

  // We can avoid all this mess with reallocations
  // by specifing required capacity: 20000000 items in our case 
  List<float> listValues = new(20000000);

  for (int i = 0; i < 20000000; i++) {
    listValues.Add(i);
  }

मापने से पहले सभी कचरा इकट्ठा करें:

  // Business as usual
  List<float> listValues = new();

  for (int i = 0; i < 20000000; i++) {
    listValues.Add(i);
  }

  // Collect garbage to measure real List efficency:
  // List allocates 33554432 items vs. 20000000 in case of array 
  // About ~70% overhead 
  GC.Collect(2);
4
Dmitry Bychenko 26 नवम्बर 2021, 15:40
विस्तृत स्पष्टीकरण के लिए धन्यवाद, मुझे अब मिल गया। GC.Collect() को कॉल करने से वास्तव में RAM का उपयोग 78 MB तक कम हो जाता है। लेकिन मैंने देखा है कि बाद में इसे फिर से कॉल करने पर, RAM उपयोग पहले की तरह वापस नहीं जाता है। सूची भरने से पहले मैं 8 एमबी पर हूं, और बाद में मैं 15 एमबी पर हूं। यह कभी पीछे नहीं हटता, और 15 एमबी नई आधार रेखा प्रतीत होती है। ऐसा क्यों है?
 – 
Zelos
26 नवम्बर 2021, 15:26
1
@Zelos: अतिरिक्त 15 एमबी कुछ स्थिर डेटा हो सकता है (कहें, असेंबली लोड से)। आप List<T> - System.Collections.Generic और .net लोडेड असेंबली (एस) का उपयोग प्रकार, स्थिर क्षेत्रों, संसाधनों आदि के साथ शुरू करते हैं।
 – 
Dmitry Bychenko
26 नवम्बर 2021, 15:29
वस्तुओं को आवंटित करते समय वे स्मृति में विभिन्न स्थानों पर समाप्त हो सकते हैं। यह इस बात पर निर्भर करता है कि वे कितने समय से स्मृति में हैं और ये वस्तुएं कितनी बड़ी हैं। और कई वस्तुओं को बनाते समय, स्मृति खंडित हो सकती है, (उदाहरण के लिए उपयोग की जाने वाली कुल स्मृति में छेद हैं जहां कुछ उपलब्ध स्मृति मौजूद है)। सबसे अच्छा अभ्यास यह है कि आप जितना हो सके कम आवंटन करें, इसलिए आपके मामले में एक बड़ी सूची को पूर्व-आवंटित करना बेहतर है यदि आपको स्थान की आवश्यकता होगी।
 – 
jessehouwing
26 नवम्बर 2021, 15:32
 – 
jessehouwing
26 नवम्बर 2021, 15:34
याद रखें कि .NET GC एक संकुचित कचरा संग्रहकर्ता है - GC के दौरान विखंडन को नियंत्रित किया जाता है। LOH को छोड़कर, निश्चित रूप से 85k से अधिक की वस्तुओं के साथ।
 – 
canton7
26 नवम्बर 2021, 15:59