किन पहिलो C++ (m) आवंटन सधैं 72 KB हुन्छ? | Mewayz Blog Skip to main content
Hacker News

किन पहिलो C++ (m) आवंटन सधैं 72 KB हुन्छ?

टिप्पणीहरू

1 min read Via joelsiks.com

Mewayz Team

Editorial Team

Hacker News

तपाईँको पहिलो C++ आवंटन पछाडिको रहस्य

तपाईँले एउटा साधारण C++ प्रोग्राम लेख्नुहुन्छ। एकल नयाँ int। चार बाइट्स। तपाईंले स्ट्रेस वा तपाईंको मनपर्ने मेमोरी प्रोफाइलरलाई फायर गर्नुभयो, र त्यहाँ छ — तपाईंको प्रक्रियाले अपरेटिङ सिस्टमबाट लगभग ७२ KB अनुरोध गरेको छ। 4 बाइट होइन। ६४ बाइट होइन। पूर्ण 72 KB। यदि तपाईंले कहिल्यै त्यो नम्बरमा हेरिरहनुभएको छ र सोच्नुभएको छ कि तपाईंको उपकरणले तपाईंलाई झूट बोलिरहेको छ कि छैन, तपाईं एक्लै हुनुहुन्न। यो विचित्र देखिने व्यवहार C++ विकासकर्ताहरू बीचमा पहिलो पटक मेमोरी इन्टरनलमा खन्ने प्रायः सोधिने प्रश्नहरू मध्ये एक हो, र जवाफले हामीलाई तपाईंको कोड र वास्तविक हार्डवेयर बीचमा बस्ने तहहरू मार्फत एक आकर्षक यात्रामा लैजान्छ।

तपाईंले नयाँ

कल गर्दा के हुन्छ

७२ KB फिगर बुझ्नको लागि, तपाईंले पूर्ण आवंटन श्रृंखला ट्रेस गर्न आवश्यक छ। जब तपाइँको C++ कोडले नयाँ int कार्यान्वयन गर्छ, कम्पाइलरले यसलाई अपरेटर नयाँ लाई कलमा अनुवाद गर्दछ, जुन धेरै लिनक्स प्रणालीहरूमा glibc बाट malloc लाई प्रतिनिधि गर्दछ। तर malloc ले सिधै कर्नेललाई ४ बाइट मेमोरीको लागि सोध्दैन। कर्नेलले पृष्ठहरूमा काम गर्छ - x86_64 मा सामान्यतया 4 KB - र प्रणाली कलको लागत साधारण मेमोरी पहुँचको तुलनामा ठूलो हुन्छ। प्रत्येक व्यक्तिगत विनियोजनको लागि brk() वा mmap() लाई कल गर्नाले कुनै पनि गैर-तुच्छ कार्यक्रमलाई रोकिनेछ।

बरु, glibc को मेमोरी एलोकेटर - ptmalloc2 भनिने कार्यान्वयन, आफै डग लीको क्लासिक dlmalloc बाट आएको हो — मध्यस्थको रूपमा कार्य गर्दछ। यसले कर्नेल अपफ्रन्टबाट मेमोरीको ठूला ब्लकहरू अनुरोध गर्दछ, त्यसपछि तिनीहरूलाई साना टुक्राहरूमा कोर्छ जुन तपाईंको कार्यक्रमलाई आवश्यक हुन्छ। यो मौलिक कारण हो कि तपाईंको पहिलो 4-बाइट आवंटनले अपरेटिङ सिस्टममा धेरै ठूलो अनुरोध ट्रिगर गर्दछ। विनियोजनकर्ता बर्बाद भइरहेको छैन। यो रणनीतिक भइरहेको छ।

72 KB विच्छेदन: जहाँ बाइटहरू जान्छन्

प्रारम्भिक आवंटन ओभरहेड धेरै फरक कम्पोनेन्टहरूबाट आउँदछ जुन रनटाइमले तपाईंलाई प्रयोगयोग्य मेमोरीको एक बाइट पनि हस्तान्तरण गर्नु अघि सुरु गर्नुपर्छ। प्रत्येक कम्पोनेन्टलाई बुझ्दा संख्याले किन त्यहाँ पुग्छ भनेर बताउँछ।

पहिले, glibc को malloc ले मुख्य एरेना लाई प्रारम्भ गर्दछ — प्राथमिक बहीखाता संरचना जसले मुख्य थ्रेडमा सबै आवंटनहरू ट्र्याक गर्दछ। यस एरेनाले हिप, फ्री-लिस्ट पोइन्टर्स, र विभिन्न आवंटन आकारहरूको लागि बिन संरचनाहरूको लागि मेटाडेटा समावेश गर्दछ। आवंटकले sbrk() मार्फत कार्यक्रम ब्रेक विस्तार गर्दछ, र प्रारम्भिक विस्तारलाई M_TOP_PAD भनिने आन्तरिक प्यारामिटरद्वारा नियन्त्रित गरिन्छ, जुन 128 KB प्याडिङमा पूर्वनिर्धारित हुन्छ। यद्यपि, वास्तविक प्रारम्भिक अनुरोध पृष्ठ पङ्क्तिबद्धता र अवस्थित ब्रेक स्थितिको लागि समायोजन गरिएको छ, जसले प्रायः सानो पहिलो अनुरोधमा परिणाम दिन्छ — सामान्यतया नयाँ सुरु भएको प्रक्रियामा त्यो ७२ KB फिगरको नजिक अवतरण हुन्छ।

दोस्रो, glibc २.२६ देखि, विनियोजनकर्ताले पहिलो प्रयोगमा थ्रेड-लोकल क्यास (tcache) सुरु गर्छ। tcache मा 64 bins (एक प्रति सानो-विनियोजन आकार वर्ग) समावेश गर्दछ, प्रत्येक 7 क्यास खण्ड सम्म समात्न सक्षम। tcache_perthread_struct आफैले लगभग 1 KB खपत गर्छ, तर यसलाई प्रारम्भ गर्ने कार्यले फराकिलो एरेना सेटअपलाई ट्रिगर गर्दछ। तेस्रो, C++ रनटाइमले तपाईँको main() रन अघि नै आवंटन गरिसकेको छ — स्थिर कन्स्ट्रक्टरहरू, std::cout र साथीहरूको लागि iostream बफर प्रारम्भिकरण, र लोकेल सेटअप सबैले त्यो प्रारम्भिक हिप फुटप्रिन्टमा योगदान गर्दछ।

एरेना प्रणाली र किन पूर्व-विनियोजन स्मार्ट छ

यसलाई टुक्राटुक्रा अनुरोध गर्नुको सट्टा मेमोरीको पर्याप्त भाग पूर्व-विनियोजन गर्ने निर्णय कार्यान्वयनको दुर्घटना होइन। यो एक जानाजानी ईन्जिनियरिङ् ट्रेडअफ प्रणाली प्रोग्रामिङ अनुभव को दशकहरु मा जरा हो। brk() वा mmap() लाई प्रत्येक कलमा प्रयोगकर्ता स्पेसबाट कर्नेल स्पेसमा सन्दर्भ स्विच, प्रक्रियाको भर्चुअल मेमोरी म्यापिङको परिमार्जन, र सम्भावित पृष्ठ तालिका अपडेटहरू समावेश हुन्छन्। आधुनिक हार्डवेयरमा, एकल प्रणाली कलको लागत लगभग १००-२०० नानोसेकेन्ड हुन्छ — अलगावमा मामूली, स्केलमा विनाशकारी।

प्रारम्भिक समयमा १०,००० सानो आवंटन गर्ने कार्यक्रमलाई विचार गर्नुहोस्। पूर्व-विनियोजन बिना, यसको मतलब 10,000 प्रणाली कलहरू हुनेछ, लगभग 1-2 मिलिसेकेन्ड शुद्ध ओभरहेडको लागत। एरेना-आधारित एलोकेटरको साथ, पहिलो आवंटनले एकल प्रणाली कल ट्रिगर गर्दछ, र त्यसपछिका 9,999 आवंटनहरू प्रयोगकर्ता स्पेसमा पोइन्टर अंकगणित र लिङ्क गरिएको-सूची सञ्चालनहरू मार्फत पूर्ण रूपमा सेवा गरिन्छ — प्रत्येकले लगभग 10-50 नानोसेकेन्डहरू लिन्छ। गणित अस्पष्ट छ: पूर्व-विनियोजन परिमाण को आदेश द्वारा जीत।

तपाईँले आफ्नो पहिलो आवंटनमा देख्नु भएको ७२ KB मेमोरी बर्बाद होइन - यो एक प्रदर्शन लगानी हो। तपाईंको कार्यक्रमले चाँडै नै थप आवंटनहरू गर्नेछ र लगभग हरेक वास्तविक-विश्व परिदृश्यमा, त्यो बाजीले राम्रोसँग भुक्तानी गर्छ भनी आबंटकले शर्त लगाइरहेको छ। अप्रयुक्त भर्चुअल ठेगाना स्पेसको लागत आधुनिक 64-बिट प्रणालीहरूमा अनिवार्य रूपमा शून्य छ।

भर्चुअल मेमोरी बनाम भौतिक मेमोरी: किन यो फरक पर्दैन

पहिलो पटक यो व्यवहारको सामना गर्ने विकासकर्ताहरू बीचको एउटा साझा चिन्ता भनेको स्रोतको बर्बादी हो। यदि मलाई केवल 4 बाइटहरू चाहिन्छ भने, मेरो कार्यक्रमले किन 72 KB खपत गरिरहेको छ? महत्वपूर्ण अन्तरदृष्टि यो हो कि भर्चुअल मेमोरी भौतिक मेमोरी होइन। जब glibc ले कार्यक्रम ब्रेकलाई ७२ KB ले विस्तार गर्छ, कर्नेलले प्रक्रियाको भर्चुअल मेमोरी म्यापिङहरू अद्यावधिक गर्छ, तर यसले ती पृष्ठहरूलाई भौतिक RAM सँग तुरुन्तै ब्याक गर्दैन। वास्तविक भौतिक पृष्ठहरू पृष्ठ त्रुटिहरू मार्फत मागमा आवंटित हुन्छन् — जब तपाइँको कार्यक्रमले कुनै निश्चित ठेगानामा लेख्छ तब मात्र कर्नेलले यसलाई मेमोरीको वास्तविक पृष्ठ प्रदान गर्दछ।

💡 DID YOU KNOW?

Mewayz replaces 8+ business tools in one platform

CRM · Invoicing · HR · Projects · Booking · eCommerce · POS · Analytics. Free forever plan available.

Start Free →

यसको मतलब यो हो कि तपाईंको प्रक्रियाको भर्चुअल साइज ७२ KB ले बढे पनि, यसको निवासी सेट साइज (RSS) - वास्तवमा खपत भएको भौतिक RAM को मात्रा - तपाईंले वास्तवमा छुने पृष्ठहरू मात्र बढ्छ। एकल नयाँ int को लागि, त्यो सामान्यतया एक 4 KB पृष्ठ हो, साथै एरेना मेटाडेटाले ओगटेको जुनसुकै पृष्ठहरू। बाँकी भर्चुअल स्पेस त्यहाँ बस्छ, प्रयोगको लागि तयार छ, ठेगाना स्पेस बाहेक अरू केही लागत छैन — जसमध्ये तपाईंसँग 64-बिट लिनक्स प्रणालीमा 128 TB छ।

उत्पादन अनुप्रयोगहरूको प्रोफाइल र अनुगमन गर्दा यो भिन्नता महत्त्वपूर्ण हुन्छ। यदि तपाइँ सफ्टवेयर निर्माण गर्दै हुनुहुन्छ जुन वास्तविक स्रोत खपत ट्र्याक गर्न आवश्यक छ - चाहे यो SaaS ब्याकएन्ड हो, एक माइक्रोसर्भिस हो, वा व्यापार सञ्चालनका लागि Mewayz जस्ता प्लेटफर्महरूमा चल्ने एनालिटिक्स पाइपलाइन हो - तपाइँ सधैँ भर्चुअल आकारको सट्टा RSS निगरानी गर्नुपर्छ। /proc/[pid]/smaps, valgrind --tool=massif, र pmap जस्ता उपकरणहरूले भर्चुअल मेमोरी फिगरहरूलाई भ्रामक बनाउनुको सट्टा सही भौतिक मेमोरी फुटप्रिन्टहरू दिन सक्छ।

विभिन्न आवंटकहरूले पहिलो आवंटनलाई कसरी ह्यान्डल गर्छन्

72 KB फिगर glibc को ptmalloc2 को लागि विशिष्ट छ। अन्य आवंटकहरूले विभिन्न ट्रेडअफहरू बनाउँछन्, र प्रारम्भिक आवंटन ओभरहेड तदनुसार भिन्न हुन्छ। प्रदर्शन-संवेदनशील अनुप्रयोगहरूको लागि विनियोजनकर्ता छनौट गर्दा यी भिन्नताहरू बुझ्न महत्त्वपूर्ण छ।

  • jemalloc (Facebook, FreeBSD द्वारा प्रयोग गरिएको) — थ्रेड-स्थानीय क्यासहरूसँग थप दानेदार एरेना संरचना प्रयोग गर्दछ। प्रारम्भिक ओभरहेड उच्च हुन्छ (प्रायः २००+ KB) तर कम लक विवादको कारणले राम्रो बहु-थ्रेडेड प्रदर्शन प्रदान गर्दछ।
  • tcmalloc (Google को Thread-Caching Malloc) — पूर्वनिर्धारित रूपमा लगभग 2 MB को प्रति-थ्रेड क्यास आवंटित गर्दछ, आक्रामक पूर्व-विनियोजनको साथ। प्रारम्भिक ओभरहेड उच्च छ, तर पछिको सानो आवंटन अत्यन्त छिटो छ।
  • musl libc's malloc — सबै आवंटनहरूको लागि mmap मा आधारित धेरै सरल डिजाइन प्रयोग गर्दछ। प्रारम्भिक ओभरहेड न्यूनतम छ (प्रायः प्रति आवंटन मात्र 4 KB), तर अधिक बारम्बार प्रणाली कलहरूको कारण प्रति-विनियोजन लागत बढी छ।
  • mimalloc (Microsoft) — 64 MB खण्डहरूसँग खण्ड-आधारित आवंटन प्रयोग गर्दछ। पहिलो आवंटनले 64 MB भर्चुअल रिजर्भेसन ट्रिगर गर्दछ (न्यूनतम भौतिक प्रतिबद्धताको साथ), असाधारण स्थान र थ्रुपुटको लागि व्यापार ठेगाना ठाउँ।

यी विनियोजनकर्ताहरू बीचको छनोट पूर्णतया तपाईंको कार्यभारमा निर्भर गर्दछ। भारी बहु-थ्रेडेड विनियोजनको साथ लामो-चलिरहेको सर्भर अनुप्रयोगहरूको लागि, jemalloc वा tcmalloc ले सामान्यतया glibc को पूर्वनिर्धारित प्रदर्शन गर्दछ। मेमोरी-प्रतिबन्धित एम्बेडेड प्रणालीहरूको लागि, कम थ्रुपुटको बाबजुद मुसलको सरल दृष्टिकोण उपयुक्त हुन सक्छ। धेरैजसो सामान्य-उद्देश्य डेस्कटप र सर्भर अनुप्रयोगहरूको लागि, ptmalloc2 को 72 KB प्रारम्भिक ओभरहेडले एक उचित पूर्वनिर्धारित प्रतिनिधित्व गर्दछ जुन ट्युनिङ बिना राम्रोसँग काम गर्दछ।

प्रारम्भिक आवंटन व्यवहार ट्यून गर्दै

यदि पूर्वनिर्धारित 72 KB प्रारम्भिक ओभरहेड तपाईंको प्रयोग केसको लागि साँच्चिकै समस्याग्रस्त छ — सायद तपाईंले हजारौं अल्पकालीन प्रक्रियाहरू फैलाउँदै हुनुहुन्छ, प्रत्येकले थोरै मात्र आवंटनहरू बनाउँदै हुनुहुन्छ — glibc ले mallopt()MALLOC_परिवारहरू मार्फत धेरै ट्युनेबलहरू प्रदान गर्दछ।

M_TOP_PAD प्यारामिटरले विनियोजनकर्ताले तुरुन्तै आवश्यक पर्नेभन्दा बढी मेमोरीको अनुरोध गरेको नियन्त्रण गर्छ। यसलाई mallopt(M_TOP_PAD, 0) सँग ० मा सेट गर्नाले विनियोजनकर्तालाई आवश्यक पर्ने मात्र अनुरोध गर्न बताउँछ, प्रारम्भिक ओभरहेडलाई उल्लेखनीय रूपमा घटाउँदै। M_MMAP_THRESHOLD प्यारामिटरले एरेनाको सट्टा mmap प्रयोग गर्ने माथिको आकारलाई नियन्त्रण गर्दछ। M_TRIM_THRESHOLD ले OS मा खाली मेमोरी फर्काउँदा नियन्त्रण गर्छ। र glibc 2.26 देखि, glibc.malloc.tcache_countglibc.malloc.tcache_max ट्युनेबलहरूले तपाईंलाई थ्रेड क्यास व्यवहार नियन्त्रण गर्न दिन्छ।

यद्यपि, सावधानीको एक शब्द: होसियार बेन्चमार्किङ बिना यी प्यारामिटरहरू ट्युन गर्नाले चीजहरू खराब बनाउँछ। पूर्वनिर्धारितहरू व्यापक वास्तविक-विश्व प्रोफाइलिङको आधारमा छनोट गरिएको थियो, र तिनीहरूले कार्यभारहरूको विशाल बहुमतको लागि मीठो स्थान प्रतिनिधित्व गर्दछ। जबसम्म तपाईंसँग उत्पादन प्रोफाइलिङबाट बलियो प्रमाण छैन कि malloc ओभरहेड एक बाधा हो - र तपाईंले आफ्नो परिवर्तनहरूको प्रभाव मापन गर्नुभएको छ - पूर्वनिर्धारितहरू एक्लै छोड्नुहोस्। आवंटकको समयपूर्व अनुकूलन याक शेभिङको विशेष रूपले कपटी रूप हो जसले नगण्य लाभको लागि अनगिन्ती इन्जिनियरिङ घण्टाहरू खपत गरेको छ।

यसले हामीलाई सिस्टम प्रोग्रामिङको बारेमा के सिकाउँछ

72 KB पहिलो-विनियोजन रहस्य, यसको मूलमा, अमूर्त तहहरू को बारेमा पाठ हो। C++ ले तपाईंलाई नयाँ int 4 बाइटहरू आवंटित गर्ने भ्रम दिन्छ। भाषाको मापदण्डले त्यही भन्छ । तपाईको मानसिक मोडेलले त्यसो भन्छ। तर तपाईंको कोड र हार्डवेयर बीचमा परिष्कृत प्रणालीहरूको स्ट्याक बस्छ — C++ रनटाइम, C लाइब्रेरी आवंटक, कर्नेलको भर्चुअल मेमोरी सबसिस्टम, र हार्डवेयरको MMU र TLB — प्रत्येकले आफ्नै व्यवहार, अप्टिमाइजेसनहरू, र ओभरहेड थप्दै।

यो कुनै दोष होइन। यो प्रणाली सफ्टवेयर को सम्पूर्ण बिन्दु हो। प्रत्येक तह वास्तविक समस्या समाधान गर्न अवस्थित छ: आवंटक अवस्थित छ त्यसैले तपाईंले प्रत्येक आवंटनको लागि प्रणाली कलहरू गर्नुपर्दैन। भर्चुअल मेमोरी प्रणाली अवस्थित छ त्यसैले तपाईंले प्रत्यक्ष रूपमा भौतिक मेमोरी व्यवस्थापन गर्नुपर्दैन। पृष्ठ त्रुटि ह्यान्डलर अवस्थित छ त्यसैले मेमोरी अल्छी र कुशलतापूर्वक प्रतिबद्ध छ। प्रत्येक तहले ठूलो मात्रामा प्रदर्शन र सुविधाको लागि पारदर्शिताको सानो मात्रामा व्यापार गर्दछ।

सबैभन्दा भरपर्दो, उच्च प्रदर्शन गर्ने प्रणालीहरू निर्माण गर्ने विकासकर्ताहरू यी तहहरू बुझ्छन् — किनभने तिनीहरूले तिनीहरूको बारेमा निरन्तर सोच्नुपर्छ भन्ने होइन, तर जब केही अप्रत्याशित हुन्छ (जस्तै रहस्यमय 72 KB आवंटन), तिनीहरूसँग किन बुझ्ने मानसिक मोडेल हुन्छ। तपाईं वास्तविक-समय व्यापार प्रणाली, खेल इन्जिन, वा हजारौं प्रयोगकर्ताहरूलाई सेवा दिने व्यापार प्लेटफर्म निर्माण गर्दै हुनुहुन्छ भने, तपाईंको कोडले प्रणाली स्तरमा वास्तवमा के गर्छ भन्ने तर्क गर्ने क्षमताले सक्षम विकासकर्ताहरूलाई असाधारण व्यक्तिहरूबाट अलग गर्छ। 72 KB कुनै बग होइन। यो तपाइँको विनियोजनकर्ता हो जसले आफ्नो काम शानदार रूपमा गरिरहेको छ।

आज नै आफ्नो व्यापार ओएस बनाउनुहोस्

फ्रीलान्सरदेखि एजेन्सीसम्म, Mewayz ले २०७ एकीकृत मोड्युलहरूका साथ १३८,०००+ व्यवसायहरूलाई शक्ति दिन्छ। नि:शुल्क सुरु गर्नुहोस्, जब तपाईं बढ्नुहुन्छ अपग्रेड गर्नुहोस्।

नि:शुल्क खाता बनाउनुहोस् →

Start managing your business smarter today

Join 30,000+ businesses. Free forever plan · No credit card required.

Ready to put this into practice?

Join 30,000+ businesses using Mewayz. Free forever plan — no credit card required.

Start Free Trial →

Ready to take action?

Start your free Mewayz trial today

All-in-one business platform. No credit card required.

Start Free →

14-day free trial · No credit card · Cancel anytime