Hacker News

რატომ არის პირველი C++ (მ) განაწილება ყოველთვის 72 კბ?

კომენტარები

1 min read Via joelsiks.com

Mewayz Team

Editorial Team

Hacker News

საიდუმლო თქვენი პირველი C++ განაწილების მიღმა

თქვენ წერთ მარტივ C++ პროგრამას. ერთი ახალი ინტ. ოთხი ბაიტი. თქვენ ამუშავებთ strace ან თქვენს საყვარელ მეხსიერების პროფილერს და აი, თქვენმა პროცესმა მოითხოვა დაახლოებით 72 KB ოპერაციული სისტემიდან. არა 4 ბაიტი. არა 64 ბაიტი. სრული 72 KB. თუ ოდესმე გიყურებთ ამ ნომერზე და დაფიქრებულხართ თუ არა თქვენი ინსტრუმენტები, თქვენ მარტო არ ხართ. ეს ერთი შეხედვით უცნაური ქცევა ერთ-ერთი ყველაზე ხშირად დასმული შეკითხვაა C++-ის დეველოპერებს შორის, რომლებიც პირველად იჭრებიან მეხსიერების ინტერიერში და პასუხი მიგვიყვანს მომხიბლავ მოგზაურობაში იმ ფენებში, რომლებიც მდებარეობს თქვენს კოდსა და რეალურ აპარატურას შორის.

რა ხდება, როცა დარეკავთ ახალი

72 კბაიტიანი ფიგურის გასაგებად, თქვენ უნდა მოძებნოთ განაწილების სრული ჯაჭვი. როდესაც თქვენი C++ კოდი ახორციელებს new int, შემდგენელი თარგმნის მას ზარად ახალი ოპერატორი, რომელიც Linux სისტემების უმეტესობაში გადასცემს malloc-ს glibc-დან. მაგრამ malloc პირდაპირ არ სთხოვს ბირთვს მეხსიერების 4 ბაიტს. ბირთვი მუშაობს გვერდებზე - ჩვეულებრივ 4 KB x86_64-ზე - და სისტემური ზარის ღირებულება უზარმაზარია მეხსიერების მარტივ წვდომასთან შედარებით. brk() ან mmap()-ის გამოძახება ყოველი ინდივიდუალური განაწილებისთვის ნებისმიერ არატრივიალურ პროგრამას შეაჩერებს.

სანაცვლოდ, glibc-ის მეხსიერების გამანაწილებელი — იმპლემენტაცია სახელწოდებით ptmalloc2, რომელიც თავად დუგ ლეას კლასიკური dlmalloc-დან არის წარმოშობილი — შუამავლის როლს ასრულებს. ის წინასწარ ითხოვს მეხსიერების დიდ ბლოკებს ბირთვიდან, შემდეგ ჭრის მათ პატარა ნაჭრებად, როგორც ეს თქვენს პროგრამას სჭირდება. ეს არის ფუნდამენტური მიზეზი, რის გამოც თქვენი პირველი 4 ბაიტიანი განაწილება იწვევს ოპერაციულ სისტემას ბევრად უფრო დიდ მოთხოვნას. გამნაწილებელი არ არის მფლანგველი. ეს არის სტრატეგიული.

72 კბაიტის დაშლა: სად მიდიან ბაიტები

თავდაპირველი განაწილების ზედნადები მომდინარეობს რამდენიმე განსხვავებული კომპონენტისგან, რომელთა ინიციალიზაცია უნდა მოხდეს სამუშაო დროის განმავლობაში, სანამ მას შეუძლია გადმოგცეთ გამოსაყენებელი მეხსიერების თუნდაც ერთი ბაიტი. თითოეული კომპონენტის გაგება განმარტავს, თუ რატომ მოდის რიცხვი იქ, სადაც ის ხდება.

პირველ რიგში, glibc-ის malloc ახდენს მთავარი არენის ინიციალიზაციას - პირველადი ბუღალტრული აღრიცხვის სტრუქტურა, რომელიც თვალყურს ადევნებს ყველა განაწილებას მთავარ თემაში. ეს არენა მოიცავს მეტამონაცემებს გროვისთვის, უფასო სიის მაჩვენებლებსა და ურნების სტრუქტურებს სხვადასხვა განაწილების ზომისთვის. ალოკატორი აგრძელებს პროგრამის შესვენებას sbrk()-ის მეშვეობით და თავდაპირველი გაფართოება რეგულირდება შიდა პარამეტრით, სახელწოდებით M_TOP_PAD, რომელიც ნაგულისხმევია 128 კბაიტიანი შიგთავსით. თუმცა, ფაქტობრივი საწყისი მოთხოვნა მორგებულია გვერდის გასწორებისა და არსებული შესვენების პოზიციისთვის, რაც ხშირად იწვევს უფრო მცირე პირველ მოთხოვნას — ჩვეულებრივ, ახლად დაწყებულ პროცესზე 72 KB ფიგურასთან ახლოს.

მეორე, glibc 2.26-დან მოყოლებული, ალოკატორი ახდენს თემა ლოკალური ქეში (tcache) ინიციალიზებას პირველი გამოყენებისას. tcache შეიცავს 64 ურნას (თითო თითო მცირე განაწილების ზომის კლასში), თითოეულს შეუძლია შეინახოს 7-მდე ქეშირებული ბლოკი. თავად tcache_perthread_struct მოიხმარს დაახლოებით 1 კბაიტს, მაგრამ მისი ინიციალიზაციის აქტი იწვევს ფართო არენის დაყენებას. მესამე, C++ გაშვების დრომ უკვე შეასრულა განაწილებები თქვენი main() გაშვებამდე — სტატიკური კონსტრუქტორები, iostream ბუფერის ინიციალიზაცია std::cout-ისთვის და მეგობრები და ლოკალური დაყენება, ეს ყველაფერი ხელს უწყობს ამ საწყის გროვის კვალს.

არენა სისტემა და რატომ არის წინასწარი განაწილება ჭკვიანი

გადაწყვეტილება მეხსიერების მნიშვნელოვანი ნაწილის წინასწარ გამოყოფის ნაცვლად მისი ცალ-ცალკე მოთხოვნის შესახებ არ არის განხორციელების შემთხვევითი. ეს არის მიზანმიმართული საინჟინრო კომპრომისი, რომელიც დაფუძნებულია სისტემების პროგრამირების ათწლეულების გამოცდილებაზე. ყოველი ზარი brk() ან mmap() გულისხმობს კონტექსტური გადართვას მომხმარებლის სივრციდან ბირთვის სივრცეში, პროცესის ვირტუალური მეხსიერების რუკების შეცვლასა და გვერდის ცხრილის პოტენციურ განახლებებს. თანამედროვე აპარატურაზე ერთი სისტემური ზარი დაახლოებით 100-200 ნანოწამი ღირს - ტრივიალური იზოლირებულად, მასშტაბით კატასტროფული.

განიხილეთ პროგრამა, რომელიც ინიციალიზაციის დროს აკეთებს 10000 მცირე გამოყოფას. წინასწარი განაწილების გარეშე, ეს ნიშნავს 10,000 სისტემურ ზარს, რაც დაახლოებით 1-2 მილიწამში ჯდება სუფთა ზედნადები. არენაზე დაფუძნებული ალოკატორით, პირველი განაწილება იწვევს ერთ სისტემურ ზარს, ხოლო შემდგომი 9,999 განაწილება სრულად ემსახურება მომხმარებლის სივრცეში მაჩვენებლის არითმეტიკისა და დაკავშირებული სიის ოპერაციების მეშვეობით - თითოეულს დაახლოებით 10-50 ნანოწამი სჭირდება. მათემატიკა ცალსახაა: წინასწარი განაწილება იგებს სიდიდის ბრძანებებს.

72 კბაიტი, რომელსაც ხედავთ თქვენს პირველ განაწილებაზე, არ არის ფუჭად დაკარგული მეხსიერება — ეს არის შესრულების ინვესტიცია. ალოკატორი დებს ფსონს, რომ თქვენი პროგრამა მალე უფრო მეტ გამოყოფას განახორციელებს და პრაქტიკულად ყველა რეალურ სცენარში, ეს ფსონი მშვენივრად იხდის. გამოუყენებელი ვირტუალური მისამართების სივრცის ღირებულება არსებითად ნულოვანია თანამედროვე 64-ბიტიან სისტემებზე.

ვირტუალური მეხსიერება ფიზიკური მეხსიერების წინააღმდეგ: რატომ არ აქვს მნიშვნელობა

დეველოპერებს შორის საერთო საზრუნავი, რომლებიც პირველად ხვდებიან ამ ქცევას, არის რესურსების ნარჩენები. თუ მე მჭირდება მხოლოდ 4 ბაიტი, რატომ მოიხმარს ჩემი პროგრამა 72 კბ? კრიტიკული შეხედულება არის ის, რომ ვირტუალური მეხსიერება არ არის ფიზიკური მეხსიერება. როდესაც glibc აგრძელებს პროგრამის შესვენებას 72 კბ-ით, ბირთვი განაახლებს პროცესის ვირტუალური მეხსიერების რუკებს, მაგრამ ის დაუყოვნებლივ არ უბრუნებს ამ გვერდებს ფიზიკური ოპერატიული მეხსიერებით. ფაქტობრივი ფიზიკური გვერდები მოთხოვნისამებრ გამოიყოფა გვერდის ხარვეზების გამო — მხოლოდ მაშინ, როდესაც თქვენი პროგრამა წერს კონკრეტულ მისამართზე, ბირთვი მას მეხსიერების რეალურ გვერდს ანიჭებს.

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

ეს ნიშნავს, რომ მიუხედავად იმისა, რომ თქვენი პროცესის ვირტუალური ზომა იზრდება 72 კბ-ით, მისი რეზიდენტული ნაკრების ზომა (RSS) — რეალურად მოხმარებული ფიზიკური RAM-ის რაოდენობა — იზრდება მხოლოდ იმ გვერდებით, რომლებსაც რეალურად ეხებით. ერთი ახალი ინფორმაციისთვის, ეს არის, როგორც წესი, ერთი 4 KB გვერდი, პლუს ნებისმიერი გვერდი, რომელსაც იკავებს არენის მეტამონაცემები. დარჩენილი ვირტუალური სივრცე იქ დგას, გამოსაყენებლად მზადაა, მისამართების სივრცის გარდა არაფერი ღირს - საიდანაც 128 ტბაიტი გაქვთ 64-ბიტიან Linux სისტემაზე.

ეს განსხვავება კრიტიკულია საწარმოო აპლიკაციების პროფილირებისა და მონიტორინგის დროს. თუ თქვენ ქმნით პროგრამულ უზრუნველყოფას, რომელიც საჭიროებს რეალურ რესურსების მოხმარებას - იქნება ეს SaaS backend, მიკროსერვისი თუ ანალიტიკური მილსადენი, როგორიცაა ისეთ პლატფორმებზე, როგორიცაა Mewayz ბიზნეს ოპერაციებისთვის - ყოველთვის უნდა აკონტროლოთ RSS, ვიდრე ვირტუალური ზომა. ისეთ ინსტრუმენტებს, როგორიცაა /proc/[pid]/smaps, valgrind --tool=massif და pmap შეუძლია მოგაწოდოთ ზუსტი ფიზიკური მეხსიერების კვალი, ვიდრე შეცდომაში შეიყვანოთ ვირტუალური მეხსიერების ფიგურები.

როგორ უმკლავდებიან სხვადასხვა ალოკატორები პირველ გამოყოფას

72 KB ფიგურა სპეციფიკურია glibc-ის ptmalloc2-ისთვის. სხვა ალოკატორები აკეთებენ განსხვავებულ კომპრომისებს და საწყისი გადანაწილების ზედნადები შესაბამისად იცვლება. ამ განსხვავებების გაგება ღირებულია შესრულებისადმი მგრძნობიარე აპლიკაციებისთვის ალოკატორის არჩევისას.

  • jemalloc (გამოიყენება Facebook-ის მიერ, FreeBSD) — იყენებს უფრო მარცვლოვან არენას სტრუქტურას thread-local cache-ებით. საწყისი ზედნადები, როგორც წესი, უფრო მაღალია (ხშირად 200+ კბაიტი), მაგრამ უზრუნველყოფს უკეთეს მრავალძალიან შესრულებას დაბლოკვის შემცირების გამო.
  • tcmalloc (Google-ის Thread-Caching Malloc) — ნაგულისხმევად გამოყოფს თითო თემაში ქეშს დაახლოებით 2 მბ, აგრესიული წინასწარი განაწილებით. საწყისი ზედნადები უფრო მაღალია, მაგრამ შემდგომი მცირე გამოყოფა ძალიან სწრაფია.
  • musl libc's malloc — იყენებს ბევრად მარტივ დიზაინს mmap-ზე დაფუძნებული ყველა გამოყოფისთვის. საწყისი ზედნადები მინიმალურია (ხშირად მხოლოდ 4 KB თითო განაწილებაზე), მაგრამ თითო გამოყოფის ღირებულება უფრო მაღალია უფრო ხშირი სისტემური ზარების გამო.
  • mimalloc (Microsoft) — იყენებს სეგმენტზე დაფუძნებულ განაწილებას 64 მბ სეგმენტებით. პირველი განაწილება იწვევს 64 მბ ვირტუალურ დაჯავშნას (მინიმალური ფიზიკური ვალდებულებით), მისამართების სივრცის ვაჭრობას განსაკუთრებული ლოკაციისა და გამტარუნარიანობისთვის.

არჩევანი ამ ალოკატორებს შორის მთლიანად დამოკიდებულია თქვენს დატვირთვაზე. გრძელვადიანი სერვერული აპლიკაციებისთვის მძიმე მრავალნაკადიანი განაწილებით, jemalloc ან tcmalloc ჩვეულებრივ აჭარბებს glibc-ის ნაგულისხმევს. მეხსიერებით შეზღუდული ჩაშენებული სისტემებისთვის, musl-ის უფრო მარტივი მიდგომა შეიძლება სასურველი იყოს, მიუხედავად დაბალი გამტარუნარიანობისა. ზოგადი დანიშნულების დესკტოპის და სერვერის აპლიკაციებისთვის, ptmalloc2-ის 72 KB საწყისი ოვერჰედი წარმოადგენს გონივრულ ნაგულისხმევს, რომელიც კარგად მუშაობს რეგულირების გარეშე.

თავდაპირველი განაწილების ქცევის რეგულირება

თუ ნაგულისხმევი 72 KB საწყისი ზედნადები ნამდვილად პრობლემურია თქვენი გამოყენების შემთხვევისთვის — შესაძლოა, თქვენ აწარმოებთ ათასობით ხანმოკლე პროცესს, თითოეული აკეთებს მხოლოდ რამდენიმე გამოყოფას — glibc უზრუნველყოფს რამდენიმე რეგულირებას mallopt() და MALLOC_ გარემოს ცვლადების ოჯახის

მეშვეობით.

M_TOP_PAD პარამეტრი აკონტროლებს, თუ რამდენ დამატებით მეხსიერებას ითხოვს გამანაწილებელი იმაზე მეტი, რაც მაშინვე საჭიროა. 0-ზე დაყენება mallopt(M_TOP_PAD, 0)-ით ეუბნება განაწილებას, მოითხოვოს მხოლოდ ის, რაც საჭიროა, რაც მნიშვნელოვნად ამცირებს საწყისი ზედნადებს. M_MMAP_THRESHOLD პარამეტრი აკონტროლებს ზომას, რომლის ზემოთაც გამოყოფები იყენებენ mmap არენის ნაცვლად. M_TRIM_THRESHOLD აკონტროლებს, როდის დაბრუნდება გამოთავისუფლებული მეხსიერება OS-ში. და რადგან glibc 2.26, glibc.malloc.tcache_count და glibc.malloc.tcache_max tunables გაძლევთ საშუალებას აკონტროლოთ ნაკადის ქეშის ქცევა.

თუმცა, სიფრთხილე: ამ პარამეტრების დარეგულირება ფრთხილად ბენჩმარკინგის გარეშე თითქმის ყოველთვის აუარესებს სიტუაციას. ნაგულისხმევი არჩეული იქნა ვრცელი რეალური სამყაროს პროფილის საფუძველზე და ისინი წარმოადგენენ ტკბილ ადგილს სამუშაო დატვირთვების დიდი უმრავლესობისთვის. თუ თქვენ არ გაქვთ ძლიერი მტკიცებულება წარმოების პროფილირებიდან, რომ malloc-ის ზედნადები შეფერხებაა - და თქვენ გაზომეთ თქვენი ცვლილებების გავლენა - დატოვეთ ნაგულისხმევი პარამეტრები. ალოკატორის ნაადრევი ოპტიმიზაცია არის იაკის გაპარსვის განსაკუთრებით მზაკვრული ფორმა, რომელმაც უთვალავი საინჟინრო საათი მოიხმარა უმნიშვნელო სარგებლისთვის.

რას გვასწავლის ეს სისტემური პროგრამირების შესახებ

72 KB პირველი განაწილების საიდუმლო არის გაკვეთილი აბსტრაქციის ფენების შესახებ. C++ გაძლევთ ილუზიას, რომ new int გამოყოფს 4 ბაიტს. ენის სტანდარტი ასე ამბობს. თქვენი გონებრივი მოდელი ასე ამბობს. მაგრამ თქვენს კოდსა და აპარატურას შორის არის დახვეწილი სისტემების დასტა - C++ გაშვების დრო, C ბიბლიოთეკის გამანაწილებელი, ბირთვის ვირტუალური მეხსიერების ქვესისტემა და აპარატურის MMU და TLB - თითოეული ამატებს საკუთარ ქცევებს, ოპტიმიზაციებს და ზედნადებს.

ეს არ არის ხარვეზი. ეს არის სისტემური პროგრამული უზრუნველყოფის მთელი წერტილი. თითოეული ფენა არსებობს რეალური პრობლემის გადასაჭრელად: ალოკატორი არსებობს, ასე რომ თქვენ არ გჭირდებათ სისტემური ზარების განხორციელება ყველა განაწილებისთვის. ვირტუალური მეხსიერების სისტემა არსებობს, ასე რომ თქვენ არ გჭირდებათ უშუალოდ ფიზიკური მეხსიერების მართვა. გვერდის შეცდომის დამმუშავებელი არსებობს, ამიტომ მეხსიერება ზარმაცი და ეფექტურად არის ჩართული. თითოეული ფენა ცვლის მცირე გამჭვირვალობას დიდი შესრულებისა და მოხერხებულობისთვის.

დეველოპერები, რომლებიც აშენებენ ყველაზე სანდო, ყველაზე მაღალეფექტურ სისტემებს, არიან ისინი, ვისაც ესმით ეს ფენები - არა იმიტომ, რომ მათ მუდმივად უნდა იფიქრონ მათზე, არამედ იმიტომ, რომ როდესაც რაღაც მოულოდნელი ხდება (როგორც იდუმალი 72 KB განაწილება), მათ აქვთ გონებრივი მოდელი იმის გასაგებად, თუ რატომ. მიუხედავად იმისა, თქვენ აშენებთ რეალურ დროში სავაჭრო სისტემას, თამაშის ძრავას ან ბიზნეს პლატფორმას, რომელიც ემსახურება ათასობით მომხმარებელს, იმის მსჯელობის შესაძლებლობა, თუ რას აკეთებს თქვენი კოდი რეალურად სისტემის დონეზე, არის ის, რაც განასხვავებს კომპეტენტურ დეველოპერებს გამონაკლისებისგან. 72 KB არ არის შეცდომა. ეს არის თქვენი გამანაწილებელი თავის საქმეს ბრწყინვალედ ასრულებს.

შექმენით თქვენი ბიზნესის OS დღეს

დაწყებული შტატგარეშე მომუშავეებიდან დაწყებული სააგენტოებით დამთავრებული, Mewayz ახორციელებს 138000+ ბიზნესს 207 ინტეგრირებული მოდულით. დაიწყეთ უფასოდ, განაახლეთ, როცა გაიზრდებით.

შექმენითუფასო