Një intervistë e madhe me Cliff Click — babai i kompilimit JIT në Java

Një intervistë e madhe me Cliff Click — babai i kompilimit JIT në JavaCliff Click — CTO of Cratus (IoT sensors for process improvement), founder and co-founder of several startups (including Rocket Realtime School, Neurensic, and H2O.ai) with several successful exits. Cliff wrote his first compiler at the age of 15 (Pascal for TRS Z-80)! He is best known for his work on C2 in Java (the Sea of Nodes IR). This compiler demonstrated to the world that JIT can produce high-quality code, which became one of the factors in establishing Java as one of the major modern software platforms. Later, Cliff helped Azul Systems build an 864-core mainframe with software written purely in Java that supported GC pauses on a 500-gigabyte heap within 10 milliseconds. Overall, Cliff has worked on all aspects of the JVM.

 
This blog post is a large interview with Cliff. We will discuss the following topics:

  • Transitioning to low-level optimizations
  • How to conduct large refactoring
  • Cost model
  • Learning low-level optimizations
  • Practical examples of performance improvement
  • Why create your own programming language
  • Career of a performance engineer
  • Technical challenges
  • A bit about register allocation and multithreading
  • The biggest challenge in life

The interviewers are:

  • Andrey Sataryn from Amazon Web Services. In his career, he has worked on a variety of projects: he tested a NewSQL distributed database at Yandex, a cloud detection system at Kaspersky Lab, a multiplayer game at Mail.ru, and a currency pricing service at Deutsche Bank. He is interested in testing large-scale backend and distributed systems.
  • Vladimir Sitnikov from Netcracker. He has been working on the performance and scalability of NetCracker OS — software used by telecom operators to automate network management processes and network equipment. He is passionate about the performance issues of Java and Oracle Database. He is the author of more than a dozen performance improvements in the official PostgreSQL JDBC driver.

Transitioning to low-level optimizations

Andrey: You are a well-known figure in JIT compilation, in Java and performance work in general, right? 

Cliff: That's right!

Andrey: Let's start with general questions about performance work. What do you think about the choice between high-level and low-level optimizations like working at the CPU level?

Cliff: Këtu gjithçka është e thjeshtë. Kodi më i shpejtë është ai që kurrë nuk ekzekutohet. Prandaj, gjithmonë duhet të filloni nga një nivel i lartë, të punoni mbi algoritmet. Një notacion O më i mirë do të tejkalojë një notacion O më të dobët, përveç rastit kur ndërhyjnë ndonjëherë disa constante mjaft të mëdha. Gjërat me nivel të ulët vijnë të fundit. Zakonisht, nëse keni optimizuar të gjithë shtetin tjetër mjaft mirë dhe ende ka diçka interesante – kjo është niveli i ulët. Por si të filloni nga një nivel i lartë? Si të dini se keni bërë mjaft punë në nivel të lartë? Epo... nuk ka mënyra të gatshme. Duhet të kuptoni problemin, të vendosni se çfarë do të bëni (për të mos e bërë hapin e panevojshëm më vonë) dhe atëherë mund të hapni profilerin që mund të thotë diçka të dobishme. Në një moment, ju vetë e kuptoni se keni hequr dorë nga gjërat e panevojshme dhe është koha të merret me rregullimin e hollë të nivelit të ulët. Kjo është me siguri një lloj arti. Një mori njerëzish bëjnë gjëra të panevojshme, por lëvizin aq shpejt saqë nuk kanë kohë të mendojnë për performancën. Por kjo zgjat derisa çështja të bëhet e rëndësishme. Zakonisht, 99% e kohës askujt nuk i intereson se çfarë po bëj, deri në momentin kur një gjë e rëndësishme do të dalë në rrugën kritike për të cilën dikujt i intereson. Dhe këtu të gjithë fillojnë të të tipojnë pse gjithçka nuk po punonte perfekt që në fillim. Në përgjithësi, gjithmonë ka diçka për të përmirësuar në performancë. Por 99% e kohës nuk keni pista! Thjesht po përpiqeni të bëni diçka të funksionojë dhe gjatë kësaj i kuptoni ato që janë thelbësore. Kurrë nuk mund ta dini paraprakisht se ky copë duhet të bëhet perfekt, prandaj, në thelb, jeni në mëshirë të qenit perfekt në gjithçka. Dhe kjo është e pamundur dhe nuk bëni ashtu. Gjithmonë ka shumë gjëra për t'u riparuar – dhe kjo është krejt normale.

How to conduct large refactoring

Andrey: Si punoni mbi performancën? Kjo është një problem i përgjithshëm. Për shembull, a keni ndonjëherë punuar mbi problemet që lindin nga ndërveprimi i një sërë funksionalitetesh të ekzistuese?

Cliff: Unë përpiqem ta shmang këtë. Nëse e di që performanca do të bëhet një problem, mendoj përpara se të filloj të kodoj, veçanërisht për strukturat e të dhënave. Por shpesh e zbulojmë këtë shumë më vonë. Dhe atëherë na duhet të marrim masa drastike dhe të bëjmë atë që e quaj «rishkruaj dhe mbizotëro»: duhet të kapim një copë të mjaftueshme. Një pjesë e kodit do të duhet të rishkruhet për shkak të problemeve me performancën ose për ndonjë arsye tjetër. Çdo arsye që ka të bëjë me rishkrimin e kodit, ndonjëherë është më mirë të rishkruash një copë më të madhe sesa një copë më të vogël. Në këtë moment, të gjithë fillojnë të tremben: «o Zot, nuk mund të prekim kaq shumë kod!» Por, në fakt, ky qasje funksionon shumë më mirë në shumicën e rasteve. Duhet ta kapësh një problem të madh menjëherë, ta përshkruash atë rreth një rrethi të madh dhe të thuash: çdo gjë brenda këtij rrethi, unë do ta rishkruaj. Kufiri është shumë më i vogël se ai përmbajtje brenda saj, e cila duhet të zëvendësohet. Dhe nëse këtë delimitim kufijsh e bën punën brenda perfekte – ke duar të lira, bëj çfarë dëshiron. Pasi e kupton problemin, procesi i rishkrimit shkon shumë më lehtë, prandaj kap një copë të madhe!
Në të njëjtën kohë, kur bën rishkrimin me një copë të madhe dhe kupton se performanca do të bëhet një problem, mund të fillosh menjëherë të shqetësohesh për të. Normalisht, kjo shndërrohet në gjëra të thjeshta si «mos e kopjo të dhënat, menaxho të dhënat sa më thjeshtë, bëj ato më të vogla». Në rishkrime të mëdha, ka mënyra standarde për të përmirësuar performancën. Dhe ato pothuajse gjithmonë rrotullohen rreth të dhënave.

Cost model

Andrey: Në një nga podkcastet, keni folur për modelet e kostos në kontekstin e performancës. Mund të shpjegoni se çfarë kishte të bëjë me këtë?

Cliff: Sigurisht. Unë kam lindur në një epokë kur performanca e procesorit ishte jashtëzakonisht e rëndësishme. Dhe kjo epokë po kthehet përsëri – fati nuk është pa ironinë e tij. Kam filluar të jetoj në kohët e makinave tetëbitëshe, kompjuteri im i parë punonte me 256 byte. Pikërisht byte. Çdo gjë ishte shumë e vogël. Duhej të numëroja instrukcionet dhe sapo filluam të avancohemi lart në piramidën e gjuhëve të programimit, gjuhët merrnin përsipër gjithnjë e më shumë. Kishim Assembler, pastaj Basic, pastaj C, dhe C merrte përsipër punën me shumë detaje, si për shembull menaxhimi i regjistrave dhe zgjedhja e instrukcioneve. Por atje gjithçka ishte mjaft e kuptueshme dhe nëse unë bëja një tregues në një instancë variable, do të merrja një ngarkesë, dhe kostoja e këtij instrukcioni është e njohur. Hardueri jep një numër të njohur ciklesh mahnitës, kështu që shpejtësia e ekzekutimit të gjërave të ndryshme mund të llogaritet thjesht duke përmbledhur të gjitha instrukcionet që po planifikon të ekzekutosh. Çdo krahasim/testim/filim/thirrje/ngarkesë/ruajtje mund të përmblidhet dhe të thuhet: ja koha e ekzekutimit. Duke u marrë me përmirësimin e performancës, patjetër që do t'i shikosh numrat që përkojnë me ciklet e ngrohta të vogla. 
Por sapo kalon në Java, Python dhe gjëra të ngjashme, shumë shpejt largohet nga hardueri i nivelit të ulët. Cila është kostoja e thirrjes së getter-it në Java? Nëse JIT në HotSpot e ka bërë gjithçka siç duhet në linj të drejtpërdrejtë, kjo do të jetë ngarkesë, por, nëse ai nuk e bëri – kjo do të jetë një thirrje funksioni. Pasiqë thirrja është në një cikël të ngrohtë, ajo do të anulojë të gjitha optimizimet e tjera në këtë cikël. Prandaj, kostoja reale do të jetë shumë më e madhe. Dhe menjëherë humbet aftësinë për të parë një copë kod dhe të kuptosh se sa vlen ta ekzekutosh atë në terma të frekuencës së procesorit, memories përdorur dhe caches. Të gjitha këto bëhen interesante vetëm nëse do të futesh thellë në performancë.
Tani jemi në një situatë ku shpejtësitë e procesorëve nuk kanë pasur rritje për më shumë se një dekadë. Koha e vjetër po kthehet! Nuk mund të shqetësoheni më për performancën e mirë të një njësie. Por, nëse papritmas merret me llogaritjet paralele – është gjithmonë e komplikuar, të gjithë të shikojnë si James Bond. Shpejtësitë dhjetëfishohet zakonisht ndodhin në ato vende ku dikush ka humbur diçka. Paraleliteti kërkon shumë punë. Për të arritur atë dhjetëfishim të shpejtësisë, duhet të kuptohet modeli i kostos. Çfarë dhe sa kushton. Dhe për këtë, duhet të kuptohet se si gjuha përputhet me harduerin në dispozita.
Martin Thompson ka gjetur një fjalë të shkëlqyer për blogun e tij Simpatia Mekanike! Duhet të kuptohet se çfarë do të bëjë hardueri, si do ta bëjë dhe pse e bën atë që bën. Duke e përdorur këtë, është mjaft e lehtë të fillosh të numërosh instruktionet dhe të zbulohet se ku po shkon koha e ekzekutimit. Nëse nuk ke përgatitjen përkatëse, thjesht je duke kërkuar një mace të zezë në një dhomë të errët. Unë vazhdimisht shoh njerëz që optimizojnë performancën, ndonëse ata nuk kanë as një ide se çfarë po bëjnë. Ata kanë shumë vuajtje dhe nuk po e arrijnë ndonjëherë qëllimin. Kur unë marr të njëjtin copë kod, vendos atje disa hacks të vogla dhe arrij katërfishim ose dhjetëfishim të shpejtësisë, ata thonë: mirë, kjo nuk është e ndershme, ne e dinim se ishe më i mirë. Maçkullisht. Çfarë po flas… modeli i kostos është për atë që kodin e shkruan dhe si funksionon në mes një pamjeje të përgjithshme.

Andrey: Dhe si të mbash një vëllim të tillë në mendje? Kjo arrihet përmes shumë përvojës, apo? Ku sigurohet një përvojë e tillë?

Cliff: Mirë, përvoja ime ka ardhur në një mënyrë jo të lehtë. Kam programuar në Assembler në ato kohë kur mund të kuptohej çdo instruktion i veçantë. Kjo duket e çuditshme, por që nga ajo kohë kam një grup instruktionesh Z80 që më kanë mbetur gjithmonë në mendje. Nuk kujtoj emrat e njerëzve pas një minute të bisedës, por kujtoj kodin e shkruar 40 vjet më parë. Qesharake, kjo duket si sindromi i "mendjes së çmendur».

Learning low-level optimizations

Andrey: A ka ndonjë mënyrë më të thjeshtë për të hyrë në këtë fushë?

Cliff: Po, dhe jo. Hardueri për të cilin të gjithë ne përdorim, gjatë kësaj kohe nuk ka ndryshuar shumë. Të gjithë përdorin x86, përveç smartphone-ve që përdorin Arm. Nëse nuk merresh me ndonjë embedim të fortë, ke të njëjtën gjë. Mirë, më tutje. Instruksionet gjithashtu nuk kanë ndryshuar për shekuj. Duhet të shkosh dhe të shkruash diçka në Assembler. Pak, por mjaftueshëm për të filluar të kuptohet. Ju buzëqeshni, por unë flas gjithmonë seriozisht. Duhet të kuptosh përputhjen mes gjuhës dhe harduerit. Pas kësaj, duhet të shkosh, të shkruash pak dhe të bësh një kompilator të vogël për një gjuhë të vogël. "I vogël" do të thotë se duhet ta bësh atë brenda një kohë të arsyeshme. Ai mund të jetë shumë i thjeshtë, por duhet të gjenerojë instruksione. Akti i gjenerimit të instruksionit do të lejojë të kuptohet modeli i koston për urën mes kodit të nivelit të lartë, në të cilin të gjithë shkruajnë, dhe kodit të makinës, i cili ekzekutohet në harduer. Kjo përputhje do të thellohet në mendjet e njerëzve në momentin e shkruarjes së kompilatorit. Edhe të kompilatorit më të thjeshtë. Pas kësaj, mund të fillosh të shikosh Java dhe se sa e thellë është hendeku semantik dhe për të ndërtuar ura mbi të, është shumë më e komplikuar. Në Java është shumë më e vështirë të kuptosh nëse ura jonë është e mirë apo e keqe, çfarë do ta bëjë atë të shembet dhe çfarë jo. Por të duhet një pikë nisëse, kur shikon kodin dhe kupton: "Ah, ky getter duhet të linjëzohet çdo herë". Më pas del se ndonjëherë ndodh, përveç rasteve kur metodi bëhet shumë i madh dhe JIT fillon të linjëzojë gjithçka. Performanca e këtyre vendeve mund të parashikohet menjëherë. Zakonisht gettaret punojnë mirë, por pastaj shikon ciklet e mëdha të nxehta dhe kupton se ka thirrje funksionesh që nuk e dinë se çfarë bëjnë. Kjo është problemi me përdorimin universalisht të gettareve, arsyeja pse ato nuk linjëzohen - nuk është e qartë se a është ky një getter. Nëse ke një bazë të kodit shumë të vogël, mund ta mbash mend atë dhe më pas të thuash: ja, ky është një getter, dhe ky është një setter. Në një bazë të madhe të kodit, çdo funksion jeton historinë e vet, të cilën askush, në përgjithësi, nuk e di. Profili tregon se kemi humbur 24% të kohës në ndonjë cikël dhe për të kuptuar se çfarë bën ky cikël, duhet të shikosh çdo funksion brenda. Nuk është e mundur të kuptohet kjo, pa studiuar funksionin, dhe kjo e ngadalëson seriozisht procesin e kuptimit. Prandaj unë nuk përdor geterë e seterë, unë kam arritur në një nivel të ri!
Si te pyet se ku ta gjej modelin e kostos? Po, mund të lexosh diçka, sigurisht... Por mendoj se mënyra më e mirë është të veprosh. Të krijosh një kompilator të vogël dhe kjo do të jetë mënyra më e mirë për ta kuptuar modelin e kostos dhe ta vendosësh atë në kokën e tua. Një kompilator i vogël që do të ishte i dobishëm për programimin e mikrovalës - kjo është një detyrë për fillestarët. E di që nëse tashmë ke aftësi programimi, duhet të mjaftojnë. Të gjitha këto gjëra si shfrytëzimi i një vargu që do të jetë ndonjë shprehje algjebrike, nxjerrja e udhëzimeve për operacionet matematikore në rendin e duhur, marrja e vlerave të sakta nga regjistrat - gjithçka bëhet lehtë. Dhe sa herë që bën këtë, do të mbetet në mendje. Mendoj se të gjithë e dinë se çfarë bën një kompilator. Dhe kjo do të sjellë kuptimin e modelit të kostos.

Practical examples of performance improvement

Andrey: Çfarë tjetër duhet të vëmë re gjatë punës mbi performancën?

Cliff: Strukturat e të dhënave. Për të vërtetë, unë nuk kam mbajtur këto lekcioni prej një kohe të gjatë... Rocket School. Ishte argëtuese, por kërkonte shumë përpjekje, dhe unë kam një jetë gjithashtu! Mirë, kështu që, në njërën nga mësimet më të mëdha dhe interesante, "Ku shkon performanca juaj", iu dhashë studentëve një shembull: dy e gjysmë gigabajt të dhënash fintech lexoheshin nga një skedar CSV dhe më pas duhej të llogaritej numri i produkteve të shitura. Të dhëna të rregullta tregtare. Paketa UDP, të transformuara në format tekstual, që nga vitet '70. Chicago Mercantile Exchange - gjëra si nafta, misri, soja dhe të ngjashme. Duhej të llogaritnim këto produkte, numrin e transaksioneve, volumin mesatar të qarkullimit të fondeve dhe mallrave, etj. Kjo është matematikë tregtare mjaft e thjeshtë: gjej kodin e produktit (këto janë 1-2 simbole në një tabelë hesh), merr shumën, shto në një nga grupe të transaksioneve, shto volumet, shto kostot dhe disa gjëra të tjera. Matematikë shumë e thjeshtë. Implementimi i lodhshëm ishte mjaft i drejtpërdrejtë: gjithçka ndodhte në një skedar, lexoja skedarin dhe lëvizja përmes tij, duke ndarë regjistrat në vargje Java, duke kërkuar në to gjërat e nevojshme dhe duke i shtuar sipas matematikës së përshkruar më sipër. Dhe kjo funksiononte me një shpejtësi të vogël.

Me këtë qasje, gjithçka është e qartë se çfarë po ndodh, dhe llogaritë paralele këtu nuk ndihmojnë, e drejta? Doli që rritja pesëfish e performancës mund të arrihet thjesht duke zgjedhur struktura të duhura të të dhënave. Dhe kjo e befason edhe programuesit më të përvojshëm! Në rastin tim konkret, fokusi ishte që nuk duhet të bësh ndarje memorie në ciklin e nxehtë. Po, kjo nuk është e gjithë e vërteta, por në përgjithësi - nuk duhet të ndash 'në çdo X', kur X është mjaft i madh. Kur X është dy e gjysmë gigabajt, nuk duhet të ndash asgjë 'në çdo letër', ose 'në çdo rresht', ose 'në çdo fushë', asgjë të tillë. Pikërisht këtu shpenzohet koha. Si funksionon kjo në të vërtetë? Imagjinoni se unë bëj një thirrje String.split() ose BufferedReader.readLine(). Readline bën një varg nga një grup bajtokësh që vijnë përmes rrjetit, një herë për çdo varg, për secilin nga qindra miliona vargjeve. Unë e marr këtë varg, e analizoj dhe e hedh. Pse e hedh - mirë, unë e kam përpunuar atë, mbaroi. Pra, për çdo bajt që lexoj nga këto 2.7G, do të shkruhen dy simbole në varg, dmth tashmë 5.4G, dhe ato më tej nuk më duhen për asgjë, ndaj hidhen. Nëse shikojmë kapacitetin e memorjes, ne ngarkojmë 2.7G, që kalojnë përmes memories dhe busit të memories në procesor, dhe më pas dy herë më shumë dërgohen në vargun që është në memorje, dhe e gjithë kjo përplaset kur krijohet çdo varg i ri. Por unë duhet ta lexoj atë, hardueri e lexon atë, edhe nëse pastaj gjithçka do të përpunohet. Dhe duhet ta shkruaj, sepse kam krijuar një varg dhe caches janë mbushur – cache nuk mund ta akomodojë 2.7G. Pra, për çdo bajt të lexuar lexoj edhe dy bajte shtesë dhe shkruaj dy bajte shtesë, dhe në fund ata kanë një raport 4:1 – në këtë raport ne harxhojmë pa efekt kapacitetin e memories. Dhe më pas del se nëse bëj String.split() – e bëj këtë shumë herë, brenda mund të kenë edhe 6-7 fusha të tjera. Prandaj, kodi klasik për leximin e CSV duke pasuar analizën e vargjeve çon në humbje të kapacitetit të memories në rreth 14:1 në krahasim me atë që do të dëshironit realisht të kishit. Nëse heqim këto ndarje, atëherë mund të arrijmë një përshpejtim pesëfish.

Dhe kjo nuk është ndonjë gjë tejet e komplikuar. Nëse e shihni kodin nga këndvështrimi i duhur, gjithçka bëhet mjaft e thjeshtë, sapo ta kuptoni esencialin e problemit. Nuk duhet aspak të ndaloni dhënien e memories: problemi është se po i jepni diçka dhe ajo vdes menjëherë, duke shpenzuar një burim të rëndësishëm, i cili në këtë rast është kapaciteti i memories. Dhe e gjithë kjo rezulton në një rënie të performancës. Në x86 zakonisht duhet të digjni me akt të procesorit, ndërsa këtu keni djegur të gjithë memories përpara së pritur. Zgjidhja është të reduktoni numrin e alokimeve. 
Një tjetër pjesë e problemit është se, nëse e aktivizoni profiler-in kur është përfunduar banda e memories, pikërisht në momentin kur ndodh, zakonisht prisni që cache të rikthehet, sepse është plot me plehra që sapo keni prodhuar, me të gjitha këto rreshta. Prandaj, çdo operacion load ose store bëhet i ngadalshëm, sepse ato sjellin humbje në cache – e gjithë cache ka ulet ngadalë, duke pritur që plehrat të dalin. Prandaj, profiler-i do të tregojë vetëm një zhurmë të ngrohtë rastësore, të shtrirë përgjatë gjithçkaje – nuk do të ketë ndonjë urdhër të veçantë të nxehtë ose vend në kod. Vetëm zhurmë. Dhe nëse shihni ciklet GC, ato do të jenë të gjitha për Generacionin e Ri dhe shumë të shpejta – mikrosekuonda ose milisekonda maksimumi. Sepse e gjithë kjo memory vdes menjëherë. Ju alokoni miliarda gigabajt, dhe ai i shkurton, dhe i shkurton, dhe përsëri i shkurton. E gjithë kjo ndodh shumë shpejt. Pra, ka cikle të lira GC, zhurmë të ngrohtë përgjatë gjithçkaje, por ne duam të arrijmë një përshpejtim 5-fish. Në këtë moment diçka duhet të lidhet në mendjen tuaj dhe të dëgjojë: ‘pse kështu?!’. Teqja e rripit të memories nuk shfaqet në debugger-in klasik, duhet të aktivizoni debugger-in e shifruesve të harduerit e të performance dhe ta shihni atë vetë dhe drejtpërdrejt. Ndryshe, mund të dyshoni për këtë nga këto tri simptoma. Simptoma e tretë është kur shihni atë që alokoni, pyesni profiler-in, dhe ai përgjigjet: ‘Ju keni bërë miliardë rreshta, por GC ka vepruar falas’. Sapo ndodh kjo, e kuptoni se keni krijuar shumë objekte dhe keni djegur të gjithë shiritin e memories. Ka një mënyrë për ta zbuluar këtë, por nuk është e qartë. 

Problemi është në strukturën e të dhënave: struktura e hollë, që qëndron pas gjithçkaje, është shumë e madhe, është 2.7G në disk, prandaj të bësh një kopje të kësaj gjëje është shumë e padëshirueshme – dëshiron ta ngarkosh direkt nga bufri i bytes në regjistrat, në mënyrë që të mos lexosh-shkruash në varg çdo herë pesë herë. Fatkeqësisht, Java, në mënyrë të paracaktuar, nuk të jep një bibliotekë të tillë si pjesë e JDK. Por kjo është thjesht, apo jo? Në thelb, janë 5-10 rreshta kodi që do të shkojnë për implementimin e një ngarkuesi të vet, i cili përsërit sjelljen e klasës së vargjeve, duke qenë një mbështjellës mbi bufret e bytes në nivelin e poshtëm. Si rezultat, rezulton se punon pothuajse si me vargje, por në të vërtetë, aty lëvizin treguesit në bufër, dhe bytes e papërpunuara nuk kopjohen askund, dhe kështu ripërdoren të njëjtat bufe, herë pas here, dhe sistemi operacional është i lumtur të marrë përsipër gjërat për të cilat është e destinuar, siç është dyfishimi i padukshëm i këtyre bufreve të bytes, ndërsa ti vetë nuk merresh më me një rrjedhë të pafund të të dhënave të panevojshme. Për rastin, ju e kuptoni, gjatë punës me GC garanton që çdo ndarje e memories nuk do të shihet nga procesori pas ciklit të fundit të GC? Prandaj, e gjithë kjo nuk mund të jetë në cache, dhe më pas ndodh një humbje e garantuar 100%. Gjatë punës me një tregues, në x86, të lexosh një regjistër nga memoria merr 1-2 cikle, dhe sa herë që ndodh kjo, ti paguan, paguan, paguan, sepse e gjithë memoria është NINE caches – dhe kjo është kostumi i ndarjes së memories. Kostumi i vërtetë.

Me thënë, strukturat e të dhënave janë ato që është më e vështirë të ndryshosh. Dhe sapo kuptoni se keni zgjedhur strukturën e gabuar të të dhënave që më vonë do të dëmtojë performancën, zakonisht kërkohet një punë e madhe për t'u rikthyer, por nëse nuk e bëni këtë, situata do të përkeqësohet. Së pari, duhet të mendoni për strukturat e të dhënave, kjo është e rëndësishme. Kostoja kryesore këtu bie mbi strukturat e mëdha të të dhënave, të cilat fillojnë të përdoren në stilin 'unë kopjova strukturën e të dhënave X në strukturën e të dhënave Y, sepse Y më pëlqen më shumë në formë'. Por operacioni i kopjimit (i cili duket i lirë) në të vërtetë shpenzon hapësirën në kujtesë dhe këtu është ku humbet gjithçka në kohë ekzekutimi. Nëse kam një varg të madh me JSON dhe dua ta shndërroj atë në një pemë DOM të strukturuar nga POJO ose diçka e tillë, operacioni i analizimit të këtij vargu dhe ndërtimi të POJO dhe më pas një shfrytëzim të ri të POJO në vazhdim do të kthehen në një kosto shtesë - një gjë e shtrenjtë. Me përjashtim të rastit kur do të lëvizni përmes POJO shumë më shpesh sesa përmes vargut. Në vend të kësaj, mund të provoni të deshifroni vargun dhe të nxirrni vetëm atë që ju nevojitet, pa e shndërruar në asnjë POJO. Nëse e gjithë kjo ndodh në një rrugë, nga e cila kërkohet performanca maksimale, asnjë POJO - duhet të hidhni diku drejt vargut.

Why create your own programming language

Andrey: Ju thatë se për të kuptuar modelin e kostos, duhet të shkruani një gjuhë të vogël…

Cliff: Jo një gjuhë, por një kompilator. Gjuha dhe kompileri janë gjëra të ndryshme. Dallimi më i rëndësishëm është në mendjen tuaj. 

Andrey: Përsa i përket, sa di unë, po eksperimentoni me krijimin e gjuhëve tuaja. Pse?

Cliff: Sepse mundem! Jam gjysmë në pension, kështu që kjo është hobiu im. Kam realizuar gjuhë të huaja gjatë gjithë jetës sime. Kam punuar shumë edhe mbi stilin e kodimit. Po ashtu, shoh probleme në gjuhët e tjera. Shoh se ka mënyra më të mira për të bërë gjëra të zakonshme. Dhe do t'i shfrytëzoja ato. Thjesht jam i lodhur nga problemet në vetvete, në Java, në Python, në çdo gjuhë tjetër. Tani po shkruaj në React Native, JavaScript dhe Elm si një hob, i cili nuk ka të bëjë me pensionin, por me punë aktive. Po ashtu shkruaj edhe në Python dhe ndoshta do të vazhdoj të punoj mbi mësimin e makinerive për backend-et Java. Ka shumë gjuhë të njohura dhe secila ka veçoritë e saj interesante. Çdo njëra është e mirë për diçka dhe mund të provosh të bashkosh të gjitha këto karakteristika. Pra, merrem me studimin e gjërave interesante për mua, sjelljes së gjuhës, përpiqem të shpik një semantikë të arsyeshme. Deri tani po arrij! Aktualisht po luftoj me semantikën e memories, sepse dëshiroj ta kem si në C dhe Java, dhe të kem një model të fortë memorje dhe semantikë për ngarkesat dhe dyqanet. Pas kësaj, të kem automatikisht nxjerrjen e tipave si në Haskell. Kështu, po përpiqem të përziej nxjerrjen e tipave të ngjashme me Haskell me një memorie që punon si në C dhe Java. Këtë e kam bërë për 2-3 muajt e fundit, për shembull.

Andrey: Nëse po ndërtoni një gjuhë që merr aspektet më të mira nga gjuhë të tjera, keni menduar ndonjëherë se dikush do të bëjë të kundërtën: do të marrë idetë tuaja dhe do t'i përdorë për vete?

Cliff: Ky është mënyra se si lindin 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 aty sigurinë e tipit, kontrollin e kufijve të masivave, GC, dhe përmirësuan disa gjëra nga C. Ata shtuan të tyret. Por ata ishin shumë të frymëzuar, apo jo? Të gjithë qëndrojnë mbi supet e gjigantëve që ishin para teje – kështu bëhet progresi.

Andrey: Sa kuptoj, gjuha juaj do të jetë e sigurt në lidhje me përdorimin e memories. Keni menduar të realizoni diçka si borrow checker nga Rust? E keni parë atë, si ju duket?

Cliff: Kam, unë kam shkruar në C për një kohë të gjatë, me gjithë këto malloc dhe free, dhe menaxhoj manualisht kohëzgjatjen. E dini, 90-95% e menaxhimit manual të kohëzgjatjes ka një strukturë të njëjtë. Dhe është shumë, shumë e dhimbshme të merresh me këtë manualisht. Do të doja që kompajleri thjesht të tregonte se çfarë ndodh dhe çfarë arrin me veprimet e tua. Për disa gjëra, borrow checker e bën këtë automatikisht. Dhe ai duhet gjithashtu të nxjerrë automatikisht informacione, të kuptojë gjithçka dhe madje të mos më ngarkojë me detyrën e shpjegimit të atij kuptimi. Ai duhet të bëjë të paktën një analizë lokale të arratisjes, dhe vetëm nëse nuk ia del, atëherë duhet të shtosh annotationet e tipeve që do të përshkruanin kohëzgjatjen – dhe një skemë e tillë është shumë më e komplikuar se borrow checker, ose ndonjë checker të ekzistueshëm të memories. Zgjedhja mes 'gjithçka është në rregull' dhe 'nuk kuptova asgjë' – jo, duhet të ketë diçka më të mirë. 
Prandaj, si një person që ka shkruar shumë kod në C, mendoj se mbështetja për menaxhimin automatik të jetës së resurseve është gjëja më e rëndësishme. Gjithashtu, më ka irrituar sa shumë Java përdor memorie, dhe ankesa kryesore është në GC. Kur alokoni memorie në Java, memoria që ka qenë lokale në ciklin e fundit të GC nuk kthehet. Në gjuhët me menaxhim më të saktë të memorjes, kjo nuk ndodh. Nëse thërrisni malloc, merrni menjëherë memorie që zakonisht sapo është përdorur. Zakonisht, ju bëni disa gjëra të përkohshme me memorien dhe menjëherë e ktheni atë. Dhe ajo kthehet menjëherë përsëri në pool-in e malloc, dhe cikli i ardhshëm i malloc e nxjerr atë jashtë përsëri. Prandaj, përdorimi i vërtetë i memorjes ulët në setin e objekteve të gjallë në një moment të caktuar, plus rrjedhjet. Dhe nëse nuk keni rrjedhje në një mënyrë krejt të papërshtatshme, pjesa më e madhe e memorjes mbetet në cache dhe procesorë, dhe kjo punon shpejt. Por kërkon shumë menaxhim manual të memorjes me malloc dhe free, të thërritura në rendin e duhur, në vendin e duhur. Rust mund ta menaxhojë këtë vetë siç duhet dhe në shumë raste ofron madje më shumë performancë, pasi përdorimi i memorjes zvogëlohet vetëm në llogaritjet aktuale – në vend të pritjes për ciklin e ardhshëm të GC, i cili do të lirojë memorien. Në fund, arritëm një mënyrë shumë interesante për të përmirësuar performancën. Dhe mjaft të fuqishme – në kuptimin, kam punuar me këto gjëra në përpunimin e të dhënave për fintech, dhe kjo ka ofruar një përparim rreth pesë herë. Ky është një përparim i madh, sidomos në botën ku procesorët nuk po bëhen më të shpejtë, dhe ne ende vazhdojmë të presim përmirësime.

Career of a performance engineer

Andrey: Gjithashtu, do të doja të pyesja për karrierën në përgjithësi. Keni qënë të njohur për punën tuaj në JIT në HotSpot, e më pas u sistemuat në Azul – dhe kjo është gjithashtu një kompani JVM. Por tani më shumë jeni marrë me harduerin se sa me softuerin. Më pas, papritur u kaluat në Big Data dhe Machine Learning, e më pas në zbulimin e mashtrimeve. Si ndodhi kjo? Këto janë fusha shumë të ndryshme të zhvillimit.

Cliff: Kam unë kam bërë programim për një kohë të gjatë dhe kam pasur mundësi të merrem me aktivitete shumë të ndryshme. Dhe kur njerëzit thonë: «O, ti je ai që bëri JIT për Java!», gjithmonë është argëtuese. Por përpara se të lidhesha me këtë, kam punuar mbi një klon të PostScript, gjuhës që Apple e përdorte dikur për printerët e saj lazer. Para kësaj, kam realizuar gjuhën Forth. Mendoj se tema kryesore për mua është zhvillimi i mjeteve. Gjatë gjithë jetës time, kam krijuar mjete me të cilat njerëzit e tjerë shkruajnë programet e tyre të shkëlqyera. Por kam qenë i angazhuar edhe në zhvillimin e sistemeve operative, drejtuesve, debugger-it të nivelit të bërthamës, gjuhëve për zhvillimin e OS-ve, të cilat filluan thjesht, por me kalimin e kohës u bëheshin gjithnjë e më të ndërlikuara. Por tema kryesore, ndonëse, mbetet zhvillimi i mjeteve. Një pjesë e madhe e jetës time ka kaluar midis Azul dhe Sun, dhe ajo kishte të bëjë me Java. Por kur fillova të merrem me Big Data dhe Machine Learning, e veshja përsëri kapelen time të veçantë dhe thashë: «Ah, tani kemi një problem të rëndësishëm, dhe këtu po ndodhin shumë gjëra interesante dhe njerëz që bëjnë diçka». Ky është një rrugë e shkëlqyer për t'u zhvilluar, e cila ia vlen të ecet.

Po, gjatë jetës time kam pasur një pasion të madh për llogaritë e shpërndara. Puna ime e parë ishte gjatë studentëve, duke punuar me C për një projekt reklamash. Kjo ishin llogari të shpërndara në çipat Zilog Z80, të cilët mbledhin të dhëna për njohjen optike të tekstit, duke u kryer nga një analizues analogjik. Ishte një temë e mrekullueshme dhe krejt e çuditshme. Megjithatë, kishte probleme, ndonjë pjesë nuk interpretohej siç duhej, prandaj ishte e nevojshme që të merrnim imazhin dhe t'ia tregonim një njeriu që e kishte lexuar me sytë e tij dhe që informonte se çfarë thoshte teksti, dhe kështu kishte punë me të dhëna, dhe këto punë kishin gjuhën e tyre. Kishte një backend që e përpunonte gjithçka – Z80 që punonin paralelisht me terminalet vt100 – një për çdo person, dhe kishte një model programimi paralel në Z80. Një pjesë e përbashkët memorie që ndante të gjitha Z80 brenda një konfigurimi të tipit “yll”; ndarja e backplane dhe gjysma e RAM-it ndante brenda rrjetit, dhe gjysma tjetër ishte private ose shkonte diku tjetër. Një sistem paralel dhe të shpërndarë mjaft kompleks me memorie të… gjysmë të ndarë. Kur u ndodhi… Nuk e mbaj mend saktësisht, diku në mes të viteve 80. Mjaft kohë më parë. 
Po, le të themi se 30 vjet janë mjaft kohë më parë. Problemet që lidhen me llogaritë e shpërndara ekzistojnë prej një kohe të konsiderueshme, njerëzit për një kohë të gjatë kanë luftuar me to. Beowulf-klasteret. Këto klastere duken si... P.sh.: ka Ethernet dhe procesori yt i shpejtë x86 është lidhur me këtë Ethernet, dhe tani dëshiron të marrësh memorien e përbashkët fake, sepse askush nuk mund të merrej me kodimin e llogaritjeve të shpërndara, ishte shumë e komplikuar dhe për këtë arsye kishte memorien e përbashkët fake me mbrojtjen e faqeve të memories në x86, dhe nëse shkruaje në këtë faqe, ne u thoshim procesorëve të tjerë që nëse ata do të kishin qasje në të njëjtën memorie të përbashkët, do të duhej ta ngarkonin nga ti, dhe kështu lindi diçka si një protokoll mbështetje për koherencën e caches dhe software për këtë. Një koncept interesant. Problemi i vërtetë, natyrisht, ishte ndryshe. E gjithë kjo funksiononte, por ti shpejt kishe probleme me performancën, sepse askush nuk e kuptonte modelin e performancës në një nivel të mjaftueshëm të mirë – cilat ishin ato modele të qasjes në memorie, si të bëje që nodet të mos pingonin pafund njëra-tjetrën, etj.

Në H2O kam menduar kështu: zhvilluesit vetë janë përgjegjës për të përcaktuar se ku është paralelizmi dhe ku nuk ka. Kam krijuar një model kodimi që e bën të lehtë dhe të thjeshtë shkruajnë kod me performancë të lartë. Ndërsa të shkruash kod që punon ngadalë është e vështirë; ai do të duket keq. Duhet të përpiqesh seriozisht për të shkruar kod të ngadalshëm, do të duhet të përdorësh metoda të natyrshme. Kodi që pengon është i dukshëm që në shikim të parë. Si pasojë, zakonisht shkruhet kod që punon shpejt, por ju duhen të kuptoni se çfarë të bëni në rast të memories së ndarë. Të gjitha këto janë të lidhura me masa të mëdha dhe sjellja aty është e ngjashme me masat e mëdha jo volatile në Java paralel. Për shembull, imagjinoni se dy rrjedha shkruajnë në një masë paralel, njëra fiton dhe tjetra, përkatësisht, humbet, dhe nuk dini se cila është cila. Nëse ato nuk janë volatile, rendi mund të jetë çfarëdo - dhe kjo funksionon me të vërtetë mirë. Njerëzit vërtetë shqetësohen për rendin e operacioneve, ata rendisin siç duhet volatile dhe në vendet e duhura presin probleme me performancën që lidhen me memorien. Në të kundërt, ata do të shkruanin thjesht kod si cikle nga 1 në N, ku N është disa triliona, duke shpresuar se të gjitha rastet komplekse do të bëhen automatikisht të paralelizuara - dhe atje kjo nuk funksionon. Por në H2O nuk është as Java dhe as Scala, mund ta quani këtë "Java minus minus" nëse dëshironi. Ky është një stil programimi shumë i kuptueshëm dhe është i ngjashëm me shkruarjen e kodit të thjeshtë në C ose Java me cikle dhe masa. Por, edhe kësaj mund të trajtojmë terabajtë memorjeje. Unë ende përdor H2O. Herë pas here e përdor në projekte të ndryshme - dhe ajo ende është gjëja më e shpejtë, me dhjetëra herë përpara konkurrentëve. Nëse po bëni Big Data me të dhëna kolonore, është shumë e vështirë të kaloni H2O.

Technical challenges

Andrey: Cila ka qenë sfida më e madhe gjatë gjithë karrierës suaj?

Cliff: Po diskutojmë pjesën teknike apo jo teknike të çështjes? Do të thoja, sfidat më të mëdha janë ato jo teknike. 
Sa ndihmuar në sfidat teknike. Unë thjesht i kam mposhtur ato. Nuk e di se cila ka qenë më e madhja, por kishte disa mjaft interesante që morën shumë kohë dhe luftë mendore. Kur shkova në Sun, isha i sigurt se do të bëja një kompilator të shpejtë, ndërsa një grup seniorësh në përgjigje thoshin se nuk do të mundja kurrë. Por unë vazhdova në këtë rrugë, shkrova një kompilator deri në alokimin e regjistrave dhe ishte mjaft i shpejtë. Ai ishte aq i shpejtë sa kompajleri modern C1, por atëherë alokatori ishte shumë më i ngadaltë, dhe duke e parë pas, kjo ishte një problem që kishte një strukturë të madhe të dhënash. Më nevojitej për të shkruar një alokator grafik regjistrash dhe nuk e kuptoja dilemën ndërmjet shprehshmërisë së kodit dhe shpejtësisë, e cila ekzistonte atëherë dhe ishte shumë e rëndësishme. Doli që struktura e të dhënave zakonisht e tejkalonte madhësinë e cache-it në x86-të e asaj kohe dhe prandaj, nëse fillimisht supozova se alokatori i regjistrave do të punonte 5-10 për qind të gjithë kohës së jitimit, në realitet kjo rezultoi në 50 për qind.

Koha kalonte, kompajleri po bëhej gjithnjë e më i qartë dhe produktiv, ndaloi së gjeneruari kod të tmerrshëm më shpesh, dhe performanca filloi të ngjajë më shumë me atë që jep kompajleri C. Nëse, sigurisht, nuk po shkruaje ndonjë gjë të keqe, që madje as C nuk e përshpejton. Nëse shkruan kod si në C, merr performancë si në C në shumë më shumë raste. Dhe sa më shumë kalonte koha, aq më shpesh del kodi, asimptotikisht përputhës me nivelin C, alokatori i regjistrave filloi të ngjajë me diçka të përfunduar… pavarësisht nëse kodi yt punon shpejt apo ngadalë. Unë vazhdoja të punoja mbi alokatorin për ta bërë atë që të bënte alokime më të mira. Ai po bëhej gjithnjë e më i ngadaltë, por po siguronte performancë gjithnjë e më të mirë në ato raste kur askush tjetër nuk arrinte. Munda të zhytem në alokatorin e regjistrave, të thelloj një muaj punë atje, dhe papritmas i gjithë kodi fillonte të ekzekutohej 5% më shpejt. Kjo ndodhte herë pas here dhe alokatori i regjistrave u bë diçka si një vepër arti – të gjithë e donin apo e urrejnin atë, dhe njerëzit nga akademia bënin pyetje si "përse gjithçka bëhet pikërisht kështu", pse jo skanimi linear, dhe si ndryshon ajo. Përtëritja është e njëjtë: një alokator i bazuar në ngjyrosjen e grafit plus një menaxhim shumë të kujdesshëm të kodit tampon i barabartë me një armë fitoreje, kombinimi më i mirë që askush nuk mund ta mposht. Dhe kjo është një gjë mjaft e papritur. Të gjitha gjërat e tjera që bën kompiler është mjaft të njohura, megjithatë janë çuar në një nivel arti. Unë gjithmonë kam bërë gjëra që duhet të kthenin kompilerin në një vepër arti. Por asgjë nga këto nuk ishte diçka jashtëzakonisht të jashtme – përveç alokatorit të regjistrave. Truku është se duhet të jesh shumë i kujdesshëm të presësh në ngarkesë dhe, nëse ndodh kjo (mund të shpjegoj më hollësisht, nëse është e interesuar), kjo do të thotë se mund të in-line më agresivisht, pa rrezik të kalosh përtej thyerjes së grafikut të performancës. Në ato kohë ka pasur shumë kompilerë të plotë, të mbushur me zbukurime dhe rrotulla, në të cilat kishte alokatorë regjistrash, por askush tjetër nuk mundi ta bëjë më.

Problemi është se, nëse shton metodat që nënshkruhen për inlining, duke rritur vazhdimisht fushën e inlining, grupi i vlerave të përdorura menjëherë tejkalon numrin e regjistrave, dhe duhet të spillohet. Niveli kritik zakonisht ndodh kur alokatori dorëzohet, dhe një kandidat i mirë për spilling vlen aq sa një tjetër, dhe spillon disa gjëra krejtësisht të çmendura. Vlera e inlining është se humbasin një pjesë të overhead-it, overhead-it të thirrjes dhe ruajtjes, mund të shohësh vlerat brenda dhe mund t'i optimizosh më tej. Kostumi i inlining është se krijohet një numër i madh i vlerave aktive, dhe nëse alokatori yt i regjistrave spillon më shumë se sa duhet, humb menjëherë. Prandaj, shumica e alokatorëve kanë një problem: kur inlining kalon një pikë të caktuar, fillon të spillohet gjithçka dhe performanca mund të bjerë në ujë të ndotur. Ata që realizojnë një kompilator shtojnë disa heuristika: për shembull, për të ndaluar inlining, duke filluar nga një madhësi mjaft e madhe, pasi alokimet e shkatërrojnë gjithçka. Kështu krijohet një thyerje në grafikun e performancës – ke inlinuar, inlinuar, performanca rritet ngadalë – dhe pastaj hop! – bie poshtë me një kthesë të shpejtë, sepse ke inlinuar shumë. Kështu ka ndodhur deri në ardhjen e Java. Java kërkon shumë më tepër inlining, prandaj më është dashur të bëj alokatorin tim më agresiv, në mënyrë që të rregullohej dhe të mos binte, dhe nëse ke inlinuar shumë – ai fillon të spillohet, por më vonë gjithsesi vjen një moment kur 'nuk ka më spilling'. Kjo është një vëzhgim interesant dhe më erdhi thjesht nga asgjë, e paqartë, por e mirëpaguar. Më ndoqi inlining agresiv dhe kjo më çoi në vende ku performanca e Java dhe C shkon krahas. Ato janë me të vërtetë të afërta – mund të shkruaj kod në Java që do të jetë shumë më i shpejtë se kodi në C dhe kështu me radhë, por në të mesme, në pamjen e madhe të gjërave, ato janë afërsisht të krahasueshme. Mendoj se pjesa e këtij meriti është alokatori i regjistrave, i cili më lejon të inlinoj në mënyrë maksimalisht të thjeshtë. Unë thjesht inlinoj gjithçka që shoh. Pyetja këtu është nëse alokatori punon mirë, nëse rezultati është një kod funkcional. Ky ka qenë një sfidë e madhe: të kuptoj gjithçka këtë dhe ta bëj atë të funksionojë.

A bit about register allocation and multithreading

Vladimir: Problemet si alokimi i regjistrave duket një temë e pafund. A është ndodhur ndonjëherë që një ide të duket premtuese, por më pas të dështojë në praktikë?

Cliff: Sigurisht! Alokimi i regjistrave është një fushë ku për të zgjidhur një problem NP-të plotë, përpiqesh të gjesh disa heuristika. Dhe asnjëherë nuk mund të arrish një zgjidhje perfekte, apo jo? Është thjesht e pamundur. Shihni, kompaktimi i Paraprakisht – ai gjithashtu punon keq. Biseda këtu është për raste të zakonshme. Për performancën tipike, kështu që mund të shkosh dhe të matësh diçka që mendon se është një performancë tipike e mirë – përfundimisht, ti po punon për ta përmirësuar atë! Alokimi i regjistrave është një temë krejtësisht e dedikuar performancës. Sapo të kesh prototipin e parë, ai funksionon dhe ngjyros atë që duhet, fillon puna për performancën. Duhet të mësosh të matesh mirë. Pse është kaq e rëndësishme? Nëse ka të dhëna të qarta, mund të shohësh pjesë të ndryshme dhe të shohësh: aha, kjo ndihmoi këtu, por aty gjërat shkuan keq! Shfaqen disa ide të mira, shton një heuristik të re dhe papritmas gjithçka fillon të funksionojë pak më mirë mesatarisht. Ose ndoshta jo. Kam pasur shumë raste kur kemi luftuar për pesë përqind të performancës që e ndanë zhvillimin tonë nga alokatori i mëparshëm. Dhe çdo herë duket kështu: diku fitove, diku humbe. Nëse ke mjete të mira për analizën e performancës, mund të gjesh idetë që humbin dhe të kuptosh pse ato humbin. Ndoshta ia vlen të lësh gjithçka siç është, ose ndoshta të merresh seriozisht me përshtatjen fine, ose të shkosh dhe të rregullosh diçka tjetër. Kjo është një shumëllojshmëri e madhe! Kam bërë këtë haks të mrekullueshëm, por më nevojitet edhe ky, ky, dhe ky – dhe gjithashtu kombinimi i tyre jep disa përmirësime. Ndërsa të vetmet mund të dështojnë. Kjo është natyra e punës në problemet NP-të plotë të performancës.

Vladimir: Duke parë situatën, duket se gjërat si ngjyrimi në alokatorët janë një problem tashmë i zgjidhur. Pra, për ju të zgjidhur, gjykuar nga ajo që thoni, atëherë a ka ndonjë kuptim…

Cliff: Ajo nuk është zgjidhur si e tillë. Ti duhet ta kthesh atë në «të zgjidhur». Ka detyra të vështira dhe ato duhen zgjidhur. Kur kjo është bërë, vjen koha për të punuar mbi performancën. Kjo punë duhet të merret seriozisht – të kryhen benchmark, të mblidhen metrika, të shpjegohen situatat kur rikthimi në versionin e mëparshëm bën që haku yt i vjetër të fillojë të funksionojë përsëri (apo anasjelltas, të ndalojë). Dhe mos u dorëzo derisa të arrish diçka. Siç kam thënë, nëse ide të shkëlqyera që nuk funksionuan, por në fushën e alokimit të regjistrave ka ide pothuajse të pafund. Mund, për shembull, të lexosh publikime shkencore. Megjithatë, tani kjo fushë ka filluar të lëvizë më ngadalë dhe është bërë më e qartë se në ditët e saj të reja. Megjithatë, në këtë fushë punon një pafundësi njerëzish dhe të gjitha idetë e tyre meritojnë të provohen, të gjithë po presin momentin e tyre. Dhe ti nuk mund të thua sa të mira janë ato, nëse nuk provon. Sa mirë integrohen me gjithçka tjetër në alokuesin tënd, sepse alokuesi bën shumë gjëra, dhe disa ide në alokuesin tënd specifik nuk do të funksionojnë, ndërsa në një alokues tjetër – lehtësisht. Mënyra primare e fitores për alokuesin është të nxjerrë gjërat e ngadalta jashtë rrugës kryesore dhe të detyruar të bëjë ndarje përgjatë kufijve të rrugëve të ngadalta. Prandaj, nëse dëshiron të nisësh GC, të shkosh në rrugën e ngadalshme, të deoptimizohesh, të hedhësh një përjashtim, gjithçka në atë frymë – di se këto gjëra janë relativisht të rralla. Dhe ato janë vërtet të rralla, e kam provuar. Bën punë shtesë dhe për këtë arsye eliminon shumë kufizime në këto rrugë të ngadalta, por kjo nuk është shumë e rëndësishme, sepse ato janë të ngadalta dhe rrallë shihen. Për shembull, treguesi zero – ai kurrë nuk ndodh, apo jo? Duhet të kesh disa rrugë për gjëra të ndryshme, por ato nuk duhet të përzihen në të parën. 

Vladimir: Çfarë mendoni për shumënukësinë, kur ka mijëra bërthama? A është një gjë e dobishme?

Cliff: Suksesi i GPU tregon se është mjaft i dobishëm!

Vladimir: Ata janë mjaft të specializuar. E çfarë për procesorët e përgjithshëm?

Cliff: E, kjo ishte modeli i biznesit të Azul. Përgjigjja erdhi në një epokë kur njerëzit e donin shumë performancën e parashikueshme. Atëherë ishte e vështirë të shkruaje kod paralel. Modeli i kodimit H2O shkallëzohet mirë, por nuk është një model me qëllim të përgjithshëm. Ndryshe nga ai që përdor GPU-në. Po flasim për kompleksitetin e zhvillimit të një gjëje të tillë apo për kompleksitetin e përdorimit të saj? Për shembull, një mësim interesant më jep Azul, mjaft i padukshëm: caches të vogla janë të pranueshme. 

The biggest challenge in life

Vladimir: Çfarë për sfidat jo teknike?

Cliff: Sfida më e madhe ishte të mos isha... i mirë dhe i dashur me njerëzit. Dhe si pasojë, unë u gjenda vazhdimisht në situata ekstremisht konfliktuale. Situata ku dija se çdo gjë po shkonte keq, por nuk dija si të ecja përpara në zgjidhjen e këtyre problemeve dhe nuk mund të merresha me to. Mjaft probleme të vazhdueshme, që zgjatën gjatë dhjetëra vjetëve, u shfaqën saktësisht në këtë mënyrë. E vërteta që në Java ka kompilatorë C1 dhe C2 është një pasojë e drejtpërdrejtë e kësaj. Fakti që në Java për një dekadë nuk kishte kompilim shumë-niveli është gjithashtu një pasojë e drejtpërdrejtë. Sigurisht, na duhej një sistem i tillë, por nuk është e qartë pse nuk kishte. Kam pasur probleme me një inxhinier... ose grup inxhinierësh. Disa kohë më parë, kur fillova punën në Sun, isha... Mirë, jo vetëm atëherë, kam gjithmonë një mendim për gjithçka. Dhe e mendoja se është një e vërtetë që mund të marrësh këtë të vërtetë time dhe ta tregosh hapur. Për më tepër, isha shokueshëm i saktë pjesën më të madhe të kohës. Dhe nëse një qasje e tillë nuk të pëlqen... sidomos nëse është e qartë që je në gabim dhe po bën gjëra të padobishme... Në përgjithësi, shumë pak njerëz mund të përballonin një formë komunikimi të tillë me tolerancë. Megjithatë disa mundeshin, për shembull, unë. E kam ndërtuar tërë jetën time mbi parimet meritokratike. Nëse më tregon diçka të gabuar, unë menjëherë do të kthehem dhe do të them: ke thënë një budallallëk. Në të njëjtën kohë, sigurisht, kërkoj falje dhe gjëra të tjera të tilla, do të theksoj meritat, nëse ka ndonjë që ekziston, dhe do të bëj veprime të tjera të duhura. Nga ana tjetër, unë jam shokueshëm i saktë një përqindje shokueshëm të madhe të kohës së përgjithshme. Dhe kjo nuk funksionon shumë mirë në marrëdhënie me njerëzit. Unë nuk përpiqem të jem i këndshëm, por e bëj çështjen të qartë. “Kjo nuk do të funksionojë kurrë, sepse një, dy dhe tre”. Dhe ata janë si: “Oh!”. Kishin edhe pasoja të tjera, të cilat ndoshta do ishte më mirë t’i kalonim: për shembull, ato që çuan në divorcin nga bashkëshortja dhe dhjetë vjet depresion pas kësaj.

Sfida është një betejë me njerëzit, me perceptimin e tyre për atë që ti mund ose nuk mund të bësh, çfarë është e rëndësishme dhe çfarë nuk është. Ka pasur shumë sfida për stilin e kodimit. Unë akoma shkruaj shumë kod, dhe në ato kohëra më duhej madje të ngadalësohesha, sepse bëja shumë detyra paralelisht dhe i bëja ato keq, në vend që të fokusoja në njërën e vetme. Kur e shoh pas, kam shkruar gjysmën e kodit të ekipit Java JIT, ekipit C2. Programuesi tjetër më i shpejtë shkruante me gjysmë më ngadalë, ai pas tij - akoma më ngadalë dhe kjo ishte një rënie eksponenciale. Personi i shtatë në këtë rresht ishte shumë, shumë ngadalë - kështu ka ndodhur gjithmonë! Unë e kam prekur një sërë kodi. Kam parë se kush shkruan çfarë, pa përjashtim, e kam parë kodin e tyre, kam bërë rishikime për secilin prej tyre, dhe akoma kam vazhduar të shkruaj më shumë se çdo njeri tjetër. Me njerëzit ky qasje nuk funksionon shumë mirë. Disa nuk e pëlqejnë atë. Dhe kur ata nuk janë në gjendje të përballen me këtë, fillojnë të gjitha llojet e ankesave. Për shembull, një herë më thanë të ndalojë së shkruari kod, sepse shkruaj tepër shumë kod, dhe kjo rrezikon ekipin, dhe për mua e gjithë kjo tingëllonte si një shaka: çuna, nëse e gjithë pjesa tjetër e ekipit zhduket, dhe unë vazhdoj të shkruaj kod, do të humbni vetëm gjysmën e ekipit. Nga ana tjetër, nëse unë vazhdoj të shkruaj kod dhe ju humbni gjysmën e ekipit - kjo tingëllon si menaxhim shumë i keq. Unë kurrë nuk kam menduar shumë për këtë, kurrë nuk kam folur për këtë, por gjithsesi ka qenë diku në mendjen time. Në thellësi të mendjeve tona, kishte një mendim: 'A po humoroni?'. 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ë udhëheqës ekipi për programuesit për një kohë të gjatë, dhe tani u them njerëzve të tjerë: e di, jam kështu siç jam, dhe do të duhet të përballeni me mua - a është në rregull nëse qëndroj këtu? Dhe kur ata filluan të përballen me këtë, gjithçka funksionoi. Sepse, në fakt, nuk jam as i keq, as i mirë, nuk kam ndonjë qëllim të keq ose ambicie egoiste, kjo është thjesht natyra ime dhe duhet të jetojmë me këtë.

Andrey: Së fundmi, të gjithë filluan të flasin për vetëdijen për introvertët, dhe në përgjithësi për aftësitë e buta. Çfarë mund të thuhet për këtë?

Cliff: Po, ishte një kuptim dhe një mësim që nxora nga divorcimi me gruan. Ajo që nxora nga divorci është kuptimi i vetes. Kështu fillova të kuptoj edhe të tjerët. Për të kuptuar se si funksionon kjo ndërveprimin. Kjo solli zbulime pas zbulimeve. Filloi të kuptohet kush jam dhe çfarë përfaqësoj. Çfarë po bëj: a jam i shqetësuar për detyrën, a po shmang konfliktin, apo diçka tjetër – dhe një nivel i tillë vetëdijesimi në të vërtetë ndihmon të mbash veten nën kontroll. Pas kësaj, gjithçka shkon më lehtë. Një gjë që kam zbuluar jo vetëm te vetja, por edhe te programuesit e tjerë – pamundësia për të verbalizuar mendimet kur je në një gjendje stresi emocional. Për shembull, po kodifikon dhe je në një gjendje fluksi, dhe papritmas vijnë dhe fillojnë të ulërasin në panik se diçka është prishur, dhe tani do të të shqyrtojnë masa ekstreme. Dhe nuk mund të thuash asnjë fjalë, sepse je në një gjendje stresi emocional. Njohuritë e fituara të lejojnë të përgatitesh për atë moment, ta kalosh dhe të kalosh në një plan të tërheqjes, pas së cilës mund të bësh diçka. Pra, po, kur fillon të kuptosh se si funksionon e gjithë kjo – është një ngjarje e madhe, që ndryshon jetën. 
Unë vetë nuk arrita të gjej fjalët e duhura, por mbajta mend radhitjen e veprimeve. Thelbi është se kjo reagim është aq fizik sa është verbalisht, dhe të duhet hapësirë. Një hapësirë e tillë, në kuptimin zen. Pikërisht këtë duhet të shpjegosh, dhe pastaj menjëherë të tërhiqesh në anë – fizikisht të tërhiqesh. Kur hesht fizikisht, mund të përballosh situatën në pjesën e emocioneve. Ndërsa adrenalina arrin në tru, të kalon në modin

Dhe kjo kërkon një hapësirë verbale. Thjesht një hapësirë të lirë. Nëse do flasësh për ndonjë gjë, mund të thua këtë dhe pastaj të shkosh dhe realisht të gjesh "hapësirën": të shkosh për një shëtitje në park, të mbyllesh në dush – nuk ka rëndësi. E rëndësishme është të shkëputesh përkohësisht nga ajo situatë. Sa herë që të shkëputesh për disa sekonda, kontrolli kthehet, fillon të mendosh qartë. "Mirë, nuk jam ndonjë idiot, nuk po bëj gjëra mendjelehta, jam një njeri mjaft i dobishëm". Sa herë që ke arritur ta bindësh veten, është koha të kalosh në hapin tjetër: të kuptosh se çfarë ndodhi. Ti u sulmua, sulmi erdhi nga një vend ku nuk e kishe pritur, ishte një kurth i pabesë e të ulët. Kjo është e keqe. Hapi tjetër – të kuptosh se përse sulmuesi e kishte të domosdoshme këtë. Me të vërtetë, përse? Ndoshta sepse ai vetë ishte në zemërim? Pse ai është në zemërim? Për shembull, ndoshta sepse ai dështoi dhe nuk mund të pranojë përgjegjësinë? Ky është mënyra e duhur për të trajtuar situatën me kujdes. Por për këtë, nevojitet një hapësirë manovrimi, një hapësirë verbale. Hapi i parë më i rëndësishëm – të prishësh kontaktin verbal. Të largohemi nga diskutimi me fjalë. Ta anashkalosh atë, të ikësh sa më shpejt. Nëse është një bisedë telefonike – thjesht vendos telefonin – ky është një aftësi që e kam fituar nga komunikimi me ish-gruan. Nëse biseda nuk sjell asgjë të mirë, thjesht thuaj "mirupafshim" dhe vendos telefonin. Nga ana tjetër e linjës: "bla-bla-bla", ti përgjigjesh: "po, mirupafshim!" dhe e vendos telefonin. Thjesht ndërpres bisedën. Pësë minuta më vonë, kur të kthehet aftësia për të menduar qartë, ti je pak më i qetë, është e mundur të mendosh për atë që ndodhi dhe çfarë do ndodhë më pas. Dhe të fillosh të formosh një përgjigje të menduar, dhe jo thjesht të reagosh emocionalisht. Pë për mua, një përparim në vetëdijesimin ka qenë pikërisht se në rast stresi emocional, unë nuk mund të flas. Të dalësh nga ky gjendje, të mendosh dhe të planifikosh mënyrën si të përgjigjesh dhe si të kompensosh problemet – këto janë hapat e duhur kur nuk mund të flasësh. Mënyra më e thjeshtë – të ikësh nga situata në të cilën shfaqet stresi emocional dhe thjesht të ndalosh të marrësh pjesë në atë stres. Pas kësaj, ti fiton aftësinë të mendosh, kur mund të mendosh, ke mundësinë të flasësh, dhe kështu me radhë.

A thua, në gjyq avokati i palës tjetër përpiqet të të bëjë këtë – tani është e qartë pse. Sepse ai ka mundësinë të të shtypë deri në një gjendje ku nuk mund as të thuash emrin tënd, për shembull. Në kuptimin më të drejtpërdrejtë, nuk do të mundesh të flasësh. Nëse kjo po të ndodh dhe nëse e di se do të jesh në një vend ku fjalët bëhen luftë, në një vend si gjykata, atëherë mund të shkosh me avokatin tënd. Avokati do të të mbrojë dhe do të ndalojë sulmin verbal, dhe do ta bëjë këtë në një mënyrë krejtësisht ligjore, dhe do të rikthesh hapësirën tënde zen të humbur. Për shembull, mua më është dashur disa herë të telefonoj familjen, gjyqtari është marrë me këtë mjaft miqësisht, por avokati i palës tjetër vazhdonte të më kritikonin me zë të lartë, nuk mundesha as të ndërhyja. Në raste të tilla, mënyra më e mirë për mua është përdorimi i një ndërmjetësi. Ndërmjetësi ndalon gjithë këtë presion, që të del me një fluks të vazhdueshëm, ti zbulon hapësirën e nevojshme zen, dhe me të rikthehet aftësia për të folur. Kjo është një fushë e tërë njohurish, në të cilën duhet të mësosh shumë, të zbulohet shumë në brendësi, dhe gjithçka kjo shndërrohet në vendime strategjike të niveleve të larta, të ndryshme për njerëz të ndryshëm. Disa njerëz nuk e kanë këtë problem të përshkruar më lart, zakonisht, ata nuk e kanë ata që profesionistët e shitjeve. Të gjithë ata njerëz që fitojnë jetesën me fjalë – këngëtarë të njohur, poetë, figura të besimit dhe politikanë, ata gjithmonë kanë diçka për të thënë. Ata nuk kanë këto probleme, ndërsa unë i kam.

Andrey: Ishte… befasuese. Po, ne kemi biseduar mjaft dhe është koha ta mbyllim këtë intervistë. Ne patjetër do të takohemi në konferencë dhe do të mund të vazhdojmë këtë dialog. Takohuni në Hydra!

Mund të vazhdohet biseda me Kliffin në konferencën Hydra 2019, e cila do të mbahet më 11-12 korrik 2019 në Shën Petersburg. Ai do të vijë me një prezantim «Përvoja e Memorjes Transactionale të Pajisjeve Azul». Biletat mund të blihen në faqen zyrtare.

Burimi: habr.com

Blini hosting të besueshëm për faqe interneti me mbrojtje nga DDoS, serverë VPS VDS 🔥 Blini hosting të besueshëm për faqe interneti me mbrojtje nga DDoS, serverë VPS VDS | ProHoster