C ++ 11 įdiegė standartizuotą atminties modelį. Ką tai reiškia? Ir kaip tai paveiks C ++ programavimą?

C ++ 11 pristatė standartizuotą atminties modelį, bet ką tai reiškia? Ir kaip tai paveiks C ++ programavimą?

Šis straipsnis ( Gavinas Klarkas cituoja Herb Sutter ) tai sako

Atminties modelis reiškia, kad C + + kodas dabar turi standartizuotą biblioteką skambinti, nepriklausomai nuo to, kas sukūrė kompiliatorių ir kurioje platformoje jis veikia. Yra standartinis būdas kontroliuoti, kaip skirtingi siūlai kalba apie procesoriaus atmintį.

„Kai kalbate apie [kodo] atskyrimą į skirtingus standarte esančius branduolius, kalbame apie atminties modelį. Mes jį optimizuosime, nepažeidžiant šių prielaidų, kurias žmonės ketina padaryti kode, - sakė Satter .

Na, aš galiu prisiminti šį ir panašius punktus, kurie yra prieinami internete (nuo pat gimimo momento turėjau savo atminties modelį: P), ir aš galiu net parašyti atsakymą į kitų pateiktus klausimus, bet sąžiningai, aš ne visai Aš tai suprantu.

„C ++“ programuotojai, norėdami sukurti daugiaspalves programas, dar anksčiau buvo sukurtos, taigi, kaip svarbu tai, ar ji yra „POSIX“ siūlai, ar „Windows“ siūlai arba „C ++ 11“ siūlai? Kokia nauda? Noriu suprasti žemo lygio detales.

Taip pat manau, kad C ++ 11 atminties modelis yra kažkaip susijęs su daugialypiu C ++ 11 palaikymu, nes aš dažnai matau šiuos du kartu. Jei taip, kaip? Kodėl jie turėtų būti susiję?

Kadangi nežinau, kaip dirbti su keliais siūlais ir kokiu atminties modeliu, kaip visuma, padėkite man suprasti šias sąvokas. :-)

1625 m
12 июня '11 в 2:30 2011-06-12 02:30 „Nawaz“ yra birželio 12 d. 11 val
@ 6 atsakymai

Pirma, jūs turite išmokti galvoti kaip teisininkas pagal kalbą.

„C ++“ specifikacija nėra susijusi su jokiu konkrečiu kompilatoriumi, operacine sistema ar procesoriumi. Jis nurodo abstrakčią mašiną, kuri yra realių sistemų apibendrinimas. Advokatų pasaulyje programuotojo užduotis yra parašyti abstrakčios mašinos kodą; kompiliatoriaus užduotis yra įgyvendinti šį kodą konkrečioje mašinoje. Jei griežtai koduojate specifikaciją, galite būti tikri, kad jūsų kodas bus sukompiliuotas ir nekeičiamas bet kurioje sistemoje su suderinamu „C ++“ kompilatoriumi, nesvarbu, ar tai būtų šiandien ar po 50 metų.

Anotacija C ++ 98 / C ++ 03 specifikacijoje iš esmės yra viena sriegė. Taigi neįmanoma rašyti daugiapjovinio C ++ kodo, kuris yra visiškai perduotas pagal specifikaciją. Specifikacija net nesako nieko apie atminties apkrovų atomiškumą ir duomenų įkėlimo bei saugojimo tvarką, nekalbant apie tokius dalykus kaip „mutexes“.

Žinoma, praktiškai galite rašyti daugiapakopį kodą konkrečioms sistemoms, pvz., Pthreads arba Windows. Tačiau nėra standartinio būdo, kaip parašyti daugiapakopį kodą C ++ 98 / C ++ 03.

Anotacija mašina C ++ 11 kelių sriegių dizainas. Ji taip pat turi gerai apibrėžtą atminties modelį; tai reiškia, kad kompiliatorius gali ir negali daryti, kai kalbama apie prieigą prie atminties.

Apsvarstykite toliau pateiktą pavyzdį, kuriame du pasauliniai kintamieji vienu metu pasiekiami dviem gijų:

  Global int x, y; Thread 1 Thread 2 x = 17; cout << y << " "; y = 37; cout << x << endl; 

Ką gali išskirti 2 siūlai?

C ++ 98 / C ++ 03 atveju tai nėra netgi neribotas elgesys; pats klausimas yra beprasmis, nes standartas nemano, kad tai vadinama „gija“.

C ++ 11 rezultatas yra neapibrėžtas elgesys, nes kroviniai ir parduotuvės neturi būti atominės. Tai neatrodo labai geras pagerėjimas ... Ir pats savaime tai nėra.

Bet su C ++ 11 galite rašyti:

  Global atomic<int> x, y; Thread 1 Thread 2 x.store(17); cout << y.load() << " "; y.store(37); cout << x.load() << endl; 

Dabar viskas tampa daug įdomesnė. Visų pirma čia apibūdinamas elgesys. Dabar 2 sriegis gali atspausdinti 0 0 (jei jis veikia prieš 1 sriegį), 37 17 (jei jis veikia po 1 temos) arba 0 17 (jei jis prasideda po 1 sriegio priskyrimo x, bet prieš jį priskiriant y ).

Tai, ką ji negali spausdinti, yra 37 0 nes numatytasis atominių apkrovų / saugojimo režimas C ++ 11 yra užtikrinti nuoseklų nuoseklumą. Tai reiškia, kad visos apkrovos ir sandėliai turėtų būti „tarsi“, jie įvyko tokia tvarka, kuria juos įrašėte į kiekvieną srautą, o operacijos tarp srautų gali keistis, tačiau sistema yra maloni. Taigi, numatytasis Atomics elgesys užtikrina tiek atomiškumą, tiek pakrovimo ir saugojimo tvarką.

Dabar, moderniame procesoriuje, nuoseklumo užtikrinimas gali būti brangus. Konkrečiai kalbant, kompiliatorius tikriausiai išskiria visapusiškas atminties kliūtis tarp kiekvienos prieigos čia. Bet jei jūsų algoritmas gali toleruoti nevaldomas apkrovas ir parduotuves; t.y. jei tam reikia atomiškumo, bet neužsakoma; t.y. jei jis gali išeiti iš 37 0 išeinant iš šios programos, galite rašyti:

  Global atomic<int> x, y; Thread 1 Thread 2 x.store(17,memory_order_relaxed); cout << y.load(memory_order_relaxed) << " "; y.store(37,memory_order_relaxed); cout << x.load(memory_order_relaxed) << endl; 

Kuo šiuolaikiškesnis procesorius, tuo labiau tikėtina, kad jis bus greitesnis už ankstesnį pavyzdį.

Galiausiai, jei jums reikia išsaugoti tam tikras apkrovas ir saugyklas, galite rašyti:

  Global atomic<int> x, y; Thread 1 Thread 2 x.store(17,memory_order_release); cout << y.load(memory_order_acquire) << " "; y.store(37,memory_order_release); cout << x.load(memory_order_acquire) << endl; 

Tai atneša mums atgal į tvarkingą apkrovą ir sandėlius, taigi 37 0 nebėra galimas išėjimas, bet tai daro su minimaliomis pridėtinėmis sąnaudomis. (Šiame trivialiame pavyzdyje rezultatas yra toks pat, kaip ir nuoseklus nuoseklus nuoseklumas; didesnėje programoje tai neįvyks).

Žinoma, jei tik norimi matmenys, 0 0 arba 37 17 , galite tiesiog įvesti mutex aplink šaltinio kodą. Bet jei jūs jį perskaitėte, esu tikras, kad jau žinote, kaip jis veikia, ir šis atsakymas yra ilgesnis, nei maniau :-).

Taigi apačioje. Mutexes yra puikios, o C ++ 11 jas standartizuoja. Tačiau kartais, dėl veikimo priežasčių, jums reikia žemesnio lygio primityvų (pvz., Klasikinis modelis su dvigubo užrakto tikrinimu ). Naujasis standartas suteikia aukšto lygio įtaisus, pvz., „Mutexes“ ir „state“ kintamuosius, taip pat teikia žemo lygio įtaisus, pvz., Atomų tipus ir įvairias atminties apsaugos parinktis. Taigi, dabar galite rašyti sudėtingas aukštos kokybės lygiagrečias rutinas tik standarte nurodyta kalba, ir jūs galite būti tikri, kad jūsų kodas sukurs ir veiks be pokyčių tiek šiandien, tiek rytoj.

Nors, atvirai kalbant, jei nesate ekspertas ir dirbate su tam tikru sunkiu žemo lygio kodu, tikriausiai turėtumėte laikytis „mutexes“ ir kintančių sąlygų. Štai ką ketinu daryti.

Daugiau informacijos rasite šiame dienoraščio įraše .

1877 m
12 июня '11 в 3:23 2011-06-12 03:23 atsakymą pateikė „ Nemo “ birželio 12 d. 11 val. 03:23 2011-06-12 03:23

Aš suteiksiu analogiją, su kuria suprantu atminties nuoseklumo modelį (arba trumpą laiką). Jis įkvėptas Lesley Lamport semantinio popieriaus „Laikas, valandos ir įvykių tvarka paskirstytoje sistemoje“. Analogija yra svarbi ir labai svarbi, tačiau daugeliui žmonių gali būti nereikalinga. Tačiau tikiuosi, kad tai suteiks psichinį vaizdą (grafinį vaizdą), todėl lengviau suprasti atminties nuoseklumo modelius.

Leidžia peržiūrėti visų atminties vietų istoriją erdvės laiko diagramoje, kurioje horizontali ašis rodo adreso erdvę (t. Y. Kiekviena atminties ląstelė yra nurodoma taške šioje ašyje), o vertikali ašis - laikas (pamatysime, kad apskritai , nėra visuotinės laiko sampratos). Taigi kiekvienoje atminties ląstelėje saugomų vertybių istoriją vaizduoja vertikali stulpelis šiame atminties adresu. Kiekvienas vertės pokytis atsiranda dėl to, kad viena iš sriegių šiai vietai rašo naują vertę. Pagal atminties vaizdą mes suprasime visų atminties vietų, kurios stebimos tam tikru laiku , vertybių visumą / derinį, naudojant konkretų srautą .

Citata iš „Suderinamumo ir talpyklos nuoseklumo įkūrėjo“

Intuityvus (ir labiausiai ribojantis) atminties modelis yra nuoseklus nuoseklumas (SC), kuriame kelių sriegių vykdymas turėtų atrodyti kaip kiekvienos sudėtinės siūlės nuoseklių vykdymų pakaitomis, tarsi siūlai buvo daugkartiniai su vieno branduolio procesoriumi.

Ši pasaulinė atminties tvarka gali skirtis priklausomai nuo programos ir gali būti nežinoma iš anksto. Tipiškas SC bruožas yra horizontalaus pjūvio rinkinys adresų erdvės-laiko diagramoje, vaizduojančioje lygiagrečiosios plokštumos (t. Y. Atvaizdus atmintyje). Šioje plokštumoje visi jos įvykiai (arba atminties reikšmės) yra vienalaikiai. Yra absoliutaus laiko sąvoka, kurioje visi siūlai sutampa su tuo, kurios atminties vertės yra vienalaikės. SC vienu metu yra tik vienas atminties vaizdas, bendras visoms temoms. Tai reiškia, kad kiekvienu momentu visi procesoriai atitinka atminties vaizdą (t. Y. Sukauptą atminties turinį). Tai reiškia ne tik tai, kad visi siūlai mato tą pačią reikšmių seką visoms atminties vietoms, bet ir tai, kad visi procesoriai atlieka tuos pačius vertybių derinius visiems kintamiesiems. Tai tas pats, kaip sakydamas, kad visos atminties operacijos (visose atminties ląstelėse) yra stebimos tuo pačiu visišku pavidalu visomis temomis.

Modeliuose su susilpnėjusiomis atmintimis kiekvienas sriegis atskirs adresų erdvės laiką savaip, vienintelis apribojimas yra tas, kad kiekvieno srauto gabalai nesusiliečia vienas su kitu, nes visi siūlai turi atitikti kiekvienos atskiros atminties >kai žiūrima į bet kurį srautą . Kiekviena gija turės kitokią idėją apie tai, kurie įvykiai (arba lygiaverčiai atminties reikšmės) yra vienalaikiai. Įvairių įvykių (arba atminties vertybių), kurios tuo pačiu metu susijusios su vienu srautu, rinkinys nėra vienalaikis su kitais. Taigi, susilpnintame atminties modelyje, visose temose vis dar yra ta pati istorija (t. Y. Reikšmių seka) kiekvienai atminties vietai. Tačiau jie gali stebėti skirtingus atminties vaizdus (t. Y. Visų atminties vietų vertybių derinius). Net jei dvi skirtingos atminties vietos yra įrašytos toje pačioje srovėje seka, dvi naujai užregistruotos vertės gali būti stebimos kitoje eilėje pagal kitus srautus.

[Vikipedijos iliustracija]

Skaitytojai, susipažinę su „Einsteins Special Relativity“ , pastebės, apie ką kalbu. Minkowskio žodžių vertimas į atminties modelių lauką: adresų erdvė ir laikas yra adreso erdvės šešėliai. Tokiu atveju kiekvienas stebėtojas (t.y. „Flow“) projektuos įvykių šešėlius (t. Y. Atmintį / apkrovas) į savo pačios pasaulio liniją (t. Y. Jo laiko ašį) ir savo simultancijos plokštumą (jo adreso lauko ašį) ) C ++ 11 atminties modelio temos atitinka stebėtojus, kurie vieni su kitais pereina specialioje reliatyvumo teorijoje. Nuoseklus nuoseklumas atitinka Galilijos erdvės laiką (t. Y. Visi stebėtojai susitaria dėl vienos absoliučios įvykių eilės ir pasaulinio lygiavertiškumo prasmės).

border=0

Atminties modelių ir specialaus reliatyvumo teorijos panašumas kyla iš to, kad abu apibrėžia dalinai užsakytus įvykių rinkinius, dažnai vadinamus priežastiniu rinkiniu. Kai kurie įvykiai (t. Y. „Saugojimas“) gali turėti įtakos kitiems įvykiams (bet jų neturi). C + + 11 srautas (arba stebėtojas fizikoje) yra ne tik įvykių grandinė (tai yra visiškai užsakytas rinkinys) (pvz., Atmintis įkelia ir saugo galimus skirtingus adresus).

Reliatyvumo teorijoje tam tikra tvarka atkuriama tariamai chaotiška iš dalies užsakytų įvykių nuotrauka, nes vienintelis laikas, kurį visi stebėtojai sutinka, yra užsakymas tarp „laikinų įvykių“ (t. Y. Tų įvykių, kurie iš esmės gali būti susiję su dalelėmis) lėtesnis nei šviesos greitis vakuume). Tik laikui bėgant įvykiai yra užsakomi iš eilės. Laikas fizikoje, Craig Callender .

C ++ 11 atminties modelyje šie lokalūs priežastiniai ryšiai nustatomi panašiu mechanizmu (nuoseklumo išleidimo-išleidimo modeliu).

Kad būtų užtikrintas atminties sekos nustatymas ir atsisakymo motyvacija, iš „ Primer“ duosiu atminties nuoseklumą ir talpyklos nuoseklumą

Bendrosios atminties kompiuterio atminties nuoseklumo modelis nustato architektūriniu požiūriu matomą jo atminties sistemos elgesį. Vieno procesoriaus šerdies teisingumo kriterijus paskirsto elgesį tarp „vieno teisingo rezultato“ ir „daug netaisyklingų alternatyvų“. Taip yra dėl to, kad procesoriaus architektūra numato, kad siūlų vykdymas konvertuoja nurodytą įvesties būseną į vieną tiksliai apibrėžtą išėjimo būseną, net ir iš pagrindinio. Tačiau nuoseklumo modeliai su bendrąja atmintimi susiję su kelių sriegių apkrovomis ir saugyklomis ir paprastai leidžia daugybę teisingų vykdymų, vengiant daugelio (daugiau) neteisingų. Daugelio teisingų vykdymų galimybė atsiranda dėl to, kad ISA leidžia vienu metu vykdyti kelis gijas, dažnai turint daugybę galimų teisėtų perėmimų iš skirtingų sričių.

Atsipalaidavę arba silpni atminties nuoseklumo modeliai yra motyvuoti tuo, kad nereikia daugumos atminties užsakymų stipriuose modeliuose. Jei srautas atnaujina dešimt duomenų elementų, o vėliau sinchronizavimo vėliava, programuotojams paprastai nėra svarbu, ar duomenų elementai yra atnaujinami vienas kito atžvilgiu, o tik visi duomenų elementai yra atnaujinami prieš vėliavos atnaujinimą (paprastai jie įgyvendinami naudojant „FENCE“ instrukciją). Atsipalaidavę modeliai linkę užfiksuoti šį padidintą užsakymų lankstumą ir išsaugoti tik užsakymus, kuriuos programuotojai „reikalauja“, kad gautų geresnį našumą ir SC tikslumą. Pavyzdžiui, kai kuriose architektūrose kiekvienas branduolys naudoja FIFO rašymo buferius fiksuotų (nuotolinių) saugyklų rezultatų išsaugojimui prieš rašydamas rezultatus į talpyklas. Šis optimizavimas pagerina našumą, bet pažeidžia SC. Rašymo buferis slepia atidėjimą saugykloms. Kadangi parduotuvės yra įprastos, daugelio jų sustabdymas yra svarbus privalumas. Vieno branduolio procesoriui rašymo buferis gali būti architektūriniu požiūriu nematomas, užtikrinant, kad įterpiant adresą A grąžinama naujausia saugykla į A, net jei viena arba daugiau A saugyklos yra rašymo buferyje. Paprastai tai daroma apeinant paskutinio saugojimo A vertę į apkrovą iš A, kur „paskutinis“ yra nustatomas pagal programos eiliškumą arba sustabdant apkrovą A, jei saugykla A yra rašymo buferyje. . Be rašymo buferių, aparatūra yra SC, tačiau su rašymo buferiais tai nėra, todėl rašymo buferiai architektūriniu būdu matomi kelių branduolių procesoriuje.

Sandėlio kaupimo pertvarkymas gali įvykti, jei branduolys turi rašymo buferį, išskyrus FIFO, kuris leidžia parduotuvėms palikti kitokia tvarka nei ta, kurioje jie buvo įvesti. Tai gali atsitikti, jei pirmoji parduotuvė praleidžia talpyklą, o antroji - arba jei antroji parduotuvė gali sujungti su ankstesne parduotuve (ty prieš pirmą parduotuvę). Apkrovos pertvarkymas taip pat gali vykti dinamiškai suplanuotuose branduoliuose, kurie vykdo programos nurodymus. Tai gali elgtis taip pat, kaip pertvarkyti parduotuves į kitą branduolį (ar galite galvoti apie dviejų srautų keitimo pavyzdį?). Pakeitus ankstyvą pakrovimą, po to saugant (pakeičiant saugojimo apkrovą), gali įvykti daug neteisingų veiksmų, pvz., Užpildyti vertę po to, kai atleidžiamas užraktas, kuris apsaugo jį (jei saugykla yra atrakinimo operacija). Atkreipkite dėmesį, kad pertvarkymas į saugyklą taip pat gali atsirasti dėl vietinio nuskaitymo bendrai įgyvendinamame FIFO rašymo buferyje, net su branduoliu, kuris vykdo visas komandas pagal programos vykdymo tvarką.

Kadangi kartkartės nuoseklumas ir atminties nuoseklumas kartais painiojami, taip pat pamokoma turėti šią citatą:

Skirtingai nuo nuoseklumo, talpyklos suderinamumas nėra rodomas nei programinėje įrangoje, nei užklausoje. Suderinamumo tikslas - užtikrinti, kad bendrosios atminties sistemos talpyklos būtų funkcionaliai nematomos kaip talpyklos vieno branduolio sistemoje. Правильная согласованность гарантирует, что программист не может определить, имеет ли и где система кэширует, анализируя результаты нагрузок и хранилищ. Это связано с тем, что правильная когерентность гарантирует, что кэши никогда не будут включать новое или другое поведение функционировать (программисты могут все еще иметь возможность вывести вероятную структуру кэша, используя информацию время ). Основная цель протоколов когерентности кеша - поддерживать инвариант одиночного писателя-множественного считывателя (SWMR) для каждой ячейки памяти. Важным различием между согласованностью и согласованностью является то, что согласованность указана в на основе расположения памяти , тогда как согласованность указана в отношении местоположений памяти all .