Hacker News

Эмне үчүн биринчи C++ (m) бөлүштүрүү дайыма 72 KB?

Комментарийлер

1 min read Via joelsiks.com

Mewayz Team

Editorial Team

Hacker News

Сиздин биринчи C++ бөлүштүрүүңүздүн сыры

Сиз жөнөкөй C++ программасын жазасыз. Жалгыз жаңы инт. Төрт байт. Сиз straceди же сүйүктүү эс тутум профилиңизди иштетесиз, жана бул жерде — процессиңиз операциялык тутумдан болжол менен 72 КБ сурады. 4 байт эмес. 64 байт эмес. Толук 72 КБ. Эгер сиз качандыр бир кезде бул санды тиктеп, инструментиңиз сизге калп айтып жатабы деп ойлонгон болсоңуз, анда сиз жалгыз эмессиз. Кызыктай көрүнгөн бул жүрүм-турум C++ иштеп чыгуучулары арасында биринчи жолу эс тутумдун ички түзүлүштөрүн казып жаткан эң көп берилүүчү суроолордун бири жана бул жооп бизди кодуңуз менен чыныгы жабдыктын ортосунда жайгашкан катмарлар аркылуу кызыктуу саякатка алып барат.

Сиз жаңы

деп чалганда эмне болот

72 КБ фигураны түшүнүү үчүн сиз бөлүштүрүүнүн толук чынжырына көз салышыңыз керек. Сиздин C++ кодуңуз new intди аткарганда, компилятор аны оператор newга чалууга которот, ал көпчүлүк Linux системаларында glibcден mallocга өкүлчүлүк кылат. Бирок malloc ядродон 4 байт эстутумду түз сурабайт. Ядро баракчаларда иштейт — адатта x86_64 боюнча 4 КБ — жана тутумдук чалуу баасы жөнөкөй эстутумга кирүүгө салыштырмалуу абдан чоң. Ар бир жеке бөлүштүрүү үчүн 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() иштей электе эле бөлүштүрүүлөрдү аткарган — статикалык конструкторлор, std::cout жана достор үчүн iostream буферин инициализациялоо жана жергиликтүү тилди орнотуу баары ошол баштапкы үймөк изине салым кошот.

Арена системасы жана эмне үчүн алдын ала бөлүштүрүү акылдуу

<б> Аны майда-чүйдөсүнө чейин талап кылуунун ордуна, эс тутумдун олуттуу бөлүгүн алдын ала бөлүштүрүү чечими ишке ашыруунун кокусунан эмес. Бул системалык программалоонун ондогон жылдардагы тажрыйбасына негизделген атайылап инженердик соода. brk() же mmap() ар бир чалуу контексттик мейкиндиктен өзөк мейкиндигине колдонуучу мейкиндигине өтүүнү, процесстин виртуалдык эстутум картасын өзгөртүүнү жана барак жадыбалынын потенциалдуу жаңыртууларын камтыйт. Заманбап жабдыктарда бир тутумдук чалуу болжол менен 100-200 наносекундду түзөт — өзүнчө маанилүү, масштабы боюнча катастрофалык.

Инициализация учурунда 10 000 кичинекей бөлүштүрүүчү программаны карап көрөлү. Алдын ала бөлүштүрүү болбосо, бул болжол менен 1-2 миллисекунддук таза кошумча чыгымды талап кылган 10 000 тутум чалууларын билдирет. Аренага негизделген бөлүштүргүч менен биринчи бөлүштүрүү бир системалык чалууга түрткү берет, ал эми кийинки 9,999 бөлүштүрүү толугу менен колдонуучу мейкиндигинде көрсөткүчтүн арифметикасы жана шилтемеленген тизме операциялары аркылуу тейленет - ар бири болжол менен 10-50 наносекундду алат. Математика бир түшүнүктүү: алдын ала бөлүштүрүү чоңдуктун тартиби боюнча утат.

Биринчи бөлүштүрүүдө көргөн 72 КБ эстутумду текке кетирбейт — бул натыйжалуулук үчүн инвестиция. Бөлүштүрүүчү сиздин программаңыз жакын арада көбүрөөк бөлүштүрүүлөрдү жасайт деп эсептейт жана иш жүзүндө ар бир реалдуу сценарийде бул коюм жакшы натыйжа берет. Колдонулбаган виртуалдык дарек мейкиндигинин баасы заманбап 64 биттик системаларда нөлгө барабар.

Виртуалдык эс жана физикалык эс: эмне үчүн бул маанилүү эмес

Мындай жүрүм-турумга биринчи жолу туш болгон иштеп чыгуучулардын жалпы тынчсыздануусу - ресурстарды ысырап кылуу. Мага болгону 4 байт керек болсо, эмне үчүн менин программасым 72 КБ керектеп жатат? Критикалык түшүнүк виртуалдык эс физикалык эс эмес. glibc программанын тыныгуусун 72 КБга узартканда, ядро ​​процесстин виртуалдык эс тутум карталарын жаңылайт, бирок ал ошол барактарды физикалык 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 →

Бул процессиңиздин виртуалдык өлчөмү 72 КБ көбөйгөнүнө карабастан, анын резиденттик топтому өлчөмү (RSS) — иш жүзүндө керектелген физикалык оперативдүү эстутумдун көлөмү — сиз тийген беттерге гана көбөйөт дегенди билдирет. Жалгыз жаңы int үчүн, адатта, бир 4 КБ барак, ошондой эле аренадагы метаберилиштер ээлеген бардык барактарды түзөт. Калган виртуалдык мейкиндик ошол жерде, колдонууга даяр, дарек мейкиндигинен башка эч нерсе талап кылбайт — анын ичинен сизде 64-бит Linux тутумунда 128 ТБ бар.

Бул айырмачылык өндүрүш колдонмолорун профилдештирүү жана көзөмөлдөөдө маанилүү. Эгер сиз чыныгы ресурстун керектелишине көз салуучу программалык камсыздоону куруп жатсаңыз - бул SaaS сервери болобу, микросервис болобу же бизнес операциялары үчүн Mewayz сыяктуу платформаларда иштеген аналитика түтүктөрү болобу - сиз ар дайым виртуалдык өлчөмдө эмес, RSS-ти көзөмөлдөшүңүз керек. /proc/[pid]/smaps, valgrind --tool=massif жана pmap сыяктуу куралдар виртуалдык эстутум көрсөткүчтөрүн адаштырбай, так физикалык эстутум издерин бере алат.

Биринчи бөлүштүрүүнү түрдүү бөлүштүргүчтөр кантип аткарышат

72 KB фигурасы glibcтин ptmalloc2ге мүнөздүү. Башка бөлүштүргүчтөр ар кандай соодалашууларды жасашат жана баштапкы бөлүштүрүү кошумча чыгымдары ошого жараша өзгөрөт. Бул айырмачылыктарды түшүнүү аткарууну сезгич колдонмолор үчүн бөлүштүргүчтү тандоодо маанилүү.

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

Бул бөлүштүргүчтөрдүн ортосундагы тандоо сиздин жумуш жүгүңүзгө жараша болот. Оор көп жиптүү бөлүштүрүү менен узакка созулган сервердик тиркемелер үчүн jemalloc же tcmalloc адатта glibc демейкисинен ашып кетет. Эстутум чектелүү орнотулган системалар үчүн, өткөрүү жөндөмдүүлүгү төмөн болгонуна карабастан, musl'дун жөнөкөй ыкмасы артыкчылыктуу болушу мүмкүн. Көпчүлүк жалпы максаттагы рабочий жана сервердик колдонмолор үчүн ptmalloc2'нин 72 КБ баштапкы чыгымы жөндөөсүз жакшы иштеген акылга сыярлык демейки маанини билдирет.

Баштапкы бөлүштүрүү жүрүм-турумун тууралоо

Эгер демейки 72 КБ баштапкы кошумча чыгым сиздин колдонуу шартыңыз үчүн чындап эле көйгөйлүү болсо — балким, сиз миңдеген кыска мөөнөттүү процесстерди жаратып жатасыз, алардын ар бири бир ууч гана бөлүштүрөт — glibc mallopt() жана MALLOC_ чөйрө өзгөрмөлөрүнүн үй-бүлөсү

аркылуу бир нече жөндөөлөрдү камсыз кылат.

M_TOP_PAD параметри бөлүштүргүч дароо талап кылынгандан ашыкча канча кошумча эстутумду сураарын көзөмөлдөйт. Аны mallopt(M_TOP_PAD, 0) менен 0 коюу, бөлүштүрүүчүгө керектүү нерсени гана суроону айтып, баштапкы ашыкча чыгымды кыйла азайтат. M_MMAP_THRESHOLD параметри жогорудагы бөлүштүрүү аренанын ордуна mmap колдонула турган өлчөмүн көзөмөлдөйт. M_TRIM_THRESHOLD бошогон эстутум OSке кайтарылганда көзөмөлдөйт. Жана glibc 2.26 болгондуктан, glibc.malloc.tcache_count жана glibc.malloc.tcache_max жөндөөлөрү жип кэшинин жүрүшүн көзөмөлдөөгө мүмкүндүк берет.

Бирок, эскертүү: кылдат салыштыруусуз бул параметрлерди жөндөө дээрлик дайыма абалды начарлатат. Демейки параметрлер кеңири реалдуу дүйнө профилинин негизинде тандалган жана алар жумуш жүктөмүнүн басымдуу бөлүгү үчүн жагымдуу жерди билдирет. Өндүрүштүк профилдештирүү боюнча сизде malloc ашыкча чыгымы кыйынчылык экенине күчтүү далилдер болбосо жана сиз өзгөртүүлөрүңүздүн таасирин өлчөгөн болсоңуз, демейки параметрлерди жөн калтырыңыз. Бөлүштүргүчтү мөөнөтүнөн мурда оптималдаштыруу топозду кырып алуунун өзгөчө тымызын түрү болуп саналат, анын пайдасы үчүн сансыз инженердик сааттар сарпталган.

Бул бизге системалык программалоо жөнүндө эмнени үйрөтөт

72 КБ биринчи бөлүштүрүү сыры, өзөктө абстракция катмарлары жөнүндө сабак. C++ сизге new int 4 байт бөлөт деген элес берет. Тил стандарты ушундай дейт. Сиздин психикалык моделиңиз ушундай дейт. Бирок кодуңуз менен аппараттык жабдыктын ортосунда татаал системалардын стеки отурат — C++ иштөө убактысы, C китепканасынын бөлүштүргүчү, ядронун виртуалдык эс тутуму жана аппараттык камсыздоонун MMU жана TLB — ар бири өзүнүн жүрүм-турумун, оптималдаштыруусун жана кошумча чыгымдарын кошот.

Бул кемчилик эмес. Бул системалык программалык камсыздоонун бардык пункту. Ар бир катмар чыныгы көйгөйдү чечүү үчүн бар: бөлүштүргүч бар, андыктан ар бир бөлүштүрүү үчүн системалык чалууларды жасоонун кажети жок. Виртуалдык эс тутуму бар, андыктан физикалык эстутумду түздөн-түз башкаруунун кереги жок. Беттин катасын иштетүүчү бар, ошондуктан эс жалкоо жана эффективдүү иштейт. Ар бир катмар чоң көлөмдөгү аткаруу жана ыңгайлуулук үчүн аз өлчөмдөгү ачык-айкындуулукту сатат.

Эң ишенимдүү, эң жогорку эффективдүү системаларды курган иштеп чыгуучулар бул катмарларды түшүнгөндөр - алар алар жөнүндө дайыма ойлонушу керек болгондуктан эмес, күтүлбөгөн нерсе болгондо (сырдуу 72 КБ бөлүштүрүү сыяктуу) эмне үчүн экенин түшүнө турган психикалык моделге ээ. Сиз реалдуу убакыт режиминде соода тутумун, оюн кыймылдаткычын же миңдеген колдонуучуларды тейлеген бизнес платформасын куруп жатасызбы, сиздин кодуңуз система деңгээлинде иш жүзүндө эмне кылары жөнүндө ой жүгүртүү жөндөмү компетенттүү иштеп чыгуучуларды өзгөчөлөрдөн айырмалап турат. 72 КБ ката эмес. Бул бөлүштүргүч өз ишин мыкты аткарып жатат.

Бүгүнкү күндө бизнесиңизди түзүңүз

Фрилансерлерден агенттиктерге чейин, Mewayz 207 интеграцияланган модулу менен 138,000+ бизнеске ыйгарым укуктарды берет. Акысыз баштаңыз, чоңойгондо жаңыртыңыз.

Акысыз каттоо эсебин түзүү →