Hacker News

Γιατί η πρώτη εκχώρηση C++ (m) είναι πάντα 72 KB;

Ανακαλύψτε γιατί η πρώτη σας εκχώρηση μνήμης C++ απαιτεί 72 KB αντί για τα αναμενόμενα byte. Εξερευνήστε τα εσωτερικά του malloc και τα επίπεδα διαχείρισης μνήμης λειτουργικού συστήματος που εξηγούνται.

1 min read

Mewayz Team

Editorial Team

Hacker News

Το μυστήριο πίσω από την πρώτη σας κατανομή C++

Γράφεις ένα απλό πρόγραμμα C++. Ένα μόνο νέο int. Τέσσερα byte. Ενεργοποιείτε το strace ή το αγαπημένο σας προφίλ μνήμης και είναι εδώ — η διαδικασία σας μόλις ζήτησε περίπου 72 KB από το λειτουργικό σύστημα. Όχι 4 byte. Όχι 64 byte. Ολόκληρα 72 KB. Αν έχετε κοιτάξει ποτέ αυτόν τον αριθμό και αναρωτηθήκατε αν σας έλεγε ψέματα τα εργαλεία σας, δεν είστε μόνοι. Αυτή η φαινομενικά παράξενη συμπεριφορά είναι μια από τις πιο συχνές ερωτήσεις μεταξύ των προγραμματιστών της C++ που σκάβουν τα εσωτερικά της μνήμης για πρώτη φορά και η απάντηση μας οδηγεί σε ένα συναρπαστικό ταξίδι στα επίπεδα που βρίσκονται μεταξύ του κώδικά σας και του πραγματικού υλικού.

Τι συμβαίνει όταν καλείτε νέο

Για να κατανοήσετε τον αριθμό των 72 KB, πρέπει να εντοπίσετε την πλήρη αλυσίδα κατανομής. Όταν ο κώδικας C++ σας εκτελεί νέο int, ο μεταγλωττιστής το μεταφράζει σε κλήση προς τον χειριστή new, ο οποίος στα περισσότερα συστήματα Linux εκχωρεί στο malloc από το glibc. Αλλά το malloc δεν ζητά απευθείας από τον πυρήνα 4 byte μνήμης. Ο πυρήνας λειτουργεί σε σελίδες — συνήθως 4 KB στο x86_64 — και το κόστος μιας κλήσης συστήματος είναι τεράστιο σε σχέση με μια απλή πρόσβαση στη μνήμη. Η κλήση brk() ή mmap() για κάθε μεμονωμένη κατανομή θα έκανε οποιοδήποτε μη τετριμμένο πρόγραμμα να σταματήσει.

Αντίθετα, ο εκχωρητής μνήμης του glibc - μια υλοποίηση που ονομάζεται ptmalloc2, η οποία προέρχεται από το κλασικό dlmalloc του Doug Lea - λειτουργεί ως μεσάζων. Ζητάει μεγάλα μπλοκ μνήμης από τον πυρήνα εκ των προτέρων και στη συνέχεια τα χαράζει σε μικρότερα κομμάτια όπως τα χρειάζεται το πρόγραμμά σας. Αυτός είναι ο βασικός λόγος που η πρώτη σας εκχώρηση 4 byte ενεργοποιεί ένα πολύ μεγαλύτερο αίτημα στο λειτουργικό σύστημα. Ο κατανεμητής δεν είναι σπάταλος. Είναι στρατηγικό.

Ανατομή των 72 KB: Where the Bytes Go

Η αρχική επιβάρυνση κατανομής προέρχεται από πολλά διακριτά στοιχεία που πρέπει να αρχικοποιήσει ο χρόνος εκτέλεσης για να μπορέσει να σας παραδώσει έστω και ένα byte χρησιμοποιήσιμης μνήμης. Η κατανόηση κάθε συστατικού εξηγεί γιατί ο αριθμός προσγειώνεται εκεί που προσγειώνεται.

💡 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 →

Πρώτον, το malloc του glibc προετοιμάζει την κύρια αρένα - την κύρια δομή τήρησης βιβλίων που παρακολουθεί όλες τις κατανομές στο κύριο νήμα. Αυτό το πεδίο περιλαμβάνει μεταδεδομένα για το σωρό, δείκτες ελεύθερης λίστας και δομές bin για διαφορετικά μεγέθη κατανομής. Ο κατανεμητής επεκτείνει τη διακοπή του προγράμματος μέσω sbrk(), και η αρχική επέκταση διέπεται από μια εσωτερική παράμετρο που ονομάζεται M_TOP_PAD, η οποία από προεπιλογή είναι 128 KB padding. Ωστόσο, το πραγματικό αρχικό αίτημα προσαρμόζεται για τη στοίχιση σελίδας και την υπάρχουσα θέση διακοπής, που συχνά οδηγεί σε μικρότερο πρώτο αίτημα — συνήθως προσγειώνεται κοντά σε αυτό το σχήμα των 72 KB σε μια διαδικασία που ξεκίνησε πρόσφατα.

Δεύτερον, από το glibc 2.26, ο εκχωρητής αρχικοποιεί μια τοπική κρυφή μνήμη νήματος (tcache) κατά την πρώτη χρήση. Η tcache περιέχει 64 bins (ένα ανά κατηγορία μεγέθους μικρής κατανομής), το καθένα ικανό να κρατήσει έως και 7 αποθηκευμένα κομμάτια. Το ίδιο το tcache_perthread_struct καταναλώνει περίπου 1 KB, αλλά η πράξη της αρχικοποίησής του ενεργοποιεί την ευρύτερη ρύθμιση αρένα. Τρίτον, ο χρόνος εκτέλεσης της C++ έχει ήδη πραγματοποιήσει εκχωρήσεις πριν καν εκτελεστεί η main() σας — στατικοί κατασκευαστές, η προετοιμασία buffer iostream για το std::cout και τους φίλους και η ρύθμιση τοπικών ρυθμίσεων όλα συμβάλλουν σε αυτό το αρχικό αποτύπωμα σωρού.

Το σύστημα Arena και γιατί η προκατανομή είναι έξυπνη

Η απόφαση να εκχωρήσετε εκ των προτέρων ένα σημαντικό κομμάτι μνήμης αντί να το ζητήσετε αποσπασματικά δεν είναι τυχαία υλοποίησης. Είναι ένας σκόπιμος μηχανικός συμβιβασμός που έχει τις ρίζες του σε δεκαετίες εμπειρίας προγραμματισμού συστημάτων. Κάθε κλήση σε brk() ή mmap() περιλαμβάνει μια εναλλαγή περιβάλλοντος από το χώρο χρήστη στον χώρο του πυρήνα, τροποποίηση των αντιστοιχίσεων εικονικής μνήμης της διαδικασίας και πιθανές ενημερώσεις πίνακα σελίδων. Σε σύγχρονο υλικό, μια μεμονωμένη κλήση συστήματος κοστίζει περίπου 100-200 νανοδευτερόλεπτα — ασήμαντο σε απομόνωση, καταστροφική σε κλίμακα.

Σκεφτείτε ένα πρόγραμμα που κάνει 10.000 μικρές εκχωρήσεις κατά την προετοιμασία. Χωρίς προκατανομή, αυτό θα σήμαινε 10.000 κλήσεις συστήματος, που κοστίζουν περίπου 1-2 χιλιοστά του δευτερολέπτου καθαρού γενικού κόστους. Με έναν κατανεμητή που βασίζεται σε αρένα, η πρώτη ενεργοποίηση κατανομής

Build Your Business OS Today

From freelancers to agencies, Mewayz powers 138,000+ businesses with 208 integrated modules. Start free, upgrade when you grow.

Create Free Account →
...

Frequently Asked Questions

Γιατί ακριβώς απαιτείται αυτή η μεγάλη ποσότητα μνήμης για ένα απλό νέο αντικείμενο;

Ο φαύλος υλικός δείκτης (raw pointer) που επιστρέφει ο operator new δεν είναι το μόνο που χρειάζεται το πρόγραμμα σας. Το runtime environment απαιτεί επιπλέον δομή δεδομένων για να διαχειριστεί την ζητούμενη μνήμη. Αυτή η δομή, γνωστή ως "bookkeeping overhead", περιλαμβάνει πληροφορίες για τη διαχείριση μνήμης όπως ο δείκτης του επόμενου free block, ο δείκτης του προηγούμενου block, και άλλα metadata. Συνήθως, αυτό το overhead είναι περίπου 8-16 byte για κάθε allocation, το οποίο προσθέτει σημαντικό κόστος όταν κάνετε μικρές κατανομές.

Πώς μπορεί να αποφευχθεί αυτή η περιττή κατανομή μνήμης;

Μια αποτελεσματική μέθοδος είναι να χρησιμοποιείτε pool allocation και να κρατάτε ένα προετοιμασμένο οριζόντιο χώρο μνήμης για αντικείμενα της ίδια περιγραφής. Μπορείτε επίσης να εξετάσετε object pools ή free lists για να αποφύγετε την επανάληψη του overhead κάθε φορά. Για μικρές κατανομές, μερικές modern C++ implementations παρέχουν specialized allocators που εξοικονομούν μνήμη χειρίζοντας μικρές κατανομές με διαφορετικό τρόπο. Ο τελικός σας στιγμιαίος, σίγουρα, είναι να χρησιμοποιείτε std::vector ή std::array αντί για δυναμικές κατανομές όταν είναι δυνατόν.

Είναι αυτός ο αριθμός 72 KB σταθερός για όλες τις εφαρμογές;

Ο ακριβής αριθμός μνήμης μπορεί να ποικίλλει ανάλογα με το compiler, το runtime environment, τις συστηματικές ρυθμίσεις και ακόμη και το λειτουργικό σύστημα. Εντούτοις, τα 72 KB είναι σχ

Try Mewayz Free

All-in-one platform for CRM, invoicing, projects, HR & more. No credit card required.

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