Intervistë e shkëlqyer me Cliff Click, babai i përpilimit JIT në Java

Intervistë e shkëlqyer me Cliff Click, babai i përpilimit JIT në JavaKlikoni në shkëmb — CTO i Cratus (sensorë IoT për përmirësimin e procesit), themelues dhe bashkëthemelues i disa startup-eve (përfshirë Rocket Realtime School, Neurensic dhe H2O.ai) me disa dalje të suksesshme. Cliff shkroi përpiluesin e tij të parë në moshën 15 vjeçare (Pascal për TRS Z-80)! Ai është më i njohur për punën e tij në C2 në Java (Deti i Nyjeve IR). Ky përpilues i tregoi botës se JIT mund të prodhonte kod me cilësi të lartë, i cili ishte një nga faktorët në shfaqjen e Java si një nga platformat kryesore moderne të softuerit. Pastaj Cliff ndihmoi Azul Systems të ndërtonte një mainframe me 864 bërthama me softuer të pastër Java që mbështeste pauzat e GC në një grumbull 500 gigabajt brenda 10 milisekondave. Në përgjithësi, Cliff arriti të punojë në të gjitha aspektet e JVM.

 
Ky habrapost është një intervistë e mrekullueshme me Cliff. Do të flasim për temat e mëposhtme:

  • Kalimi në optimizime të nivelit të ulët
  • Si të bëni një rifaktorim të madh
  • Modeli i kostos
  • Trajnim optimizimi i nivelit të ulët
  • Shembuj praktikë të përmirësimit të performancës
  • Pse të krijoni gjuhën tuaj të programimit
  • Karriera e inxhinierit të performancës
  • Sfidat teknike
  • Pak për shpërndarjen e regjistrave dhe shumë bërthama
  • Sfida më e madhe në jetë

Intervistat zhvillohen nga:

  • Andrey Satarin nga Shërbimet e Uebit të Amazon. Në karrierën e tij, ai arriti të punojë në projekte krejtësisht të ndryshme: ai testoi bazën e të dhënave të shpërndarë NewSQL në Yandex, një sistem zbulimi cloud në Kaspersky Lab, një lojë me shumë lojtarë në Mail.ru dhe një shërbim për llogaritjen e çmimeve të këmbimit valutor në Deutsche Bank. I interesuar në testimin e sistemeve të backend dhe të shpërndarë në shkallë të gjerë.
  • Vladimir Sitnikov nga Netcracker. Dhjetë vjet punë në performancën dhe shkallëzueshmërinë e NetCracker OS, softuer i përdorur nga operatorët e telekomit për të automatizuar proceset e menaxhimit të pajisjeve të rrjetit dhe rrjetit. I interesuar për çështjet e performancës së Java dhe Oracle Database. Autor i më shumë se një duzinë përmirësimesh të performancës në drejtuesin zyrtar të PostgreSQL JDBC.

Kalimi në optimizime të nivelit të ulët

Andrew: Ju jeni një emër i madh në botën e përpilimit të JIT, Java dhe punës së performancës në përgjithësi, apo jo? 

Shkëmbi: Kështu është!

Andrew: Le të fillojmë me disa pyetje të përgjithshme në lidhje me punën e performancës. Çfarë mendoni për zgjedhjen midis optimizimeve të nivelit të lartë dhe të nivelit të ulët si puna në nivel CPU?

Shkëmbi: Po, gjithçka është e thjeshtë këtu. Kodi më i shpejtë është ai që nuk ekzekutohet kurrë. Prandaj, gjithmonë duhet të filloni nga një nivel i lartë, të punoni në algoritme. Një shënim më i mirë O do të mundë një shënim më të keq O nëse nuk ndërhyjnë disa konstante mjaft të mëdha. Gjërat e nivelit të ulët shkojnë të fundit. Në mënyrë tipike, nëse e keni optimizuar mjaft mirë pjesën tjetër të grumbullit tuaj dhe ka ende disa gjëra interesante, ky është një nivel i ulët. Por si të filloni nga një nivel i lartë? Si e dini se është bërë mjaft punë e nivelit të lartë? Epo... në asnjë mënyrë. Nuk ka receta të gatshme. Ju duhet të kuptoni problemin, të vendosni se çfarë do të bëni (për të mos ndërmarrë hapa të panevojshëm në të ardhmen) dhe më pas mund të zbuloni profilin, i cili mund të thotë diçka të dobishme. Në një moment, ju vetë e kuptoni se keni hequr qafe gjërat e panevojshme dhe është koha për të bërë një akordim të mirë të nivelit të ulët. Ky është padyshim një lloj arti i veçantë. Ka shumë njerëz që bëjnë gjëra të panevojshme, por lëvizin aq shpejt sa nuk kanë kohë të shqetësohen për produktivitetin. Por kjo është derisa të lind pyetja troç. Zakonisht në 99% të rasteve askush nuk kujdeset për atë që bëj unë, deri në momentin kur një gjë e rëndësishme vjen në rrugën kritike për të cilën askush nuk kujdeset. Dhe këtu të gjithë fillojnë të të bezdisin se "pse nuk funksionoi në mënyrë perfekte që në fillim". Në përgjithësi, ka gjithmonë diçka për të përmirësuar në performancë. Por 99% të rasteve nuk keni të dhëna kryesore! Ju thjesht po përpiqeni të bëni diçka të funksionojë dhe në këtë proces do të kuptoni se çfarë është e rëndësishme. Asnjëherë nuk mund ta dini paraprakisht se kjo pjesë duhet të jetë e përsosur, kështu që, në fakt, duhet të jeni perfekt në gjithçka. Por kjo është e pamundur dhe ju nuk e bëni atë. Gjithmonë ka shumë gjëra për të rregulluar - dhe kjo është krejtësisht normale.

Si të bëni një rifaktorim të madh

Andrew: Si punon për një shfaqje? Ky është një problem ndërsektorial. Për shembull, a ju është dashur ndonjëherë të punoni për problemet që lindin nga kryqëzimi i shumë funksionaliteteve ekzistuese?

Shkëmbi: Mundohem ta shmang. Nëse e di që performanca do të jetë një problem, mendoj për këtë përpara se të filloj të kodoj, veçanërisht me strukturat e të dhënave. Por shpesh ju i zbuloni të gjitha këto shumë më vonë. Dhe atëherë duhet të shkoni në masa ekstreme dhe të bëni atë që unë e quaj "rishkruaj dhe pushto": duhet të kapësh një pjesë mjaft të madhe. Një pjesë e kodit do të duhet ende të rishkruhet për shkak të problemeve të performancës ose diçka tjetër. Cilado qoftë arsyeja e rishkrimit të kodit, është pothuajse gjithmonë më mirë të rishkruhet një pjesë më e madhe sesa një pjesë më e vogël. Në këtë moment, të gjithë fillojnë të dridhen nga frika: "O Zot, nuk mund të prekësh kaq shumë kod!" Por në fakt, kjo qasje pothuajse gjithmonë funksionon shumë më mirë. Ju duhet të merrni menjëherë një problem të madh, të vizatoni një rreth të madh rreth tij dhe të thoni: Unë do të rishkruaj gjithçka brenda rrethit. Kufiri është shumë më i vogël se përmbajtja brenda tij që duhet të zëvendësohet. Dhe nëse një përcaktim i tillë i kufijve ju lejon të bëni punën brenda në mënyrë të përsosur, duart tuaja janë të lira, bëni atë që dëshironi. Pasi ta kuptoni problemin, procesi i rishkrimit është shumë më i lehtë, kështu që merrni një kafshatë të madhe!
Në të njëjtën kohë, kur bëni një rishkrim të madh dhe kuptoni se performanca do të jetë një problem, mund të filloni menjëherë të shqetësoheni për të. Kjo zakonisht kthehet në gjëra të thjeshta si "mos kopjoni të dhënat, menaxhoni të dhënat sa më thjesht që të jetë e mundur, bëjini të vogla". Në rishkrimet e mëdha, ka mënyra standarde për të përmirësuar performancën. Dhe ato pothuajse gjithmonë rrotullohen rreth të dhënave.

Modeli i kostos

Andrew: Në një nga podkastet ju folët për modelet e kostos në kontekstin e produktivitetit. A mund të shpjegoni se çfarë do të thoshit me këtë?

Shkëmbi: Sigurisht. Unë kam lindur në një epokë kur performanca e procesorit ishte jashtëzakonisht e rëndësishme. Dhe kjo epokë kthehet përsëri - fati nuk është pa ironi. Fillova të jetoj në kohën e makinerive tetë-bitësh; kompjuteri im i parë punonte me 256 bajt. Pikërisht bajt. Gjithçka ishte shumë e vogël. Udhëzimet duheshin numëruar dhe ndërsa filluam të ngjiteshim në grupin e gjuhëve të programimit, gjuhët morën gjithnjë e më shumë. Ishte Assembler, pastaj Basic, pastaj C, dhe C u kujdes për shumë detaje, si ndarja e regjistrave dhe përzgjedhja e udhëzimeve. Por gjithçka ishte mjaft e qartë atje, dhe nëse do të bëja një tregues për një shembull të një ndryshoreje, atëherë do të merrja ngarkesën dhe kostoja e këtij udhëzimi dihet. Pajisja prodhon një numër të caktuar ciklesh makinerie, kështu që shpejtësia e ekzekutimit të gjërave të ndryshme mund të llogaritet thjesht duke mbledhur të gjitha udhëzimet që do të ekzekutoni. Çdo krahasim/test/degë/thirrje/ngarkim/dyqan mund të shtohet dhe të thuhet: kjo është koha e ekzekutimit për ju. Kur punoni për përmirësimin e performancës, patjetër do t'i kushtoni vëmendje atyre numrave që korrespondojnë me cikle të vogla të nxehta. 
Por sapo kaloni në Java, Python dhe gjëra të ngjashme, shumë shpejt largoheni nga hardueri i nivelit të ulët. Sa është kostoja e thirrjes së një marrësi në Java? Nëse JIT në HotSpot është i saktë të rreshtuara, do të ngarkohet, por nëse nuk e ka bërë këtë, do të jetë një thirrje funksioni. Meqenëse thirrja është në një qark të nxehtë, ajo do të anashkalojë të gjitha optimizimet e tjera në atë cikli. Prandaj, kostoja reale do të jetë shumë më e lartë. Dhe ju humbni menjëherë aftësinë për të parë një pjesë të kodit dhe për të kuptuar se ne duhet ta ekzekutojmë atë për sa i përket shpejtësisë së orës së procesorit, kujtesës dhe cache-it të përdorur. E gjithë kjo bëhet interesante vetëm nëse vërtet futeni në performancë.
Tani e gjejmë veten në një situatë ku shpejtësia e procesorit pothuajse nuk është rritur për një dekadë. Kohët e vjetra janë kthyer! Nuk mund të mbështeteni më në performancën e mirë me një fije. Por nëse befas futeni në llogaritjen paralele, është tepër e vështirë, të gjithë ju shikojnë si James Bond. Përshpejtimet e dhjetëfishta këtu zakonisht ndodhin në vendet ku dikush ka ngatërruar diçka. Konkurrenca kërkon shumë punë. Për të marrë atë shpejtësi XNUMXx, duhet të kuptoni modelin e kostos. Çfarë dhe sa kushton? Dhe për ta bërë këtë, ju duhet të kuptoni se si gjuha përshtatet në pajisjen themelore.
Martin Thompson zgjodhi një fjalë të mrekullueshme për blogun e tij Simpatia mekanike! Ju duhet të kuptoni se çfarë do të bëjë pajisja, si do ta bëjë saktësisht dhe pse bën atë që bën në radhë të parë. Duke përdorur këtë, është mjaft e lehtë të filloni të numëroni udhëzimet dhe të kuptoni se ku po shkon koha e ekzekutimit. Nëse nuk keni trajnimin e duhur, thjesht po kërkoni një mace të zezë në një dhomë të errët. Unë shoh njerëz që optimizojnë performancën gjatë gjithë kohës, të cilët nuk e kanë idenë se çfarë dreqin po bëjnë. Ata vuajnë shumë dhe nuk po bëjnë shumë përparim. Dhe kur marr të njëjtin kod kodi, fut në disa hakime të vogla dhe marr një shpejtësi pesë ose dhjetëfish, ato janë si: mirë, kjo nuk është e drejtë, ne e dinim që ju ishit më mirë. E mahnitshme. Për çfarë po flas... modeli i kostos ka të bëjë me atë lloj kodi që shkruani dhe sa shpejt funksionon mesatarisht në pamjen e madhe.

Andrew: Dhe si mund të mbani një vëllim të tillë në kokën tuaj? A arrihet kjo me më shumë përvojë, apo? Nga vjen një përvojë e tillë?

Shkëmbi: Epo, nuk e mora përvojën time në mënyrën më të lehtë. Unë programova në Asamble në ditët kur mund të kuptonit çdo udhëzim të vetëm. Duket marrëzi, por që atëherë grupi i udhëzimeve Z80 ka mbetur gjithmonë në kokën time, në kujtesën time. Nuk i mbaj mend emrat e njerëzve brenda një minute pasi kam folur, por më kujtohet kodi i shkruar 40 vjet më parë. Është qesharake, duket si një sindromë "shkencëtar idiot'.

Trajnim optimizimi i nivelit të ulët

Andrew: A ka ndonjë mënyrë më të lehtë për të hyrë?

Shkëmbi: Po dhe jo. Pajisja që ne përdorim të gjithë nuk ka ndryshuar shumë me kalimin e kohës. Të gjithë përdorin x86, me përjashtim të telefonave inteligjentë Arm. Nëse nuk jeni duke bërë një lloj ngulitjeje të fortë, po bëni të njëjtën gjë. Mirë, tjetër. Udhëzimet gjithashtu nuk kanë ndryshuar me shekuj. Duhet të shkosh dhe të shkruash diçka në Kuvend. Jo shumë, por mjaftueshëm për të filluar të kuptojmë. Ju po buzëqeshni, por unë po flas plotësisht seriozisht. Ju duhet të kuptoni korrespondencën midis gjuhës dhe harduerit. Pas kësaj ju duhet të shkoni dhe të shkruani pak dhe të bëni një përpilues të vogël lodrash për një gjuhë të vogël lodrash. Si lodër do të thotë se duhet të bëhet në një kohë të arsyeshme. Mund të jetë shumë e thjeshtë, por duhet të gjenerojë udhëzime. Akti i gjenerimit të një udhëzimi do t'ju ndihmojë të kuptoni modelin e kostos për urën midis kodit të nivelit të lartë që shkruan të gjithë dhe kodit të makinës që funksionon në harduer. Kjo korrespodencë do të digjet në tru në kohën kur shkruhet përpiluesi. Edhe përpiluesi më i thjeshtë. Pas kësaj, mund të filloni të shikoni Java dhe faktin që humnera e saj semantike është shumë më e thellë, dhe është shumë më e vështirë të ndërtoni ura mbi të. Në Java, është shumë më e vështirë të kuptosh nëse ura jonë doli e mirë apo e keqe, çfarë do ta bëjë atë të shembet dhe çfarë jo. Por ju duhet një lloj pikënisjeje ku të shikoni kodin dhe të kuptoni: "Po, ky marrës duhet të futet çdo herë". Dhe pastaj rezulton se ndonjëherë kjo ndodh, me përjashtim të situatës kur metoda bëhet shumë e madhe dhe JIT fillon të rreshtojë gjithçka. Performanca e vendeve të tilla mund të parashikohet në çast. Zakonisht marrësit funksionojnë mirë, por më pas ju shikoni sythe të mëdha të nxehta dhe kuptoni se ka disa thirrje funksionesh që qarkullojnë përreth që nuk e dinë se çfarë po bëjnë. Ky është problemi i përdorimit të gjerë të marrësve, arsyeja pse ato nuk janë të rreshtuara është se nuk është e qartë nëse janë marrës. Nëse keni një bazë kodi super të vogël, thjesht mund ta mbani mend atë dhe më pas të thoni: ky është një marrës dhe ky është një vendosës. Në një bazë të madhe kodi, çdo funksion jeton historinë e vet, e cila, në përgjithësi, nuk është e njohur për askënd. Profiluesi thotë se ne kemi humbur 24% të kohës në disa lak dhe për të kuptuar se çfarë po bën ky lak, duhet të shikojmë çdo funksion brenda. Është e pamundur ta kuptosh këtë pa studiuar funksionin dhe kjo ngadalëson seriozisht procesin e të kuptuarit. Kjo është arsyeja pse unë nuk përdor getters dhe setters, kam arritur një nivel të ri!
Ku të merrni modelin e kostos? Epo, sigurisht që mund të lexosh diçka... Por unë mendoj se mënyra më e mirë është të veprosh. Bërja e një përpiluesi të vogël do të jetë mënyra më e mirë për të kuptuar modelin e kostos dhe për ta përshtatur atë në kokën tuaj. Një përpilues i vogël që do të ishte i përshtatshëm për programimin e një mikrovalë është një detyrë për një fillestar. Epo, dua të them, nëse tashmë keni aftësi programimi, atëherë kjo duhet të jetë e mjaftueshme. Të gjitha këto gjëra si analizimi i një vargu që keni si një lloj shprehjeje algjebrike, nxjerrja e udhëzimeve për veprime matematikore prej andej në rendin e duhur, marrja e vlerave të sakta nga regjistrat - e gjithë kjo bëhet menjëherë. Dhe ndërsa ju e bëni atë, ajo do të jetë e ngulitur në trurin tuaj. Unë mendoj se të gjithë e dinë se çfarë bën një përpilues. Dhe kjo do të japë një kuptim të modelit të kostos.

Shembuj praktikë të përmirësimit të performancës

Andrew: Çfarë tjetër duhet t'i kushtoni vëmendje kur punoni për produktivitetin?

Shkëmbi: Strukturat e të dhënave. Nga rruga, po, unë nuk i kam dhënë këto klasa për një kohë të gjatë ... Shkolla e raketave. Ishte argëtuese, por kërkonte shumë përpjekje, dhe unë kam edhe një jetë! NE RREGULL. Kështu, në një nga klasat e mëdha dhe interesante, "Ku shkon performanca juaj", u dhashë studentëve një shembull: dy gigabajt e gjysmë të dhëna fintech u lexuan nga një skedar CSV dhe më pas u duhej të llogarisnin numrin e produkteve të shitura. . Të dhënat e rregullta të tregut. Paketat UDP u konvertuan në format teksti që nga vitet '70. Bursa Tregtare e Çikagos - të gjitha llojet e gjërave si gjalpi, misri, soja, gjëra të tilla. Ishte e nevojshme të numëroheshin këto produkte, numri i transaksioneve, vëllimi mesatar i lëvizjes së fondeve dhe mallrave, etj. Është një matematikë shumë e thjeshtë e tregtimit: gjeni kodin e produktit (që është 1-2 karaktere në tabelën hash), merrni shumën, shtojeni atë në një nga grupet e tregtisë, shtoni vëllim, shtoni vlerë dhe disa gjëra të tjera. Matematikë shumë e thjeshtë. Zbatimi i lodrës ishte shumë i drejtpërdrejtë: gjithçka është në një skedar, lexoj skedarin dhe kaloj nëpër të, duke i ndarë regjistrimet individuale në vargje Java, duke kërkuar gjërat e nevojshme në to dhe duke i mbledhur ato sipas matematikës së përshkruar më sipër. Dhe funksionon me një shpejtësi të ulët.

Me këtë qasje, është e qartë se çfarë po ndodh, dhe llogaritja paralele nuk do të ndihmojë, apo jo? Rezulton se një rritje pesëfish në performancë mund të arrihet thjesht duke zgjedhur strukturat e duhura të të dhënave. Dhe kjo befason edhe programuesit me përvojë! Në rastin tim të veçantë, mashtrimi ishte se nuk duhet të bëni ndarje të memories në një lak të nxehtë. Epo, kjo nuk është e gjithë e vërteta, por në përgjithësi - nuk duhet të theksoni "një herë në X" kur X është mjaft i madh. Kur X është dy gigabajt e gjysmë, nuk duhet të ndani asgjë "një herë për shkronjë", ose "një herë në rresht", ose "një herë në fushë", diçka të tillë. Këtu harxhohet koha. Si funksionon kjo edhe? Imagjinoni të bëj një telefonatë String.split() ose BufferedReader.readLine(). Readline bën një varg nga një grup bajtësh që erdhën në rrjet, një herë për çdo rresht, për secilën prej qindra miliona rreshtave. E marr këtë rresht, e analizoj dhe e hedh tutje. Pse po e hedh - mirë, e kam përpunuar tashmë, kjo është e gjitha. Pra, për çdo bajt të lexuar nga këto 2.7G, dy karaktere do të shkruhen në rresht, domethënë tashmë 5.4G, dhe nuk më duhen për asgjë më tej, kështu që ato hidhen. Nëse shikoni gjerësinë e brezit të memories, ne ngarkojmë 2.7G që kalon përmes autobusit të memories dhe kujtesës në procesor, dhe më pas dërgohet dy herë më shumë në linjën e vendosur në memorie, dhe e gjithë kjo prishet kur krijohet çdo linjë e re. Por më duhet ta lexoj, pajisja e lexon, edhe nëse gjithçka prishet më vonë. Dhe më duhet ta shkruaj sepse kam krijuar një linjë dhe memoriet e fshehta janë plot - cache nuk mund të strehojë 2.7G. Pra, për çdo bajt që lexoj, lexoj dy bajt të tjerë dhe shkruaj dy bajtë të tjerë, dhe në fund ata kanë një raport 4:1 - në këtë raport po humbim gjerësinë e brezit të memories. Dhe pastaj rezulton se nëse e bëj String.split() – nuk është hera e fundit që e bëj këtë, mund të ketë edhe 6-7 fusha të tjera brenda. Pra, kodi klasik i leximit të CSV dhe më pas analizimit të vargjeve rezulton në një humbje të gjerësisë së brezit të kujtesës prej rreth 14:1 në krahasim me atë që do të dëshironit të kishit në të vërtetë. Nëse i hidhni këto zgjedhje, mund të merrni një shpejtësi pesëfish.

Dhe nuk është aq e vështirë. Nëse e shikoni kodin nga këndi i duhur, gjithçka bëhet mjaft e thjeshtë sapo të kuptoni problemin. Nuk duhet të ndaloni fare ndarjen e memories: problemi i vetëm është se alokoni diçka dhe ajo menjëherë vdes, dhe gjatë rrugës djeg një burim të rëndësishëm, që në këtë rast është gjerësia e brezit të kujtesës. Dhe e gjithë kjo rezulton në një rënie të produktivitetit. Në x86 zakonisht duhet të digjni në mënyrë aktive ciklet e procesorit, por këtu keni djegur të gjithë kujtesën shumë më herët. Zgjidhja është zvogëlimi i sasisë së shkarkimit. 
Pjesa tjetër e problemit është se nëse e përdorni profilin kur mbaron shiriti i memories, pikërisht kur ndodh, zakonisht jeni duke pritur që cache të kthehet sepse është plot me mbeturina që sapo keni prodhuar, të gjitha ato rreshta. Prandaj, çdo funksion i ngarkimit ose i ruajtjes bëhet i ngadalshëm, sepse ato çojnë në humbje të cache - e gjithë memoria është bërë e ngadaltë, duke pritur që mbeturinat të largohen prej saj. Prandaj, profiluesi thjesht do të tregojë zhurmë të ngrohtë të rastësishme të lyer në të gjithë lakin - nuk do të ketë asnjë udhëzim të veçantë të nxehtë ose vend në kod. Vetëm zhurmë. Dhe nëse shikoni ciklet GC, ato janë të gjitha të gjeneratës së re dhe super të shpejtë - mikrosekonda ose milisekonda maksimale. Në fund të fundit, e gjithë kjo kujtesë vdes menjëherë. Ju ndani miliarda gigabajt, dhe ai i shkurton ato, i shkurton dhe i shkurton përsëri. E gjithë kjo ndodh shumë shpejt. Rezulton se ka cikle të lira GC, zhurmë të ngrohtë gjatë gjithë ciklit, por ne duam të marrim një shpejtësi 5x. Në këtë moment, diçka duhet të mbyllet në kokën tuaj dhe të tingëllojë: "pse është kjo?" Rrjedha e shiritit të kujtesës nuk shfaqet në korrigjuesin klasik; duhet të ekzekutoni korrigjuesin e numëruesit të performancës së harduerit dhe ta shihni vetë dhe drejtpërdrejt. Por kjo nuk mund të dyshohet drejtpërdrejt nga këto tre simptoma. Simptoma e tretë është kur shikoni atë që theksoni, pyesni profilin dhe ai përgjigjet: "Ju bëtë një miliard rreshta, por GC funksionoi falas." Sapo të ndodhë kjo, ju e kuptoni se keni krijuar shumë objekte dhe keni djegur të gjithë korsinë e kujtesës. Ekziston një mënyrë për ta kuptuar këtë, por nuk është e qartë. 

Problemi është në strukturën e të dhënave: struktura e zhveshur që qëndron në themel të çdo gjëje që ndodh, është shumë e madhe, është 2.7G në disk, kështu që të bësh një kopje të kësaj gjëje është shumë e padëshirueshme - dëshiron ta ngarkosh menjëherë nga buferi i bajtit të rrjetit në regjistra, në mënyrë që të mos lexoni-shkruani në rresht pesë herë përpara dhe mbrapa. Fatkeqësisht, Java nuk ju jep një bibliotekë të tillë si pjesë e JDK si parazgjedhje. Por kjo është e parëndësishme, apo jo? Në thelb, këto janë 5-10 rreshta kodi që do të përdoren për të zbatuar ngarkuesin tuaj të vargut të buferuar, i cili përsërit sjelljen e klasës së vargut, ndërsa është një mbështjellës rreth buferit bazë të bajtit. Si rezultat, rezulton se ju jeni duke punuar pothuajse sikur me vargje, por në fakt treguesit drejt buferit po lëvizin atje, dhe bajtët e papërpunuar nuk kopjohen askund, dhe kështu të njëjtat bufera ripërdoren pa pushim, dhe sistemi operativ është i lumtur të marrë përsipër gjërat për të cilat është krijuar, si p.sh. buferimi i fshehur i dyfishtë i këtyre buferave të bajtit dhe ju nuk jeni më duke bluar nëpër një rrjedhë të pafundme të dhënash të panevojshme. Nga rruga, a e kuptoni se kur punoni me GC, është e garantuar që çdo ndarje e memories nuk do të jetë e dukshme për procesorin pas ciklit të fundit GC? Prandaj, e gjithë kjo nuk mund të jetë në cache, dhe më pas ndodh një humbje e garantuar 100%. Kur punoni me një tregues, në x86, zbritja e një regjistri nga memorja kërkon 1-2 cikle ore, dhe sapo të ndodhë kjo, ju paguani, paguani, paguani, sepse kujtesa është e gjitha aktive. NËNTË memorie – dhe kjo është kostoja e alokimit të memories. Vlera reale.

Me fjalë të tjera, strukturat e të dhënave janë gjëja më e vështirë për t'u ndryshuar. Dhe sapo të kuptoni se keni zgjedhur strukturën e gabuar të të dhënave që do të shkatërrojë performancën më vonë, zakonisht ka shumë punë për të bërë, por nëse nuk e bëni, gjërat do të përkeqësohen. Para së gjithash, duhet të mendoni për strukturat e të dhënave, kjo është e rëndësishme. Kostoja kryesore këtu bie mbi strukturat e të dhënave të majme, të cilat kanë filluar të përdoren në stilin e "Kam kopjuar strukturën e të dhënave X në strukturën e të dhënave Y sepse më pëlqen forma e Y". Por operacioni i kopjimit (i cili duket i lirë) në fakt harxhon gjerësinë e brezit të memories dhe këtu është varrosur e gjithë koha e humbur e ekzekutimit. Nëse kam një varg gjigant JSON dhe dua ta kthej atë në një pemë të strukturuar DOM të POJO-ve ose diçka tjetër, operacioni i analizimit të atij vargu dhe i ndërtimit të POJO-së, dhe më pas qasja në POJO përsëri më vonë, do të rezultojë në kosto të panevojshme - është jo i lirë. Përveç nëse vraponi rreth POJO-ve shumë më shpesh sesa vraponi rreth një vargu. Në të kundërt, mund të provoni të deshifroni vargun dhe të nxirrni vetëm atë që ju nevojitet prej andej, pa e kthyer atë në ndonjë POJO. Nëse e gjithë kjo ndodh në një rrugë nga e cila kërkohet performanca maksimale, pa POJO për ju, duhet të gërmoni disi direkt në linjë.

Pse të krijoni gjuhën tuaj të programimit

Andrew: Ju thatë që për të kuptuar modelin e kostos, duhet të shkruani gjuhën tuaj të vogël...

Shkëmbi: Jo një gjuhë, por një përpilues. Një gjuhë dhe një përpilues janë dy gjëra të ndryshme. Dallimi më i rëndësishëm është në kokën tuaj. 

Andrew: Meqë ra fjala, me sa di unë, ju po eksperimentoni me krijimin e gjuhëve tuaja. Per cfare?

Shkëmbi: Sepse mundem! Unë jam gjysmë pensionist, kështu që ky është hobi im. Unë kam zbatuar gjuhët e të tjerëve gjatë gjithë jetës sime. Kam punuar shumë edhe në stilin tim të kodimit. Dhe gjithashtu sepse shoh probleme në gjuhë të tjera. Unë shoh se ka mënyra më të mira për të bërë gjëra të njohura. Dhe unë do t'i përdorja ato. Jam lodhur duke parë probleme në veten time, në Java, në Python, në çdo gjuhë tjetër. Tani shkruaj në React Native, JavaScript dhe Elm si një hobi që nuk ka të bëjë me pensionin, por me punën aktive. Unë gjithashtu shkruaj në Python dhe, ka shumë të ngjarë, do të vazhdoj të punoj në mësimin e makinerive për backend-et Java. Ka shumë gjuhë të njohura dhe të gjitha ato kanë veçori interesante. Secili është i mirë në mënyrën e vet dhe mund të përpiqeni t'i bashkoni të gjitha këto veçori. Pra, po studioj gjëra që më interesojnë, sjelljen e gjuhës, duke u përpjekur të arrij me semantikë të arsyeshme. Dhe deri më tani ia kam dalë! Për momentin jam duke luftuar me semantikën e memories, sepse dua ta kem atë si në C dhe Java, dhe të marr një model të fortë memorie dhe semantikë memorie për ngarkesa dhe dyqane. Në të njëjtën kohë, keni konkluzion të tipit automatik si në Haskell. Këtu, po përpiqem të përziej konkluzionet e tipit Haskell me punën e memories si në C ashtu edhe në Java. Kjo është ajo që kam bërë për shembull 2-3 muajt e fundit.

Andrew: Nëse ndërtoni një gjuhë që merr aspekte më të mira nga gjuhët e tjera, a mendoni se dikush do të bëjë të kundërtën: të marrë idetë tuaja dhe t'i përdorë ato?

Shkëmbi: Pikërisht kështu shfaqen gjuhët e reja! Pse Java është e ngjashme me C? Sepse C kishte një sintaksë të mirë që të gjithë e kuptonin dhe Java u frymëzua nga kjo sintaksë, duke shtuar sigurinë e tipit, kontrollin e kufijve të grupeve, GC, dhe gjithashtu përmirësoi disa gjëra nga C. Ata shtuan të tyren. Por ata u frymëzuan shumë, apo jo? Të gjithë qëndrojnë mbi supet e gjigantëve që erdhën përpara jush - kështu bëhet përparim.

Andrew: Siç e kuptoj unë, gjuha juaj do të jetë e sigurt për kujtesën. A keni menduar të zbatoni diçka si një kontrollues huazimi nga Rust? E keni parë atë, çfarë mendoni për të?

Shkëmbi: Epo, unë kam shkruar C për shumë vite, me gjithë këtë malloc dhe falas, dhe menaxhoj manualisht jetën. E dini, 90-95% e jetës së kontrolluar me dorë ka të njëjtën strukturë. Dhe është shumë, shumë e dhimbshme ta bësh atë me dorë. Unë do të doja që përpiluesi thjesht t'ju tregojë se çfarë po ndodh atje dhe çfarë keni arritur me veprimet tuaja. Për disa gjëra, kontrolluesi i huasë e bën këtë jashtë kutisë. Dhe duhet të shfaqë automatikisht informacionin, të kuptojë gjithçka dhe as të mos më ngarkojë mua me paraqitjen e këtij kuptimi. Duhet të bëjë të paktën analiza lokale të arratisjes, dhe vetëm nëse dështon, atëherë duhet të shtojë shënime të tipit që do të përshkruajnë jetëgjatësinë - dhe një skemë e tillë është shumë më komplekse se një kontrollues huamarrjeje, ose në të vërtetë çdo kontrollues ekzistues i memories. Zgjedhja midis "gjithçka është mirë" dhe "Unë nuk kuptoj asgjë" - jo, duhet të ketë diçka më të mirë. 
Pra, si dikush që ka shkruar shumë kode në C, mendoj se të kesh mbështetje për kontrollin automatik të jetëgjatësisë është gjëja më e rëndësishme. Jam ngopur gjithashtu me sa shumë përdor Java memorie dhe ankesa kryesore është GC. Kur shpërndani memorie në Java, nuk do të riktheheni memorien që ishte lokale në ciklin e fundit GC. Ky nuk është rasti në gjuhët me menaxhim më të saktë të kujtesës. Nëse telefononi malloc, menjëherë merrni kujtesën që zakonisht sapo përdorej. Zakonisht ju bëni disa gjëra të përkohshme me kujtesën dhe e ktheni menjëherë. Dhe menjëherë kthehet në pishinën malloc, dhe cikli tjetër malloc e nxjerr atë përsëri. Prandaj, përdorimi aktual i kujtesës reduktohet në grupin e objekteve të gjalla në një kohë të caktuar, plus rrjedhjet. Dhe nëse gjithçka nuk rrjedh në një mënyrë krejtësisht të pahijshme, shumica e memories përfundon në cache dhe procesor, dhe funksionon shpejt. Por kërkon shumë menaxhim manual të kujtesës me malloc dhe pa pagesë të thirrur në rendin e duhur, në vendin e duhur. Rust mund ta trajtojë këtë si duhet vetë, dhe në shumë raste të japë performancë edhe më të mirë, pasi konsumi i memories ngushtohet vetëm në llogaritjen aktuale - në krahasim me pritjen për ciklin tjetër GC për të çliruar memorien. Si rezultat, ne morëm një mënyrë shumë interesante për të përmirësuar performancën. Dhe mjaft i fuqishëm - dua të them, kam bërë gjëra të tilla kur përpunoja të dhëna për fintech, dhe kjo më lejoi të përshpejtoja rreth pesë herë. Ky është një nxitje mjaft e madhe, veçanërisht në një botë ku procesorët nuk po bëhen më të shpejtë dhe ne jemi ende në pritje të përmirësimeve.

Karriera e inxhinierit të performancës

Andrew: Do të doja të pyesja edhe rreth karrierës në përgjithësi. Ju u bëtë i njohur me punën tuaj JIT në HotSpot dhe më pas u transferuat në Azul, e cila është gjithashtu një kompani JVM. Por ne tashmë po punonim më shumë në harduer sesa në softuer. Dhe më pas ata papritmas kaluan në Big Data dhe Machine Learning, dhe më pas në zbulimin e mashtrimit. Si ndodhi kjo? Këto janë fusha shumë të ndryshme të zhvillimit.

Shkëmbi: Unë jam duke programuar për një kohë të gjatë dhe kam arritur të marr shumë klasa të ndryshme. Dhe kur njerëzit thonë: "Oh, ti je ai që bëre JIT për Java!", është gjithmonë qesharake. Por para kësaj, unë isha duke punuar në një klon të PostScript - gjuhën që Apple përdorte dikur për printerët e saj lazer. Dhe më parë bëra një implementim të gjuhës së katërt. Mendoj se një temë e zakonshme për mua është zhvillimi i mjeteve. Gjatë gjithë jetës sime kam bërë vegla me të cilat njerëzit e tjerë shkruajnë programet e tyre të lezetshme. Por unë u përfshiva gjithashtu në zhvillimin e sistemeve operative, drejtuesve, korrigjuesve të nivelit të kernelit, gjuhëve për zhvillimin e OS, të cilat filluan të parëndësishme, por me kalimin e kohës u bënë gjithnjë e më komplekse. Por tema kryesore është ende zhvillimi i mjeteve. Një pjesë e madhe e jetës sime kaloi midis Azul dhe Sun, dhe ishte për Java. Por kur u futa në Big Data dhe Machine Learning, vendosa përsëri kapelën time të bukur dhe thashë: "Oh, tani kemi një problem jo të parëndësishëm dhe ka shumë gjëra interesante që po ndodhin dhe njerëzit bëjnë gjëra." Kjo është një rrugë e shkëlqyer zhvillimi për t'u ndjekur.

Po, më pëlqen vërtet kompjuteri i shpërndarë. Puna ime e parë ishte si student në C, në një projekt reklamimi. Kjo u shpërnda duke llogaritur në çipat Zilog Z80 që mblidhnin të dhëna për OCR analoge, të prodhuara nga një analizues i vërtetë analog. Ishte një temë e lezetshme dhe krejtësisht e çmendur. Por kishte probleme, një pjesë nuk u njoh saktë, kështu që duhej të nxirreje një fotografi dhe t'ia tregonte një personi që tashmë mund të lexonte me sytë e tij dhe të raportonte atë që thoshte, dhe për këtë arsye kishte punë me të dhëna, dhe këto punë kishin gjuhën e tyre. Kishte një backend që përpunonte të gjitha këto - Z80s që funksiononin paralelisht me terminalet vt100 që funksiononin - një për person, dhe kishte një model programimi paralel në Z80. Një pjesë e zakonshme e memories e ndarë nga të gjithë Z80-të brenda një konfigurimi ylli; Plani i pasmë u nda gjithashtu, dhe gjysma e RAM-it ndahej brenda rrjetit, dhe gjysma tjetër ishte private ose shkoi në diçka tjetër. Një sistem i shpërndarë paralel domethënës kompleks me memorie të përbashkët... gjysmë të përbashkët. Kur ishte kjo... As që më kujtohet, diku në mesin e viteve '80. Shumë kohë më parë. 
Po, le të supozojmë se 30 vjet janë shumë kohë më parë. Problemet që lidhen me llogaritjen e shpërndarë kanë ekzistuar për një kohë mjaft të gjatë; njerëzit kanë qenë prej kohësh në luftë me Beowulf-grupe. Grupe të tilla duken si... Për shembull: ka Ethernet dhe x86 juaj i shpejtë është i lidhur me këtë Ethernet, dhe tani dëshironi të merrni memorie të përbashkët të rreme, sepse askush nuk mund të bënte kodim kompjuterik të shpërndarë atëherë, ishte shumë e vështirë dhe për këtë arsye atje ishte memorie e përbashkët e rreme me faqet e memories mbrojtëse në x86, dhe nëse keni shkruar në këtë faqe, atëherë ne u thamë procesorëve të tjerë që nëse ata aksesojnë të njëjtën memorie të përbashkët, ajo do të duhej të ngarkohej nga ju, dhe kështu diçka si një protokoll për mbështetje u shfaq koherenca e cache dhe softueri për këtë. Koncept interesant. Problemi i vërtetë, natyrisht, ishte diçka tjetër. E gjithë kjo funksionoi, por shpejt keni probleme me performancën, sepse askush nuk i kuptoi modelet e performancës në një nivel mjaft të mirë - cilat modele të aksesit në memorie ishin aty, si të sigurohesh që nyjet të mos bënin ping pafund me njëri-tjetrin, etj.

Ajo që dola në H2O është se janë vetë zhvilluesit ata që janë përgjegjës për të përcaktuar se ku fshihet paralelizmi dhe ku jo. Unë dola me një model kodimi që e bëri shkrimin e kodit me performancë të lartë të lehtë dhe të thjeshtë. Por shkrimi i kodit që funksionon ngadalë është i vështirë, do të duket keq. Ju duhet të përpiqeni seriozisht të shkruani kod të ngadaltë, do të duhet të përdorni metoda jo standarde. Kodi i frenimit është i dukshëm në shikim të parë. Si rezultat, zakonisht shkruani kod që funksionon shpejt, por duhet të kuptoni se çfarë të bëni në rastin e memories së përbashkët. E gjithë kjo është e lidhur me vargje të mëdha dhe sjellja atje është e ngjashme me vargjet e mëdha jo të paqëndrueshme në Java paralele. Dua të them, imagjinoni që dy fije shkruajnë në një grup paralel, njëri prej tyre fiton, dhe tjetri, në përputhje me rrethanat, humbet, dhe ju nuk e dini se cili është cili. Nëse ato nuk janë të paqëndrueshme, atëherë rendi mund të jetë si të dëshironi - dhe kjo funksionon vërtet mirë. Njerëzit vërtet kujdesen për rendin e operacioneve, ata vendosin paqëndrueshmërinë në vendet e duhura dhe presin probleme të performancës që lidhen me kujtesën në vendet e duhura. Përndryshe, ata thjesht do të shkruanin kodin në formën e sytheve nga 1 në N, ku N është disa triliona, me shpresën se të gjitha rastet komplekse do të bëhen automatikisht paralele - dhe nuk funksionon atje. Por në H2O kjo nuk është as Java as Scala; nëse dëshironi, mund ta konsideroni "Java minus minus". Ky është një stil programimi shumë i qartë dhe është i ngjashëm me shkrimin e kodit të thjeshtë C ose Java me sythe dhe vargje. Por në të njëjtën kohë, memoria mund të përpunohet në terabajt. Unë ende përdor H2O. E përdor herë pas here në projekte të ndryshme - dhe është ende gjëja më e shpejtë, dhjetëra herë më e shpejtë se konkurrentët e saj. Nëse jeni duke bërë Big Data me të dhëna kolone, është shumë e vështirë të mposhtni H2O.

Sfidat teknike

Andrew: Cila ka qenë sfida juaj më e madhe në të gjithë karrierën tuaj?

Shkëmbi: Po diskutojmë për pjesën teknike apo jo teknike të çështjes? Unë do të thosha se sfidat më të mëdha nuk janë ato teknike. 
Sa i përket sfidave teknike. Thjesht i munda. Unë as nuk e di se cila ishte më e madhja, por kishte disa goxha interesante që morën mjaft kohë, luftë mendore. Kur shkova në Sun, isha i sigurt se do të bëja një përpilues të shpejtë dhe një grup të moshuarish thanë në përgjigje se nuk do të kisha sukses. Por unë ndoqa këtë rrugë, shkrova një përpilues në alokuesin e regjistrit dhe ishte mjaft i shpejtë. Ishte po aq i shpejtë sa C1 modern, por alokuesi ishte shumë më i ngadalshëm në atë kohë, dhe në pamje të pasme ishte një problem i madh i strukturës së të dhënave. Më duhej për të shkruar një alokues grafik të regjistrit dhe nuk e kuptova dilemën midis shprehjes së kodit dhe shpejtësisë, që ekzistonte në atë epokë dhe ishte shumë e rëndësishme. Doli që struktura e të dhënave zakonisht tejkalon madhësinë e cache-ve në x86 të asaj kohe, dhe për këtë arsye, nëse fillimisht supozoja se alokuesi i regjistrit do të punonte 5-10 përqind të kohës totale të nervozizmit, atëherë në realitet doli të ishte 50 për qind.

Me kalimin e kohës, përpiluesi u bë më i pastër dhe më efikas, ndaloi së gjeneruari kod të tmerrshëm në më shumë raste dhe performanca filloi të ngjante gjithnjë e më shumë me atë që prodhon një përpilues C. Përveç nëse, sigurisht, shkruani disa mut që as C nuk e shpejton . Nëse shkruani kod si C, do të merrni performancë si C në më shumë raste. Dhe sa më tej shkoni, aq më shpesh merrni kodin që përputhej asimptotikisht me nivelin C, alokuesi i regjistrit filloi të dukej si diçka e plotë... pavarësisht nëse kodi juaj funksionon shpejt apo ngadalë. Vazhdova të punoja për alokuesin për ta bërë atë të bënte zgjedhje më të mira. Ai bëhej gjithnjë e më i ngadalshëm, por jepte performancë gjithnjë e më të mirë në rastet kur askush tjetër nuk mund ta përballonte. Mund të zhytesha në një shpërndarës regjistrash, të varrosja një muaj punë atje dhe papritmas i gjithë kodi do të fillonte të ekzekutohej 5% më shpejt. Kjo ndodhi herë pas here dhe alokuesi i regjistrave u bë diçka si një vepër arti - të gjithë e donin ose e urrenin, dhe njerëzit nga akademia bënin pyetje në temën "pse bëhet gjithçka në këtë mënyrë", pse jo skanimi i linjës, dhe cili është ndryshimi. Përgjigja është ende e njëjtë: një alokues i bazuar në ngjyrosjen e grafikut plus punë shumë të kujdesshme me kodin bufer është e barabartë me një armë fitoreje, kombinimi më i mirë që askush nuk mund ta mposhtë. Dhe kjo është një gjë mjaft jo e qartë. Gjithçka tjetër që bën përpiluesi atje janë gjëra mjaft të studiuara, megjithëse janë sjellë edhe në nivelin e artit. Gjithmonë bëja gjëra që duhej ta kthenin përpiluesin në një vepër arti. Por asnjë nga këto nuk ishte asgjë e jashtëzakonshme - përveç caktuesit të regjistrit. Truku është të jesh i kujdesshëm prerë nën ngarkesë dhe, nëse kjo ndodh (mund të shpjegoj më hollësisht nëse më intereson), kjo do të thotë që ju mund të futeni në mënyrë më agresive, pa rrezikun e rënies mbi një ngërç në orarin e performancës. Në ato ditë, kishte një tufë përpiluesish në shkallë të plotë, të varur me bilbila dhe bilbil, që kishin alokues regjistrash, por askush tjetër nuk mund ta bënte këtë.

Problemi është se nëse shtoni metoda që i nënshtrohen rreshtimit, rritjes dhe rritjes së zonës së rreshtimit, grupi i vlerave të përdorura tejkalon menjëherë numrin e regjistrave dhe ju duhet t'i shkurtoni ato. Niveli kritik zakonisht vjen kur alokuesi heq dorë, dhe një kandidat i mirë për një derdhje vlen një tjetër, ju do të shisni disa gjëra përgjithësisht të egra. Vlera e inlinimit këtu është se ju humbisni një pjesë të shpenzimeve të përgjithshme, shpenzimet e sipërme për thirrje dhe ruajtje, mund t'i shihni vlerat brenda dhe mund t'i optimizoni më tej ato. Kostoja e inlinimit është se formohen një numër i madh vlerash të drejtpërdrejta, dhe nëse alokuesi juaj i regjistrit digjet më shumë seç duhet, ju humbni menjëherë. Prandaj, shumica e alokuesve kanë një problem: kur rreshtimi kalon një vijë të caktuar, gjithçka në botë fillon të shkurtohet dhe produktiviteti mund të hidhet në tualet. Ata që zbatojnë përpiluesin shtojnë disa heuristika: për shembull, për të ndaluar inlinimin, duke filluar me një madhësi mjaft të madhe, pasi alokimet do të shkatërrojnë gjithçka. Kështu formohet një ngërç në grafikun e performancës - ju inline, inline, performanca rritet ngadalë - dhe më pas bum! – bie si krik i shpejtë se u rreshtove shumë. Kështu funksiononte gjithçka përpara ardhjes së Java. Java kërkon shumë më tepër inlining, kështu që m'u desh ta bëja alokuesin tim shumë më agresiv në mënyrë që të nivelizohet në vend që të rrëzohet, dhe nëse futni shumë, fillon të derdhet, por më pas vjen momenti "jo më derdhje". Ky është një vëzhgim interesant dhe më erdhi nga hiçi, jo i dukshëm, por u shpagua mirë. Mora inlinimin agresiv dhe më çoi në vende ku performanca Java dhe C funksionojnë krah për krah. Ata janë vërtet afër - unë mund të shkruaj kodin Java që është dukshëm më i shpejtë se kodi C dhe gjëra të tilla, por mesatarisht, në pamjen e madhe të gjërave, ato janë afërsisht të krahasueshme. Mendoj se pjesë e kësaj merite është alokuesi i regjistrit, i cili më lejon të futem sa më marrëzi. Unë thjesht rreshtoj gjithçka që shoh. Pyetja këtu është nëse alokuesi funksionon mirë, nëse rezultati është kod që funksionon në mënyrë inteligjente. Kjo ishte një sfidë e madhe: të kuptosh gjithë këtë dhe ta bësh të funksionojë.

Pak për shpërndarjen e regjistrave dhe shumë bërthama

Vladimir: Problemet si shpërndarja e regjistrave duken si një lloj teme e përjetshme dhe e pafundme. Pyes veten nëse ka pasur ndonjëherë një ide që dukej premtuese dhe më pas dështoi në praktikë?

Shkëmbi: Sigurisht! Shpërndarja e regjistrave është një fushë në të cilën ju përpiqeni të gjeni disa heuristika për të zgjidhur një problem të plotë NP. Dhe nuk mund të arrini kurrë një zgjidhje perfekte, apo jo? Kjo është thjesht e pamundur. Shikoni, përpilimi Ahead of Time - gjithashtu funksionon dobët. Biseda këtu është për disa raste mesatare. Rreth performancës tipike, kështu që mund të shkoni dhe të matni diçka që mendoni se është performancë e mirë tipike - në fund të fundit, ju po punoni për ta përmirësuar atë! Shpërndarja e regjistrave është një temë mbi performancën. Pasi të keni prototipin e parë, ai funksionon dhe pikturon atë që nevojitet, fillon puna e performancës. Ju duhet të mësoni të matni mirë. Pse është e rëndësishme? Nëse keni të dhëna të qarta, mund të shikoni në zona të ndryshme dhe të shihni: po, kjo ndihmoi këtu, por këtu u prish gjithçka! Dalin disa ide të mira, ju shtoni heuristika të reja dhe papritmas çdo gjë fillon të funksionojë pak më mirë mesatarisht. Ose nuk fillon. Kam pasur një mori rastesh kur ne po luftonim për performancën pesë për qind që e diferenconte zhvillimin tonë nga alokuesi i mëparshëm. Dhe çdo herë duket kështu: diku fiton, diku humbet. Nëse keni mjete të mira të analizës së performancës, mund të gjeni idetë e humbura dhe të kuptoni pse ato dështojnë. Ndoshta ia vlen të lini gjithçka ashtu siç është, ose ndoshta të merrni një qasje më serioze për rregullimin e imët, ose të dilni dhe të rregulloni diçka tjetër. Është një mori gjërash! Unë bëra këtë hak të lezetshëm, por më duhet edhe ky, dhe ky dhe ky - dhe kombinimi i tyre total jep disa përmirësime. Dhe të vetmuarit mund të dështojnë. Kjo është natyra e punës së performancës për problemet e kompletuara me NP.

Vladimir: Ndihet ndjenja se gjërat si pikturimi në alokues janë një problem që tashmë është zgjidhur. Epo, është vendosur për ju, duke gjykuar nga ajo që thoni, kështu që a ia vlen atëherë…

Shkëmbi: Nuk zgjidhet si e tillë. Jeni ju që duhet ta ktheni atë në "të zgjidhur". Ka probleme të vështira dhe ato duhet të zgjidhen. Pasi të bëhet kjo, është koha për të punuar në produktivitetin. Ju duhet t'i qaseni kësaj pune në përputhje me rrethanat - bëni standarde, mblidhni metrikë, shpjegoni situatat kur, kur u kthyet në një version të mëparshëm, hakimi juaj i vjetër filloi të funksiononte përsëri (ose anasjelltas, ndaloi). Dhe mos u dorëzoni derisa të arrini diçka. Siç e thashë tashmë, nëse ka ide të lezetshme që nuk funksionuan, por në fushën e ndarjes së regjistrave të ideve është afërsisht e pafundme. Për shembull, mund të lexoni botime shkencore. Edhe pse tani kjo zonë ka filluar të lëvizë shumë më ngadalë dhe është bërë më e qartë se në rininë e saj. Megjithatë, ka njerëz të panumërt që punojnë në këtë fushë dhe të gjitha idetë e tyre ia vlen të provohen, të gjithë presin në krahë. Dhe nuk mund të thuash sa të mira janë nëse nuk i provon. Sa mirë integrohen me gjithçka tjetër në alokuesin tuaj, sepse një alokues bën shumë gjëra dhe disa ide nuk do të funksionojnë në alokuesin tuaj specifik, por në një alokues tjetër ato do të funksionojnë lehtësisht. Mënyra kryesore për të fituar për alokuesin është të tërheqësh gjërat e ngadalta jashtë shtegut kryesor dhe ta detyrosh atë të ndahet përgjatë kufijve të shtigjeve të ngadalta. Pra, nëse doni të drejtoni një GC, merrni rrugën e ngadaltë, deoptimizoni, bëni një përjashtim, të gjitha këto gjëra - ju e dini që këto gjëra janë relativisht të rralla. Dhe ato janë vërtet të rralla, kontrollova. Ju bëni punë shtesë dhe kjo heq shumë kufizime në këto shtigje të ngadalta, por nuk ka shumë rëndësi sepse ato janë të ngadalta dhe udhëtohen rrallë. Për shembull, një tregues null - nuk ndodh kurrë, apo jo? Ju duhet të keni disa shtigje për gjëra të ndryshme, por ato nuk duhet të ndërhyjnë me atë kryesore. 

Vladimir: Çfarë mendoni për multi-bërthamat, kur ka mijëra bërthama në të njëjtën kohë? A është kjo një gjë e dobishme?

Shkëmbi: Suksesi i GPU-së tregon se është mjaft i dobishëm!

Vladimir: Janë mjaft të specializuar. Po në lidhje me procesorët me qëllime të përgjithshme?

Shkëmbi: Epo, ky ishte modeli i biznesit të Azulit. Përgjigja u kthye në një epokë kur njerëzve vërtet u pëlqente performanca e parashikueshme. Ishte e vështirë të shkruash kodin paralel në atë kohë. Modeli i kodimit H2O është shumë i shkallëzueshëm, por nuk është një model me qëllim të përgjithshëm. Ndoshta pak më e përgjithshme sesa kur përdorni një GPU. Po flasim për kompleksitetin e zhvillimit të një gjëje të tillë apo kompleksitetin e përdorimit të saj? Për shembull, Azul më mësoi një mësim interesant, një mësim jo të qartë: memoriet e vogla janë normale. 

Sfida më e madhe në jetë

Vladimir: Po sfidat jo-teknike?

Shkëmbi: Sfida më e madhe ishte të mos ishe... i sjellshëm dhe i sjellshëm me njerëzit. Dhe si rezultat, vazhdimisht e gjeja veten në situata jashtëzakonisht konfliktuale. Ata ku e dija se gjërat po shkonin keq, por nuk dinin të ecnin përpara me ato probleme dhe nuk mund t'i trajtonin ato. Në këtë mënyrë lindën shumë probleme afatgjata, të zgjatura me dekada. Fakti që Java ka përpilues C1 dhe C2 është pasojë e drejtpërdrejtë e kësaj. Fakti që për dhjetë vjet rresht nuk ka pasur përpilim në shumë nivele në Java është gjithashtu një pasojë e drejtpërdrejtë. Është e qartë se ne kishim nevojë për një sistem të tillë, por nuk është e qartë pse nuk ekzistonte. Kam pasur probleme me një inxhinier... ose një grup inxhinierësh. Dikur, kur fillova të punoja në Sun, isha... Mirë, jo vetëm atëherë, përgjithësisht kam gjithmonë mendimin tim për çdo gjë. Dhe mendova se ishte e vërtetë që mund ta merrje këtë të vërtetën tënde dhe ta thuash kokën. Sidomos pasi kisha të drejtë tronditëse shumicën e kohës. Dhe nëse nuk ju pëlqen kjo qasje... veçanërisht nëse padyshim e keni gabim dhe bëni marrëzi... Në përgjithësi, pak njerëz mund ta tolerojnë këtë formë komunikimi. Edhe pse disa munden, si unë. Tërë jetën time e kam ndërtuar mbi parime meritokratike. Nëse më tregoni diçka të gabuar, unë menjëherë do të kthehem dhe do të them: thatë marrëzi. Në të njëjtën kohë, natyrisht, kërkoj falje dhe të gjitha këto, do të vërej meritat, nëse ka, dhe do të ndërmarr veprime të tjera korrekte. Nga ana tjetër, kam jashtëzakonisht të drejtë për një përqindje tronditëse të madhe të kohës totale. Dhe nuk funksionon shumë mirë në marrëdhëniet me njerëzit. Nuk po përpiqem të jem i mirë, por po e bëj pyetjen troç. "Kjo nuk do të funksionojë kurrë, sepse një, dy dhe tre." Dhe ata thanë: "Oh!" Kishte pasoja të tjera që ndoshta ishte më mirë t'i injoroja: për shembull, ato që çuan në një divorc nga gruaja ime dhe dhjetë vjet depresion pas kësaj.

Sfida është një luftë me njerëzit, me perceptimin e tyre për atë që ju mund ose nuk mund të bëni, çfarë është e rëndësishme dhe çfarë jo. Kishte shumë sfida në lidhje me stilin e kodimit. Unë ende shkruaj shumë kode, dhe në ato ditë madje më duhej të ngadalësoja, sepse po bëja shumë detyra paralele dhe po i bëja dobët, në vend që të fokusohesha në një. Duke parë prapa, shkrova gjysmën e kodit për komandën Java JIT, komandën C2. Koduesi tjetër më i shpejtë shkroi gjysmën më të ngadaltë, tjetri gjysmën më ngadalë dhe ishte një rënie eksponenciale. Personi i shtatë në këtë rresht ishte shumë, shumë i ngadalshëm - kjo ndodh gjithmonë! Kam prekur shumë kode. Shikova se kush çfarë shkroi, pa përjashtim, ia nguli sytë kodit të tyre, rishikova secilin prej tyre dhe vazhdova të shkruaj më shumë vetë se kushdo prej tyre. Kjo qasje nuk funksionon shumë mirë me njerëzit. Disa njerëz nuk e pëlqejnë këtë. Dhe kur nuk e përballojnë dot, fillojnë lloj-lloj ankesash. Për shembull, një herë më thanë të ndaloja kodimin sepse po shkruaja shumë kod dhe kjo po rrezikonte ekipin, dhe gjithçka më dukej si shaka: shoku, nëse pjesa tjetër e ekipit zhduket dhe unë vazhdoj të shkruaj kod, ti Do të humbasin vetëm gjysmë skuadre. Nga ana tjetër, nëse vazhdoj të shkruaj kod dhe ju humbni gjysmën e ekipit, kjo tingëllon si një menaxhim shumë i keq. Nuk kam menduar kurrë për të, nuk kam folur kurrë për të, por ishte ende diku në kokën time. Mendimi po rrotullohej në pjesën e prapme të mendjes sime: "A po tallesh të gjithë me mua?" Pra, problemi më i madh isha unë dhe marrëdhëniet e mia me njerëzit. Tani e kuptoj veten shumë më mirë, kam qenë një drejtues ekipi për programuesit për një kohë të gjatë, dhe tani u them drejtpërdrejt njerëzve: ju e dini, unë jam ai që jam, dhe ju do të duhet të merreni me mua - a është mirë nëse qëndroj? këtu? Dhe kur ata filluan të merren me të, gjithçka funksionoi. Në fakt, unë nuk jam as i keq, as i mirë, nuk kam asnjë qëllim të keq apo aspiratë egoiste, është thjesht thelbi im dhe më duhet të jetoj me të disi.

Andrew: Kohët e fundit të gjithë filluan të flasin për vetëdijen për introvertët, dhe aftësitë e buta në përgjithësi. Çfarë mund të thoni për këtë?

Shkëmbi: Po, ky ishte depërtimi dhe mësimi që mësova nga divorci nga gruaja ime. Ajo që mësova nga divorci ishte të kuptoja veten. Kështu fillova të kuptoj njerëzit e tjerë. Kuptoni se si funksionon ky ndërveprim. Kjo çoi në zbulime njëra pas tjetrës. Kishte një vetëdije se kush jam dhe çfarë përfaqësoj. Çfarë jam duke bërë: ose jam i preokupuar me detyrën, ose po shmang konfliktin, ose diçka tjetër - dhe ky nivel i vetëdijes me të vërtetë ndihmon për të mbajtur veten nën kontroll. Pas kësaj, gjithçka shkon shumë më lehtë. Një gjë që kam zbuluar jo vetëm tek vetja, por edhe tek programuesit e tjerë është paaftësia për të verbalizuar mendimet kur jeni në gjendje stresi emocional. Për shembull, ju jeni ulur atje duke koduar, në një gjendje rrjedhjeje, dhe më pas ata vijnë me vrap drejt jush dhe fillojnë të bërtasin me histerikë se diçka është prishur dhe tani do të merren masa ekstreme kundër jush. Dhe nuk mund të thuash asnjë fjalë sepse je në një gjendje stresi emocional. Njohuritë e fituara ju lejojnë të përgatiteni për këtë moment, ta mbijetoni atë dhe të kaloni në një plan tërheqjeje, pas së cilës mund të bëni diçka. Pra, po, kur filloni të kuptoni se si funksionon gjithçka, është një ngjarje e madhe që të ndryshon jetën. 
Unë vetë nuk mund të gjeja fjalët e duhura, por mbaja mend sekuencën e veprimeve. Çështja është se ky reagim është sa fizik aq edhe verbal dhe ju duhet hapësirë. Një hapësirë ​​e tillë, në kuptimin Zen. Kjo është pikërisht ajo që duhet shpjeguar, dhe pastaj menjëherë të largohesh - largohu thjesht fizikisht. Kur hesht verbalisht, mund ta përpunoj situatën emocionalisht. Ndërsa adrenalina arrin trurin tuaj, ju kalon në modalitetin e luftimit ose fluturimit, nuk mund të thoni më asgjë, jo - tani ju jeni një idiot, një inxhinier fshikullues, i paaftë për një përgjigje të denjë apo edhe për të ndaluar sulmin, dhe sulmuesi është i lirë. për të sulmuar përsëri dhe përsëri. Së pari ju duhet të bëheni përsëri vetvetja, të rifitoni kontrollin, të dilni nga modaliteti "luftoni ose fluturoni".

Dhe për këtë na duhet hapësirë ​​verbale. Vetëm hapësirë ​​e lirë. Nëse thua diçka fare, atëherë mund të thuash pikërisht atë, dhe më pas shkoni dhe gjeni vërtet "hapësirë" për veten tuaj: shkoni për një shëtitje në park, mbylleni veten në dush - nuk ka rëndësi. Gjëja kryesore është të shkëputeni përkohësisht nga kjo situatë. Sapo fikni për të paktën disa sekonda, kontrolli kthehet, filloni të mendoni me maturi. "Mirë, unë nuk jam një lloj idiot, nuk bëj budallallëqe, unë jam një person mjaft i dobishëm." Pasi të keni qenë në gjendje të bindni veten, është koha për të kaluar në fazën tjetër: të kuptoni se çfarë ndodhi. Ju sulmuan, sulmi erdhi nga nuk e prisnit, ishte një pritë e pandershme, e poshtër. Kjo është e keqe. Hapi tjetër është të kuptojmë pse sulmuesit i duhej kjo. Vërtet pse? Ndoshta sepse ai vetë është i tërbuar? Pse është i çmendur? Për shembull, për shkak se ai e ka dehur veten dhe nuk pranon dot përgjegjësinë? Kjo është mënyra për të trajtuar me kujdes të gjithë situatën. Por kjo kërkon hapësirë ​​për manovrim, hapësirë ​​verbale. Hapi i parë është ndërprerja e kontaktit verbal. Shmangni diskutimin me fjalë. Anulojeni, largohuni sa më shpejt që të jetë e mundur. Nëse është një bisedë telefonike, thjesht mbyllni - kjo është një aftësi që mësova nga komunikimi me ish-gruan time. Nëse biseda nuk po shkon askund mirë, thjesht thuaj "lamtumirë" dhe mbylle telefonin. Nga ana tjetër e telefonit: "bla bla bla", ju përgjigjeni: "Po, mirupafshim!" dhe mbyll telefonin. Ju thjesht përfundoni bisedën. Pesë minuta më vonë, kur ju kthehet aftësia për të menduar në mënyrë të arsyeshme, ju jeni ftohur pak, bëhet e mundur të mendoni për gjithçka, çfarë ndodhi dhe çfarë do të ndodhë më pas. Dhe filloni të formuloni një përgjigje të menduar, në vend që të reagoni thjesht nga emocionet. Për mua, përparimi në vetëdijen ishte pikërisht fakti që në rast stresi emocional nuk mund të flas. Të dalësh nga kjo gjendje, të mendosh dhe të planifikosh se si të përgjigjesh dhe të kompensosh problemet - këto janë hapat e duhur në rastin kur nuk mund të flasësh. Mënyra më e lehtë është të ikësh nga situata në të cilën shfaqet stresi emocional dhe thjesht të ndalosh pjesëmarrjen në këtë stres. Pas kësaj ju bëheni të aftë për të menduar, kur mund të mendoni, bëheni të aftë të flisni, etj.

Nga rruga, në gjykatë, avokati kundërshtar përpiqet t'jua bëjë këtë - tani është e qartë pse. Sepse ai ka aftësinë të të shtypë në një gjendje të tillë që nuk mund ta shqiptosh as emrin, për shembull. Në një kuptim shumë të vërtetë, ju nuk do të jeni në gjendje të flisni. Nëse ju ndodh kjo, dhe nëse e dini se do të gjeni veten në një vend ku betejat verbale janë të ndezura, në një vend si gjykata, atëherë mund të vini me avokatin tuaj. Avokati do të ngrihet për ju dhe do të ndalojë sulmin verbal dhe do ta bëjë atë në mënyrë krejtësisht të ligjshme dhe hapësira e humbur Zen do t'ju kthehet. Për shembull, më është dashur të telefonoj disa herë familjen time, gjyqtari ishte mjaft miqësor për këtë, por avokati kundërshtar bërtiti dhe bërtiti mbi mua, unë nuk mund të merrja as një fjalë të çuditshme. Në këto raste, përdorimi i një ndërmjetësi funksionon më mirë për mua. Ndërmjetësi ndalon gjithë këtë presion që po ju derdh në një rrjedhë të vazhdueshme, ju gjen hapësirën e nevojshme Zen dhe bashkë me të kthehet aftësia për të folur. Kjo është një fushë e tërë njohurish në të cilën ka shumë për të studiuar, shumë për të zbuluar brenda vetes dhe e gjithë kjo kthehet në vendime strategjike të nivelit të lartë që janë të ndryshme për njerëz të ndryshëm. Disa njerëz nuk i kanë problemet e përshkruara më sipër; zakonisht, njerëzit që janë shitës profesionistë nuk i kanë ato. Të gjithë këta njerëz që e bëjnë jetesën me fjalë - këngëtarë, poetë, udhëheqës fetarë dhe politikanë të njohur, kanë gjithmonë diçka për të thënë. Ata nuk kanë probleme të tilla, por unë kam.

Andrew: Ishte... e papritur. Shkëlqyeshëm, ne kemi folur tashmë shumë dhe është koha për të përfunduar këtë intervistë. Ne patjetër do të takohemi në konferencë dhe do të mund të vazhdojmë këtë dialog. Shihemi në Hidra!

Bisedën me Cliff-in mund ta vazhdoni në konferencën Hydra 2019, e cila do të mbahet në datat 11-12 korrik 2019 në Shën Petersburg. Ai do të vijë me një raport "Përvoja e kujtesës transaksionale të harduerit Azul". Biletat mund të blihen në faqen zyrtare.

Burimi: www.habr.com

Shto një koment