Hacker News

Чаму першае размеркаванне C++ (m) заўсёды складае 72 КБ?

Каментарыі

1 min read Via joelsiks.com

Mewayz Team

Editorial Team

Hacker News

Таямніца вашага першага размеркавання C++

Вы пішаце простую праграму на C++. Адзіны новы int. Чатыры байта. Вы запускаеце strace або ваш любімы прафіліроўшчык памяці, і вось ён — ваш працэс толькі што запытаў у аперацыйнай сістэмы прыкладна 72 КБ. Не 4 байты. Не 64 байт. Поўныя 72 Кб. Калі вы калі-небудзь глядзелі на гэтую лічбу і задаваліся пытаннем, ці хлусіць вам ваша абсталяванне, вы не самотныя. Гэтыя, здавалася б, дзіўныя паводзіны - адно з найбольш часта задаваных пытанняў сярод распрацоўшчыкаў C++, якія ўпершыню вывучаюць унутраную памяць, і адказ вядзе нас у займальнае падарожжа па слаях, якія знаходзяцца паміж вашым кодам і рэальным абсталяваннем.

Што адбываецца, калі вы тэлефануеце новы

Каб зразумець лічбу ў 72 КБ, трэба прасачыць увесь ланцужок размеркавання. Калі ваш код C++ выконвае new int, кампілятар пераводзіць гэта ў выклік operator new, які ў большасці сістэм Linux дэлегуе malloc з glibc. Але malloc не запытвае ў ядра непасрэдна 4 байты памяці. Ядро працуе ў выглядзе старонак — звычайна 4 КБ на x86_64 — і кошт сістэмнага выкліку велізарны ў параўнанні з простым доступам да памяці. Выклік brk() або mmap() для кожнага асобнага размеркавання прывядзе да прыпынку любой нетрывіяльнай праграмы.

Замест гэтага размеркавальнік памяці glibc — рэалізацыя пад назвай ptmalloc2, якая сама паходзіць ад класічнага dlmalloc Дуга Лі — дзейнічае як пасярэднік. Ён загадзя запытвае вялікія блокі памяці ў ядра, а потым разбівае іх на больш дробныя кавалкі па меры патрэбы вашай праграмы. Гэта асноўная прычына, па якой ваша першае 4-байтавае размеркаванне выклікае нашмат большы запыт да аперацыйнай сістэмы. Размеркавальнік не марнатраўны. Гэта стратэгічна.

Разбор 72 КБ: Куды ідуць байты

Накладныя выдаткі на пачатковае размеркаванне паходзяць з некалькіх розных кампанентаў, якія асяроддзе выканання павінна ініцыялізаваць, перш чым яно зможа перадаць вам хоць адзін байт карыснай памяці. Разуменне кожнага кампанента тлумачыць, чаму лічба трапляе менавіта там.

Па-першае, glibc malloc ініцыялізуе галоўную арэну — асноўную структуру бухгалтэрыі, якая адсочвае ўсе размеркаванні ў галоўным патоку. Гэтая арэна ўключае ў сябе метаданыя для кучы, паказальнікі вольнага спісу і структуры бункераў для розных памераў размеркавання. Размяркоўвальнік пашырае перапынак праграмы праз sbrk(), а першапачатковае пашырэнне рэгулюецца ўнутраным параметрам пад назвай M_TOP_PAD, які па змаўчанні складае 128 КБ запаўнення. Тым не менш, фактычны першапачатковы запыт карэктуецца з улікам выраўноўвання старонкі і існуючай пазіцыі разрыву, што часта прыводзіць да меншага першага запыту - звычайна прыпадае на лічбу ў 72 КБ у толькі што запушчаным працэсе.

Па-другое, пачынаючы з glibc 2.26, размеркавальнік ініцыялізуе лакальны кэш патокаў (tcache) пры першым выкарыстанні. Tcache змяшчае 64 бункера (па адным на клас малога памеру размеркавання), кожны з якіх здольны ўтрымліваць да 7 кэшаваных фрагментаў. Сам tcache_perthread_struct займае каля 1 КБ, але акт яго ініцыялізацыі запускае больш шырокую наладу арэны. Па-трэцяе, асяроддзе выканання C++ ужо выканала размеркаванне яшчэ да таго, як запусціцца ваш main() — статычныя канструктары, ініцыялізацыя буфера iostream для std::cout і сяброў, а таксама налада лакалі - усё гэта спрыяе першапачатковаму адбітку кучы.

Сістэма Arena і чаму папярэдняе размеркаванне разумнае

Рашэнне папярэдне выдзеліць значную частку памяці, а не запытваць яе па частках, не з'яўляецца выпадковасцю рэалізацыі. Гэта наўмысны інжынерны кампраміс, заснаваны на дзесяцігоддзях вопыту сістэмнага праграмавання. Кожны выклік brk() або mmap() уключае пераключэнне кантэксту з прасторы карыстальніка ў прастору ядра, мадыфікацыю супастаўленняў віртуальнай памяці працэсу і патэнцыйныя абнаўленні табліцы старонак. На сучасным абсталяванні адзін сістэмны выклік каштуе прыкладна 100-200 нанасекунд - трывіяльна ў ізаляцыі, катастрафічна ў маштабе.

Разгледзім праграму, якая робіць 10 000 невялікіх размеркаванняў падчас ініцыялізацыі. Без папярэдняга размеркавання гэта азначала б 10 000 сістэмных выклікаў, якія каштуюць прыкладна 1-2 мілісекунды чыстых накладных выдаткаў. З размеркавальнікам на аснове арэны першае размеркаванне запускае адзіны сістэмны выклік, а наступныя 9999 размеркаванняў абслугоўваюцца цалкам у карыстальніцкай прасторы з дапамогай арыфметыкі паказальнікаў і аперацый са звязаным спісам — кожная з іх займае прыкладна 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) — аб'ём фізічнай аператыўнай памяці, які фактычна спажываецца — павялічваецца толькі на тыя старонкі, якія вы сапраўды дакранаецеся. Для аднаго новага int гэта звычайна адна старонка памерам 4 КБ плюс любыя старонкі, якія займаюць метададзеныя арэны. Астатняя віртуальная прастора знаходзіцца там, гатовая да выкарыстання, не каштуючы нічога, акрамя адраснай прасторы - з якіх у вас ёсць 128 ТБ у 64-бітнай сістэме Linux.

Гэта адрозненне вельмі важна пры прафіляванні і маніторынгу вытворчых прыкладанняў. Калі вы ствараеце праграмнае забеспячэнне, якому неабходна адсочваць рэальнае спажыванне рэсурсаў — няхай гэта будзе бэкэнд SaaS, мікрасэрвіс або канвеер аналітыкі, напрыклад, тыя, што працуюць на такіх платформах, як Mewayz для бізнес-аперацый, — вам заўсёды трэба кантраляваць RSS, а не віртуальны памер. Такія інструменты, як /proc/[pid]/smaps, valgrind --tool=massif і pmap, могуць даць вам дакладныя сляды фізічнай памяці, а не ўвесці ў зман віртуальную памяць.

Як розныя размеркавальнікі апрацоўваюць першае размеркаванне

Паказчык у 72 КБ характэрны для ptmalloc2 glibc. Іншыя размеркавальнікі робяць розныя кампрамісы, і першапачатковыя выдаткі на размеркаванне змяняюцца адпаведна. Разуменне гэтых адрозненняў каштоўна пры выбары размеркавальніка для прыкладанняў, адчувальных да прадукцыйнасці.

  • jemalloc (выкарыстоўваецца Facebook, FreeBSD) — выкарыстоўвае больш дэталёвую структуру арэны з лакальнымі кэшамі патокаў. Першапачатковыя накладныя выдаткі, як правіла, вышэйшыя (часта 200+ КБ), але забяспечваюць лепшую шматструменную прадукцыйнасць з-за зніжэння канкурэнцыі за блакіроўку.
  • tcmalloc (Google's Thread-Caching Malloc) — выдзяляе для кожнага патоку кэш прыблізна 2 МБ па змаўчанні з агрэсіўным папярэднім размеркаваннем. Першапачатковыя накладныя выдаткі вышэй, але наступныя невялікія размеркаванні надзвычай хуткія.
  • malloc musl libc — Выкарыстоўвае значна больш просты дызайн, заснаваны на mmap для ўсіх размеркаванняў. Першапачатковыя выдаткі мінімальныя (часта ўсяго 4 КБ на размеркаванне), але кошт кожнага размеркавання вышэй з-за больш частых сістэмных выклікаў.
  • mimalloc (Microsoft) — выкарыстоўвае размеркаванне на аснове сегментаў з сегментамі па 64 МБ. Першае размеркаванне запускае віртуальнае рэзерваванне 64 МБ (з мінімальным фізічным абавязацельствам), абмен адраснай прасторай на выключную лакальнасць і прапускную здольнасць.

Выбар паміж гэтымі размеркавальнікамі цалкам залежыць ад вашай нагрузкі. Для доўгапрацуючых серверных прыкладанняў з інтэнсіўным шматструменным размеркаваннем jemalloc або tcmalloc звычайна перавышае па змаўчанні glibc. Для ўбудаваных сістэм з абмежаванай памяццю больш просты падыход musl можа быць пераважней, нягледзячы на ​​меншую прапускную здольнасць. Для большасці настольных і серверных прыкладанняў агульнага прызначэння першапачатковыя накладныя выдаткі ptmalloc2 у 72 КБ уяўляюць сабой разумнае значэнне па змаўчанні, якое добра працуе без налады.

Настройка пачатковага размеркавання

Калі першапачатковыя накладныя выдаткі па змаўчанні ў 72 КБ сапраўды праблематычныя для вашага варыянту выкарыстання — магчыма, вы спараджаеце тысячы кароткачасовых працэсаў, кожны з якіх робіць толькі некалькі размеркаванняў — glibc забяспечвае некалькі наладжвальных параметраў праз mallopt() і сямейства зменных асяроддзя MALLOC_.

Параметр M_TOP_PAD кантралюе, колькі дадатковай памяці запытвае размеркавальнік звыш таго, што неадкладна неабходна. Усталяванне яго ў 0 з дапамогай mallopt(M_TOP_PAD, 0) загадвае размеркавальніку запытваць толькі тое, што неабходна, значна зніжаючы першапачатковыя накладныя выдаткі. Параметр M_MMAP_THRESHOLD кантралюе памер, вышэй за які размеркаванне выкарыстоўвае mmap замест арэны. M_TRIM_THRESHOLD кантралюе, калі вызваленая памяць вяртаецца ў АС. А пачынаючы з glibc 2.26, параметры glibc.malloc.tcache_count і glibc.malloc.tcache_max дазваляюць кіраваць паводзінамі кэша патокаў.

Аднак слова засцярогі: налада гэтых параметраў без стараннага параўнання амаль заўсёды пагаршае сітуацыю. Значэнні па змаўчанні былі выбраны на аснове шырокага прафілявання ў рэальным свеце, і яны ўяўляюць салодкае месца для пераважнай большасці працоўных нагрузак. Калі ў вас няма пераканаўчых доказаў прафілявання вытворчасці, што накладныя выдаткі malloc з'яўляюцца вузкім месцам - і вы вымералі ўплыў вашых змяненняў - пакіньце значэнні па змаўчанні. Заўчасная аптымізацыя размеркавальніка з'яўляецца асабліва падступнай формай галення яка, якая паглынула незлічоныя гадзіны інжынераў для нязначнай карысці.

Чаму гэта нас вучыць аб сістэмным праграмаванні

Таямніца першага размеркавання 72 КБ - гэта, па сутнасці, урок аб узроўнях абстракцыі. C++ стварае ілюзію, што new int выдзяляе 4 байты. Так гаворыць моўны стандарт. Ваша разумовая мадэль кажа так. Але паміж вашым кодам і апаратным забеспячэннем знаходзіцца стос складаных сістэм — асяроддзе выканання C++, размеркавальнік бібліятэкі C, падсістэма віртуальнай памяці ядра і апаратныя MMU і TLB — кожная з якіх дадае ўласныя паводзіны, аптымізацыю і накладныя выдаткі.

Гэта не недахоп. Гэта ўвесь сэнс сістэмнага праграмнага забеспячэння. Кожны ўзровень існуе, каб вырашыць рэальную праблему: існуе размеркавальнік, таму вам не трэба рабіць сістэмныя выклікі для кожнага размеркавання. Сістэма віртуальнай памяці існуе, таму вам не трэба непасрэдна кіраваць фізічнай памяццю. Апрацоўшчык памылак старонкі існуе, таму памяць выдзяляецца ляніва і эфектыўна. Кожны пласт мяняе невялікую колькасць празрыстасці на высокую прадукцыйнасць і зручнасць.

Распрацоўшчыкі, якія ствараюць самыя надзейныя і высокапрадукцыйныя сістэмы, - гэта тыя, хто разумее гэтыя ўзроўні - не таму, што ім трэба пастаянна пра іх думаць, а таму, што калі здараецца нешта нечаканае (напрыклад, загадкавае размеркаванне 72 КБ), у іх ёсць разумовая мадэль, каб зразумець, чаму. Незалежна ад таго, ствараеце вы гандлёвую сістэму ў рэжыме рэальнага часу, гульнявы ​​механізм або бізнес-платформу, якая абслугоўвае тысячы карыстальнікаў, здольнасць разважаць аб тым, што насамрэч робіць ваш код на сістэмным узроўні, - гэта тое, што адрознівае кампетэнтных распрацоўшчыкаў ад выключных. 72 КБ - гэта не памылка. Гэта ваш размеркавальнік, які выдатна выконвае сваю працу.

Стварыце сваю бізнес-АС сёння

Ад фрылансераў да агенцтваў, Mewayz падтрымлівае больш за 138 000 прадпрыемстваў з дапамогай 207 інтэграваных модуляў. Пачніце бясплатна, абнаўляйце па меры росту.

Стварыць бясплатны ўліковы запіс →

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