JSON मेमोरी लेजर: स्ट्रिंग्स, ऑब्जेक्ट्स और बड़े ऐरे असल में कितना खर्च करते हैं
मुख्य बात: एक 10MB JSON फ़ाइल V8 हीप में 30-50MB हो सकती है। जानें कि स्ट्रिंग्स, ऑब्जेक्ट्स और संख्याएँ असल में कितना खर्च करते हैं, process.memoryUsage से बेसलाइन कैसे बनाएँ, और स्ट्रीमिंग कब मदद करता है।

मुख्य निष्कर्ष: पहले process.memoryUsage से बेसलाइन बनाएँ, टेक्स्ट और ऑब्जेक्ट ग्राफ़ को दो अलग-अलग वृद्धियों के रूप में दर्ज करें, फिर कुछ और आज़माने से पहले एक पूरी कॉपी हटा दें — न JSON.parse(JSON.stringify(x)), न कच्चे दस्तावेज़ की रिएक्टिव रैपिंग। इसके बाद लंबे समय तक जीवित संदर्भों को काटें, और स्ट्रीमिंग पर अंत में विचार करें: इसकी सपाट मेमोरी वक्र तभी रैंडम एक्सेस खोने के लायक है जब फ़ाइल फ़िट न हो या आपको बस कुछ फ़ील्ड चाहिए।
एक 10MB JSON दस्तावेज़
JSON.parseके बाद हीप में 30MB से 50MB हो सकता है, और क्षणिक शिखर तो और भी ऊँचा जाता है। यह गणित किए बिना, "अनुकूलन" अनुमान है।
1. टेक्स्ट और ऑब्जेक्ट ग्राफ़ दो अलग-अलग लेजर हैं
डिस्क पर और अनुरोध बॉडी में, JSON बाइट्स का एक क्रम है। मेमोरी में आते ही यह दो चीज़ें बन जाती है: एक स्ट्रिंग, और ऑब्जेक्ट्स, ऐरे, संख्याओं व बूलियन से बना एक ऑब्जेक्ट ग्राफ़। इन दो लेजरों को अलग रखना ही एकमात्र तरीका है जिससे यह समझाया जा सके कि 10MB फ़ाइल किसी प्रक्रिया को 80MB क्यों खिला देती है।
| डेटा आकार | ऑब्जेक्ट ग्राफ़ बनाम टेक्स्ट आकार | प्रमुख लागत |
|---|---|---|
| समतल ऑब्जेक्ट ऐरे (प्रत्येक लगभग 8 फ़ील्ड) | लगभग 2.5-4 गुना | प्रति एलिमेंट एक ऑब्जेक्ट हेडर और प्रॉपर्टी स्लॉट |
| गहराई से नेस्टेड कॉन्फ़िगरेशन | लगभग 3-5 गुना | बहुत से छोटे ऑब्जेक्ट, प्रत्येक का अपना हेडर |
| बहुत लंबी स्ट्रिंग्स का ऐरे | लगभग 1.2-2 गुना | स्ट्रिंग हेडर प्लस कैरेक्टर डेटा |
| दशमलव वाली बड़ी संख्याएँ | लगभग 2-3 गुना | प्रति मान एक HeapNumber |
| बहुत बार दोहराई गई कुंजियों वाले दस्तावेज़ | कुंजी गिनती के अनुपात में | पार्सिंग के दौरान कुंजी हैशिंग और प्रॉपर्टी सेटअप |
ये गुणक परिमाण-कोटि के संदर्भ हैं जो V8 संस्करण, पॉइंटर कम्प्रेशन, फ़ील्ड गिनती और स्ट्रिंग सामग्री पर निर्भर करते हैं। तालिका एक बात कहती है: ऑब्जेक्ट ग्राफ़ का आकार "नोड गिनती x प्रति-नोड ओवरहेड" से तय होता है, टेक्स्ट के बाइट्स की संख्या से नहीं।
2. स्ट्रिंग्स: सबसे अधिक कम आँका गया मद
V8 स्ट्रिंग कोई नंगा कैरेक्टर ऐरे नहीं है। इसमें एक ऑब्जेक्ट हेडर होता है (एक map पॉइंटर, एक हैश फ़ील्ड, और एक लंबाई), जो पॉइंटर कम्प्रेशन वाले सामान्य 64-बिट बिल्ड पर किसी भी कैरेक्टर से पहले लगभग एक दर्जन बाइट्स खर्च करता है। कैरेक्टर दो तरीकों में से एक में संग्रहित होते हैं:
- वन-बाइट स्ट्रिंग्स Latin-1 श्रेणी के कैरेक्टरों के लिए: प्रति कैरेक्टर 1 बाइट;
- टू-बाइट स्ट्रिंग्स जब सामग्री में CJK कैरेक्टर, इमोजी, या Latin-1 से बाहर का कुछ भी हो: प्रति कैरेक्टर 2 बाइट।
एक चीनी कैरेक्टर UTF-8 फ़ाइल में 3 बाइट घेरता है, लेकिन टू-बाइट स्ट्रिंग में प्रति कैरेक्टर 2 बाइट, साथ में हेडर। टेक्स्ट आकार और हीप आकार एक ही माप नहीं हैं, और वे हमेशा एक ही दिशा में नहीं बढ़ते।
तीन विवरण JSON कार्य के लिए सीधे मायने रखते हैं:
- स्लाइस मूल को जीवित रखते हैं। V8 लंबे सबस्ट्रिंग को
SlicedStringके रूप में दर्शाता है, जो एक मूल संदर्भ के साथ एक ऑफ़सेट और एक लंबाई संग्रहित करता है। किसी 80MB लॉग में से काटे गए 200 कैरेक्टर संग्रहित करें तो वह स्लाइस रिलीज़ होने तक पूरा 80MB पहुँच-योग्य बना रहता है। - कन्कैटनेशन
ConsStringबनाता है। बड़ी स्ट्रिंग्स पर बार-बार+=करने से टुकड़ों का एक ट्री बनता है जो केवल तभी सन्निहित मेमोरी में समतल होता है जब कैरेक्टर वास्तव में पढ़े जाते हैं। दस हज़ार बार जोड़कर फिर एक बार पढ़ने पर उस क्षण बड़ा आवंटन उछाल आता है। - दोहराए गए मान स्वतः साझा नहीं होते।
"status": "paid"वाले दस हज़ार रिकॉर्ड को आम तौर पर दस हज़ार स्वतंत्र स्ट्रिंग ऑब्जेक्ट मिलते हैं; पार्सर उन्हें एक स्थिरांक पर नहीं इंगित करता। वह लागत बचाने के लिए आपको बार-बार आने वाली स्ट्रिंग्स को स्वयं enum या संख्याओं पर मैप करना होगा।
जब आपको कोई छोटा अंश लंबे समय तक रखना हो, तो उसे एक स्वतंत्र स्ट्रिंग बनाएँ और मूल को रिलीज़ करें:
// ख़राब: स्लाइस पूरे दस्तावेज़ का संदर्भ रखता है, इसलिए logText एकत्र नहीं हो सकता
const summary = logText.slice(0, 200);
// अच्छा: एक स्वतंत्र स्ट्रिंग बनाएँ, फिर logText रिलीज़ करें
const summaryCopy = Buffer.from(summary, 'utf8').toString('utf8');
3. ऑब्जेक्ट्स: हर कुंजी एक स्लॉट घेरती है
एक सादे ऑब्जेक्ट में कम से कम तीन चीज़ें होती हैं: उसकी छिपी क्लास (map) के लिए एक पॉइंटर, उसके प्रॉपर्टी स्टोर के लिए एक पॉइंटर, और उसके एलिमेंट स्टोर के लिए एक पॉइंटर। पॉइंटर कम्प्रेशन के साथ हर पॉइंटर 4 बाइट है, और हीप ऑब्जेक्ट हेडर और जोड़ता है, इसलिए एक खाली ऑब्जेक्ट भी लगभग सोलह से बीस-कुछ बाइट्स खर्च करता है। पॉइंटर कम्प्रेशन के बिना 64-बिट बिल्ड पर इसे दुगना करें।
नामित प्रॉपर्टीज़ दो में से एक स्थान पर जाती हैं:
- इन-ऑब्जेक्ट प्रॉपर्टीज़: ऑब्जेक्ट हेडर के ठीक बाद रखे गए स्लॉट, जो सबसे तेज़ पहुँच देते हैं;
- एक
PropertyArray: इन-ऑब्जेक्ट स्लॉट ख़त्म होने पर प्रॉपर्टीज़ एक अलग ऐरे में चली जाती हैं और ऑब्जेक्ट उसका पॉइंटर रखता है।
हर स्लॉट 4 या 8 बाइट है, और कुंजी स्ट्रिंग का हिसाब अलग से होता है। इसीलिए लंबे फ़ील्ड नाम लाखों रिकॉर्ड में एक वास्तविक लागत बन जाते हैं, केवल सौंदर्य का मुद्दा नहीं।
अच्छी ख़बर यह है कि छिपी क्लासें साझा होती हैं: एक ही क्रम में एक ही कुंजियों वाले दस लाख ऑब्जेक्ट एक ही map साझा करते हैं, संरचनात्मक जानकारी की कॉपी नहीं बनाते। बुरी ख़बर यह है कि ये पैटर्न उस साझाकरण को तोड़ते हैं या ऑब्जेक्ट को अधिक महँगे प्रतिनिधित्व में धकेलते हैं:
- कुछ रिकॉर्ड में
remarkफ़ील्ड होती है और कुछ में नहीं, जिससे दो आकार बनते हैं और हर पहुँच पर एक अतिरिक्त आकार-जाँच होती है; - अलग-अलग प्रविष्टि क्रम अलग-अलग आकार बना सकते हैं;
- बहुत अधिक प्रॉपर्टी गिनती (लगभग एक हज़ार से अधिक) या लगातार
deleteऑपरेशन ऑब्जेक्ट को dictionary मोड में धकेल देते हैं, जहाँ हर प्रॉपर्टी एक हैश-तालिका प्रविष्टि भी रखती है; Object.create(null)जन्म से ही dictionary मोड में होता है। यह एक अच्छी लुकअप तालिका है और हॉट लूप में पहुँचे जाने वाले निश्चित-स्कीमा रिकॉर्ड के लिए ख़राब विकल्प है।
एक आम ग़लतफ़हमी का सुधार ज़रूरी है: JSON दोहराई गई कुंजियों की अनुमति देता है, और {"a":1,"a":2} एक वैध दस्तावेज़ है जिसका परिणाम a === 2 होता है। अंतिम ऑब्जेक्ट एक मान रखता है, लेकिन पार्सर फिर भी हर घटना को स्कैन और असाइन करता है। दोहराई गई कुंजियाँ मेमोरी नहीं बचातीं; वे केवल पार्स समय खर्च करती हैं।
4. ऐरे और संख्याएँ: एलिमेंट काइंड चौड़ाई तय करते हैं
V8 ऐरे के लिए कई "एलिमेंट काइंड" ट्रैक करता है, और हर एक की प्रति-स्लॉट चौड़ाई अलग होती है:
| ऐरे की सामग्री | एलिमेंट काइंड | प्रति स्लॉट अनुमानित लागत |
|---|---|---|
| छोटे पूर्णांक (Smi श्रेणी के भीतर) | PACKED_SMI_ELEMENTS |
4 बाइट |
| दशमलव या बहुत बड़े पूर्णांक | PACKED_DOUBLE_ELEMENTS |
8 बाइट |
| स्ट्रिंग्स या ऑब्जेक्ट्स | PACKED_ELEMENTS |
4-बाइट पॉइंटर प्लस मान स्वयं |
delete से बने छेद वाले ऐरे |
HOLEY_* |
वही, प्लस छेद-जाँच |
| बहुत विरल ऐरे | DICTIONARY_ELEMENTS |
हैश प्रविष्टियाँ, आम तौर पर 20+ बाइट |
दो नियम याद रखने लायक हैं:
पहला, एलिमेंट काइंड केवल कमज़ोर होते हैं। छोटे पूर्णांकों से शुरू होने वाला ऐरे जिस क्षण आप 0.5 डालते हैं double ऐरे बन जाता है, और जब आप स्ट्रिंग डालते हैं तो generic एलिमेंट ऐरे बन जाता है। यह कभी स्वतः नहीं सुधरता; आपको नया ऐरे बनाना होता है।
दूसरा, कोई JSON संख्या कहाँ पहुँचती है यह उसके मान पर निर्भर करता है। Smi श्रेणी के भीतर पूर्णांक (लगभग प्लस/माइनस 1.07 अरब) सीधे Smi के रूप में संग्रहित होते हैं और एक पॉइंटर स्लॉट घेरते हैं। दशमलव या घातांक वाली संख्याएँ, या उस श्रेणी से बाहर के मान, लगभग 16 बाइट का एक HeapNumber आवंटित करते हैं, जिसका पॉइंटर ऐरे में संग्रहित होता है। इसलिए [1, 2, 3] और [0.1, 0.2, 0.3] केवल मान में नहीं, बल्कि "एक 4-बाइट स्लॉट" बनाम "एक 8-बाइट स्लॉट प्लस एक हीप ऑब्जेक्ट" में भी भिन्न हैं।
यह बड़े-पूर्णांक ID परिशुद्धता समस्या की भी जड़ है: सुरक्षित पूर्णांक श्रेणी से बाहर, JSON संख्याओं को double माना जाता है और निम्न-क्रम अंक बस ग़ायब हो जाते हैं। JSON ऐरे पैक्ड और छेद-रहित आते हैं, इसलिए पार्सिंग के बाद एलिमेंट्स को delete न करें; यह पूरे ऐरे को अधिक महँगे प्रतिनिधित्व की ओर धकेलता है।
5. पार्स और सीरियलाइज़ शिखर: वह क्षण जब कोई प्रक्रिया वास्तव में मरती है
स्थिर-अवस्था मेमोरी एक लेजर है; शिखर दूसरा। जब JSON.parse(text) चल रहा होता है, प्रक्रिया एक साथ तीन चीज़ें रखती है:
- इनपुट स्ट्रिंग
text; - बनने वाला और अभी पूरा न हुआ ऑब्जेक्ट ग्राफ़;
- पार्सर की अपनी अस्थायी संरचनाएँ।
पार्स शिखर लगभग "टेक्स्ट + ऑब्जेक्ट ग्राफ़ + अस्थायी ओवरहेड" होता है। पार्सिंग के बाद, यदि text का संदर्भ अब भी किसी वेरिएबल, क्लोज़र, मॉड्यूल-स्तरीय कैश, या रिएक्टिव स्टोर में है, तो वह टेक्स्ट मेमोरी कभी वापस नहीं होती।
सीरियलाइज़ेशन का भी एक शिखर होता है। JSON.stringify(data) आउटपुट स्ट्रिंग बनाते समय पूरा ऑब्जेक्ट ग्राफ़ रखता है, बीच में कन्कैटनेशन बफ़र के साथ। जब आउटपुट V8 की स्ट्रिंग लंबाई सीमा (लगभग 500 मिलियन कैरेक्टर) के करीब पहुँचता है, तो यह RangeError फेंकता है; यह विफलता आम तौर पर उन स्क्रिप्टों में दिखती है जो पूरे डेटाबेस को एक JSON दस्तावेज़ के रूप में निर्यात करती हैं।
सबसे महँगा आम पैटर्न सीरियलाइज़ेशन को डीप क्लोन के रूप में उपयोग करता है:
// शिखर लगभग: मूल ऑब्जेक्ट ग्राफ़ + अस्थायी टेक्स्ट + नया ऑब्जेक्ट ग्राफ़
const safeCopy = JSON.parse(JSON.stringify(bigData));
वह एक पंक्ति वही डेटा तीन बार संग्रहित करती है। केवल-पढ़ने के काम के लिए, संदर्भ पास करें। जब अलगाव वास्तव में आवश्यक हो, तो structuredClone का उपयोग करें और यह स्वीकार करें कि वह भी पूरा ऑब्जेक्ट ग्राफ़ पुनः बनाता है।
6. process.memoryUsage से मापना
Node में, process.memoryUsage() एक साथ पाँच संख्याएँ लौटाता है: rss (प्रक्रिया द्वारा उपयोग की गई भौतिक मेमोरी), heapTotal, heapUsed (उपयोग किया गया JS हीप), external (C++ ऑब्जेक्ट्स द्वारा रखी गई मेमोरी), और arrayBuffers (ArrayBuffer और Buffer मेमोरी)। JSON हैंडलिंग का अध्ययन करते समय, heapUsed ऑब्जेक्ट ग्राफ़ के लिए सही लेंस है और rss बताता है कि पूरी प्रक्रिया नियंत्रण से बाहर है या नहीं।
एक मापन स्क्रिप्ट को तीन अनुशासन चाहिए: पहले global.gc() कॉल करें, चरणों में नमूना लें, और एक समय में एक चर बदलें। यह स्क्रिप्ट "टेक्स्ट पढ़ें" और "ऑब्जेक्ट्स में पार्स करें" के लिए वृद्धियाँ अलग-अलग दर्ज करती है:
// चलाएँ: node --expose-gc measure-json-memory.mjs
import { readFileSync } from 'node:fs';
global.gc();
const baseline = process.memoryUsage().heapUsed;
const text = readFileSync('big.json', 'utf8');
const afterRead = process.memoryUsage().heapUsed;
const data = JSON.parse(text);
const afterParse = process.memoryUsage().heapUsed;
console.log({
textOnHeap: afterRead - baseline,
graphOnHeap: afterParse - afterRead,
parsePeak: afterParse - baseline,
});
कुछ जाल:
--expose-gcस्पष्ट रूप से सक्षम करना होगा; अन्यथाglobal.gcमौजूद नहीं होता;heapUsedकेवल JS हीप को कवर करता है। फ़ाइल-पढ़ने वाले BuffersexternalऔरarrayBuffersके अंतर्गत रहते हैं, इसलिए सब कुछ समझाने के लिए एक संख्या का उपयोग न करें;- नमूनों के बीच बड़े ऑब्जेक्ट को
console.logन करें; यह एक अस्थायी संदर्भ जीवित रखता है; - पहला रन JIT वार्म-अप और आलसी कम्पाइलेशन शामिल करता है। कम से कम तीन दौर चलाएँ और माध्यिका लें;
- ब्राउज़र में तुलना करने के लिए, DevTools Memory पैनल में हीप स्नैपशॉट लें और Allocation Timeline में आरी-दाँतों को घूरने के बजाय
Retained Sizeपढ़ें।
इस बेसलाइन के बिना, "स्ट्रीमिंग पर स्विच करने से यह ठीक हो जाएगा" या "कैश इसे तेज़ कर देगा" जैसे दावे केवल भावना हैं।
7. स्ट्रीमिंग शिखर मेमोरी खरीदता है, कुल समय नहीं
स्ट्रीमिंग पार्सिंग में सौदा स्पष्ट है: रैंडम एक्सेस के बदले ऐसी मेमोरी जो अब फ़ाइल आकार के साथ नहीं बढ़ती।
| दृष्टिकोण | शिखर मेमोरी | रैंडम एक्सेस | सबसे उपयुक्त |
|---|---|---|---|
पूरा JSON.parse |
टेक्स्ट प्लस पूरा ऑब्जेक्ट ग्राफ़ | हाँ | लगभग 100MB से नीचे और अधिकांश फ़ील्ड चाहिए |
stream-json जैसा SAX पार्सर |
एक चंक प्लस वर्तमान एलिमेंट | नहीं | विशाल फ़ाइलें, या कुछ ही फ़ील्ड चाहिए |
| पंक्ति-दर-पंक्ति JSON Lines | एक पंक्ति का ऑब्जेक्ट | नहीं | वह डेटा जो स्वाभाविक रूप से रिकॉर्ड का बैच है |
| बाहरी इंडेक्स या डेटाबेस | कुछ भी पूरा लोड नहीं | हाँ | उसी डेटा पर बार-बार क्वेरी |
import { createReadStream } from 'node:fs';
import { parser } from 'stream-json';
import { pick } from 'stream-json/filters/Pick';
import { streamArray } from 'stream-json/streamers/StreamArray';
const pipeline = createReadStream('huge.json')
.pipe(parser())
.pipe(pick({ filter: 'items' }))
.pipe(streamArray());
for await (const { value } of pipeline) {
handleItem(value);
}
यह कोड पूरा ट्री कभी नहीं बनाता, इसलिए मेमोरी वक्र सपाट रहता है। लागतें भी असली हैं: आप छोड़े गए फ़ील्ड के लिए वापस नहीं जा सकते, वैश्विक एकत्रीकरण कठिन हो जाता है, त्रुटि वसूली आपकी ज़िम्मेदारी है, और शुद्ध-JS कैरेक्टर-दर-कैरेक्टर स्कैन आम तौर पर नेटिव JSON.parse से परिमाण-कोटि धीमा होता है।
निर्णय "क्या फ़ाइल 10MB से बढ़ गई" नहीं है। दो प्रश्न पूछें: क्या मुझे पार्सिंग के बाद अधिकांश फ़ील्ड चाहिए? क्या मैं क्रमिक पहुँच के साथ जी सकता हूँ? यदि दोनों उत्तर हाँ हैं, तो पूरा पार्सिंग सही है और आपका प्रयास कॉपी कम करने और रेंडरिंग में लगना चाहिए। यदि कोई भी उत्तर नहीं है, तो स्ट्रीमिंग पर विचार करें। पूरी स्तरित रणनीति के लिए, देखें बड़ी JSON का पार्सिंग, रेंडरिंग और स्ट्रीमिंग।
8. कम कॉपी, पहले रिलीज़
मेमोरी के काम में, एक पूरी कॉपी हटाना अक्सर पार्सर बदलने से अधिक मूल्यवान होता है:
- केवल-पढ़ने के काम के लिए संदर्भ पास करें। "सुरक्षा के लिए" डीप-क्लोन न करें, विशेष रूप से
JSON.parse(JSON.stringify(x))से नहीं; - कच्चे दस्तावेज़ को किसी रिएक्टिव सिस्टम में न लपेटें। Vue का
reactiveपूरे ऑब्जेक्ट ग्राफ़ को पुनरावर्ती रूप से प्रॉक्सी करता है, और प्रॉक्सी परत की अपनी मेमोरी लागत होती है; - व्युत्पन्न डेटा अलग से गणना और कैश करें। यदि किसी सूची को केवल id, title, और status चाहिए, तो स्टोर में पूरे रिकॉर्ड न डालें;
- उच्च-आवृत्ति स्ट्रिंग्स को सामान्यीकृत करें।
"paid"और"pending"जैसे मानों को संख्यात्मक enum पर मैप करें ताकि स्ट्रिंग ऑब्जेक्ट और तुलना लागत दोनों बचें; - लंबे समय तक जीवित संदर्भ काटें। काम होने पर बड़े ऑब्जेक्ट को null करें, कैश साफ़ करें, और कॉलबैक अनरजिस्टर करें ताकि क्लोज़र उन्हें अगले पेज तक न ढोएँ;
- Worker कॉपी लागत का हिसाब रखें।
postMessageडिफ़ॉल्ट रूप से structured clone का उपयोग करता है; 50MB का ऑब्जेक्ट ग्राफ़ मुख्य थ्रेड को वापस भेजना दूसरी कॉपी बनाता है। जब संभव हो सारांश लौटाएँ।
आकार लेजर का दूसरा आधा भाग JSON कम्प्रेशन ग़लत क्रम में करना बंद करें में है: डिस्क बाइट्स और वायर बाइट्स हीप में ऑब्जेक्ट ग्राफ़ से अलग खाता हैं, और कोई एक प्रतिशत दोनों को नहीं समझाता। एडिटर और CLI टूल मेमोरी के साथ कैसा व्यवहार करते हैं, इसके लिए देखें एडिटर, कमांड-लाइन टूल और ऑटोमेशन।
9. चेकलिस्ट
| लक्षण | अधिक संभावित कारण | पहला कदम |
|---|---|---|
| 10MB फ़ाइल प्रक्रिया को 50MB तक धकेलती है | ऑब्जेक्ट ग्राफ़ और टेक्स्ट दोनों जीवित रहते हैं | चरणों में मापें; जाँचें कि टेक्स्ट बना रहता है या नहीं |
| पार्स समय पर OOM, स्थिर अवस्था में सामान्य | पार्स शिखर पुराने डेटा के साथ ओवरलैप करता है | नई फ़ाइल पार्स करने से पहले पुराने ऑब्जेक्ट रिलीज़ करें |
| मेमोरी धीरे-धीरे बढ़ती है और कभी गिरती नहीं | कोई स्लाइस, कैश, या क्लोज़र संदर्भ रखता है | हीप स्नैपशॉट लें और retained size से क्रमबद्ध करें |
| ऐरे अपेक्षा से बड़े हैं | दशमलव ने एलिमेंट काइंड कमज़ोर कर दिया | संख्यात्मक प्रकार एकीकृत करें; delete से बचें |
| कैश जोड़ने से चीज़ें धीमी हुईं | कुंजियाँ और मान मेमोरी खाते हैं, GC दबाव बढ़ाते हैं | कैश को सीमित करें और केवल सारांश संग्रहित करें |
JSON.parse के बाद मेमोरी तीन या चार गुना क्यों बढ़ जाती है?
क्योंकि टेक्स्ट केवल इनपुट है; पार्स का उत्पाद एक ऑब्जेक्ट ग्राफ़ है। हर ऑब्जेक्ट का एक हेडर होता है, हर प्रॉपर्टी का एक स्लॉट, और हर स्ट्रिंग का अपना हेडर। पर्याप्त नोड्स के साथ, कुल ओवरहेड टेक्स्ट के बाइट गिनती से अधिक हो जाता है। गुणक डेटा आकार पर निर्भर करता है, और समतल ऑब्जेक्ट ऐरे गहराई से नेस्टेड दस्तावेज़ों से बहुत भिन्न होते हैं।
10MB JSON फ़ाइल को पूरा पार्स करना चाहिए या स्ट्रीम करना?
पहले उद्देश्य देखें। यदि आपको रैंडम एक्सेस चाहिए, अधिकांश फ़ील्ड चाहिए, और फ़ाइल मेमोरी में फ़िट होती है, तो पूरा JSON.parse उपयोग करें। यदि आपको केवल कुछ फ़ील्ड चाहिए, या फ़ाइल फ़िट नहीं होती, तो स्ट्रीम करें। 10MB कोई सीमा नहीं है; स्ट्रीमिंग आम तौर पर 100MB से कहीं ऊपर अनिवार्य हो जाती है।
process.memoryUsage().heapUsed सिस्टम मॉनिटरिंग से क्यों मेल नहीं खाता?
heapUsed केवल JS हीप के उपयोग किए गए हिस्से को कवर करता है, external, arrayBuffers, और कोड स्पेस को छोड़कर। सिस्टम मॉनिटरिंग rss बताती है, जो एक अलग माप है। heapUsed और rss दोनों साथ ट्रैक करें; बड़ा अंतर Buffers और नेटिव ऑब्जेक्ट्स की ओर इशारा करता है।
क्या JSON.parse(JSON.stringify(x)) सुरक्षित डीप क्लोन है?
नहीं। यह मूल ग्राफ़, अस्थायी टेक्स्ट, और नया ग्राफ़ एक साथ रखता है, इसलिए शिखर स्थिर अवस्था का लगभग तीन गुना है, और यह undefined, फ़ंक्शन, और Date सेमांटिक्स को गिरा देता है। केवल-पढ़ने के काम के लिए संदर्भ पास करें; जब अलगाव आवश्यक हो, तो structuredClone का उपयोग करें और वही पुनर्निर्माण लागत स्वीकार करें।