Cliff 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 showed the world that JIT can produce quality code, which was one of the factors that established Java as one of the major modern software platforms. Later, Cliff helped Azul Systems build an 864-core mainframe with software in pure Java that supported GC pauses on a 500-gigabyte heap within 10 milliseconds. In fact, Cliff has worked on all aspects of the JVM.
This Habr post is a comprehensive interview with Cliff. We will discuss the following topics:
- Transitioning to low-level optimizations
- How to perform a major refactoring
- Cost model
- Learning low-level optimizations
- Practical examples of performance improvements
- Why create your own programming language
- The career of a performance engineer
- Technical challenges
- A bit about register allocation and multithreading
- The biggest challenge in life
The interviewers are:
- Andrey Satarin from Amazon Web Services. In his career, he has worked on various projects: testing a distributed NewSQL 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 for ten years — software used by telecommunications operators to automate network management and equipment processes. He is passionate about Java and Oracle Database performance issues. 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 person in the world of JIT compilation, Java, and performance work in general, are you not?
Cliff: That's right!
Andrey: Let's start with some general questions about performance work. What do you think about the choice between high-level and low-level optimizations such as CPU-level work?
Cliff: Siin on kõik lihtne. Kiireim kood on see, mis kunagi ei tööta. Seetõttu tuleb alati alustada kõrge tasemega ja töötada algoritmide kallal. Parema O-märgise ületab halvem O-märgise, juhul kui piisavalt suured muutujad ei sekkuma. Madalat taset käsitletakse viimasena. Tüüpiliselt, kui olete kogu ülejäänud nipi piisavalt hästi optimeerinud ja on endiselt midagi huvitavat – siis on see madal tase. Kuid kuidas alustada kõrge tasemega? Kuidas teada, et olete kõrgel tasemel piisavalt tööd teinud? No... ei kuidagi. Pole valmis retsepte. Tuleb aru saada probleemist, välja selgitada, mida soovite teha (et mitte teha edasisi tarbetuid samme) ja siis saab lahti võtta profilers, mis võib öelda midagi kasulikku. Teatud hetkel mõistate, et olete pääsenud tarbetutest asjadest ning on aeg tegeleda madala taseme täpsustamisega. See on kindlasti eriline kunstivorm. Paljud inimesed teevad tarbetuid asju, kuid edasi liikuda nii kiiresti, et neil pole aega jõudluse pärast muretseda. Aga see on seni, kuni küsimus ei tõuse esile. Tüüpiliselt 99% ajast ei huvita kedagi, millega ma tegelen, kuni kriitilisel teel ei teki olulist asja, mis kellelegi korda läheb. Ja siis kõik hakkavad sinult küsima, „miks see algusest peale ei töötanud täiuslikult”. Kokkuvõttes, alati on midagi, mida jõudluses parandada. Kuid 99% ajast pole sul vihjeid! Sa lihtsalt püüad midagi töötama panna ja selle käigus mõistad, mis on oluline. Kunagi ei saa ette teada, et see konkreetne tükk peab olema ideaalne, seega pead põhimõtteliselt olema ideaalne kõiges. Ja see ei ole võimalik ning sa seda ei tee. Alati on palju asju, mille kallal töötada – ja see on täiesti normaalne.
How to perform a major refactoring
Andrey: Kuidas te töötate jõudluse kallal? See on ju üldine probleem. Näiteks, kas olete pidanud tegelema probleemidega, mis tulenevad suure hulga juba olemasoleva funktsionaalsuse ristumisest?
Cliff: Ma väldin seda. Kui ma tean, et jõudlus saab probleemi põhjuseks, mõtlen ma sellele enne, kui hakkan kodeerima, eriti andmestruktuuride osas. Aga sageli avastad sa kõik need probleemid liiga hilja. Siis tuleb astuda äärmuslikke samme ja teha seda, mida ma nimetan 'ümbersalvestamiseks ja domineerimiseks': tuleb haarata piisavalt suur tükk. Osa koodist tuleb ikkagi ümber kirjutada, olgu põhjuseks jõudluse probleemid või midagi muud. Ükskõik milline ümberkirjutamise põhjus, on peaaegu alati parem ümber kirjutada suurem tükk kui väiksem. Sel hetkel hakkavad kõik kartma: 'oh jumal, ei tohi nii palju koodi puudutada!'. Kuid tegelikult toimib selline lähenemine peaaegu alati palju paremini. Tuleb kohe haarata suure probleemiga, joonistada selle ümber suur ring ja öelda: kõik, mis on ringi sees, ma kirjutan ümber. Piir on ju palju väiksem kui see sisu, mis on selle sees, mis tuleb asendada. Ja kui selline piiride joonistamine võimaldab töö siseselt ideaalselt teha – sul on käed vabad, tee, mida iganes soovid. Kui sa oled aru saanud probleemist, läheb ümberkirjutamise protsess palju sujuvamalt, seega hammusta suur tükk!
Samas, kui teed ümberkirjutamise suure tüki ja mõistad, et jõudlus saab probleemiks, võid kohe hakata selle pärast muretsema. Tavaliselt muutuvad need lihtsad asjad, nagu 'ärge kopeeri andmeid, hallake andmeid võimalikult lihtsalt, tehke neid vähemaks'. Suurtes ümberkirjutamistes on standardseid viise, kuidas jõudlust parandada. Ja need keerlevad peaaegu alati andmete ümber.
Cost model
Andrey: Ühes teie podkastis rääkisite hindamismudelitest jõudluse kontekstis. Kas saaksite selgitada, mida te selle all silmas pidite?
Cliff: Loomulikult. Ma sündisin ajastul, mil protsessorite jõudlus oli äärmiselt oluline. Ja see ajastu naaseb taas – saatusel ei puudunud iroonia. Ma hakkasin elama ajal, mil olid kaheksabitised masinad, minu esimene arvuti töötas 256 baitiga. Just baitidega. Kõik oli väga väike. Pidi lugema käske ja niipea, kui me hakkasime liikuma programmeerimiskeelte virnas ülespoole, hakkasid keeled endale võtmisele üha rohkem ja rohkem. Oli Assembler, siis Basic, siis C, ja C võttis enda alla paljusid detaile, nagu registrite ja käskude jaotamine. Kuid seal oli kõik üsna arusaadav ja kui ma tegin näitaja muutuja eksemplarile, siis sain ma load käsu ja selle käsu hind oli teada. Riistvara annab teada kindla hulga masina tsükleid, seega võis erinevate asjade täitmise kiirus arvutada lihtsalt, liites kõik käsud, millele plaanisin käivitada. Iga compare/test/branch/call/load/store sai kokku liita ja öelda: siin on täitmise aeg. Kui tegid jõudluse parandamise nimel tööd, panid kindlasti tähele, mis numbrid vastavad väikestele kuumadele tsüklitele.
Aga niipea, kui lülitad Java, Python ja sarnaste asjade vahel, kaldud väga kiiresti eemal madalama taseme riistvarast. Mis on Java getter'i kutsumise hind? Kui JIT HotSpot'is kõik õigesti , siis on see load, kuid kui ta ei teinud seda – on see funktsiooni kutsumine. Kuna kutse on kuumas tsüklis, tühistab see kõik muud optimeerimised selles tsüklis. Seega on tegelik hind palju suurem. Ja sa kaotad kohe võime vaadata koodilõiku ja mõista, kui palju meil tasub seda täita protsessori taktsageduse, kasutatava mälu ja vahemälu mõttes. Kõik see muutub huvitavaks, kui tõeliselt süvened jõudlusesse.
Nüüd oleme olukorras, kus protsessorite kiirus pole enam peaaegu kümme aastat kasvanud. Vanad ajad on tagasi! Te ei saa enam loota heale ühetuumalisele jõudlusele. Kuid kui teete paralleelseid arvutusi – see on uskumatult keeruline, kõik vaatavad teid nagu James Bondi. Kümnekordsed kiirused tekivad tavaliselt seal, kus keegi midagi tähelepanuta jättis. Paralleelsus nõuab palju tööd. Kümnekordse kiiruskasvu saavutamiseks peate mõistma kulumudelit. Mida ja kui palju see maksab. Ja selleks peate aru saama, kuidas keel sobib alumisele riistvarale.
Martin Thompson leidis oma blogi jaoks suurepärase sõna. ! On vajalik mõista, mida riistvara plaanib teha, kuidas see täpselt seda teeb ja miks see tegelikult seda teeb. Kasutades seda, on üsna lihtne hakata loendama juhiseid ja aru saama, kuhu täitmise aeg läheb. Kui te pole vastava ettevalmistusega, otsite lihtsalt musta kassi pimedas toas. Ma näen pidevalt inimesi, kes optimeerivad jõudlust, kellel pole vähimatki aimu, mida nad üldse teevad. Nad vaevavad end väga ja ei edene just kuigi palju. Ja kui ma võtan sama koodi ja lisan sinna paar väikest nippi ning saavutavad viiekordse või kümnekordse kiiruskasvu, ütlevad nad: no, see pole aus, me teadsime ju, et sa oled parem. Hämmastav. Millest see jutt on... kulumudelist – see räägib sellest, millist koodi te kirjutate ja kui kiiresti see keskmiselt töötab kogu pildis.
Andrey: Ja kuidas sellist mahtu peas hoida? Kas see saavutatakse suure kogemuse kaudu, või? Kust sellist kogemust saada?
Cliff: Noh, oma kogemuse sain ma mitte kõige lihtsamal viisil. Programmeerisin Assemblers veel siis, kui iga üksiku juhisega sai tutvuda. See kõlab rumalalt, kuid sellest ajast on mul peas, mälus, igaveseks jäänud Z80 juhiste komplekt. Ma ei mäleta inimeste nimesid juba minut pärast vestlust, kuid mäletan 40 aastat tagasi kirjutatud koodi. Huvitav, see näeb välja nagu „».
Learning low-level optimizations
Andrey: Kas on mingi lihtsam viis sisse elada?
Cliff: Jah ja ei. Riistad, mida me kõik kasutame, pole selle aja jooksul kuigi palju muutunud. Kõik kasutavad x86, välja arvatud Armil põhinevad nutitelefonid. Kui sa ei tegele hardcore-embeddediga, on sul kõik sama. Hea, jätkame. Juhised pole samuti sajandeid muutunud. Tuleb minna ja kirjutada midagi Assemblys. Veidi, aga piisavalt, et alustada arvestamist. Sa naeratad, aga ma räägin täiesti tõsiselt. Pead mõistma keele ja riista vastavust. Pärast seda pead minema ja natuke kirjutama ning tegema väikese mängu kompilaatori väikesele mängu keelele. „Mänge” tähendab, et tuleb teha see mõistliku ajaga. See võib olla superlihtne, aga peab genereerima juhiseid. Juhiste genereerimise akt aitab mõista, milline on kulumudel silla ehitamiseks kõrgetasemelise koodi ja masina koodi vahel, mis käitub riistvaral. See vastavus jääb meelde kompilaatori kirjutamise hetkel. Isegi kõige lihtsama kompilaatori kirjutamise hetkel. Pärast seda saab hakata vaatama Java keelt ja mille poolest semantiline vahe on palju sügavam ning kuidas selle üle on keerulisem sildu ehitada. Java's on palju raskem aru saada, kas meie sild on hea või halb, mis viib selle kokkuvarisemiseni ja mis mitte. Aga sul on vaja mingit alust, millega vaadat koodi ja mõista: „ah, see getter peab iga kord inline'ima.” Ja siis selgub, et mõnikord see juhtubki, välja arvatud juhul, kui meetod muutub liiga suureks, ja JIT hakkab kõike järjest inline'ima. Selliste kohtade tulemuslikkust saab koheselt ennustada. Üldiselt töötavad getterid hästi, aga siis vaatad suuri kuumi sildu ja mõistad, et seal liiguvad mingid funktsiooni kutsed, millel ei ole selge, mida nad teevad. See on probleem getterite laialdases kasutuses, põhjus, miks nad ei inline'ita – pole selge, kas see on getter. Kui sul on superväike koodibaas, saad selle lihtsalt meelde jätta ja siis öelda: see on getter, aga see on setter. Suures koodibaasis elab iga funktsioon oma lugu, mis ei ole kellelegi kuigi teada. Profilera ütleb, et oleme kaotanud 24% aega mingis silmus ja et selle silmuse tegevuse mõistmiseks pead vaatama igat funktsiooni sees. Seda on võimatu mõista, teadmata funktsiooni, ja see aeglustab tõeliselt arusaamisprotsessi. Seetõttu ma ei kasuta getterit ja setterit, ma olen jõudnud uuele tasemele!
Kust leida hinnamudelit? No, muidugi võib midagi lugeda... Aga ma arvan, et parim viis on tegutseda. Luua väike kompilaator ja see on parim viis mõista hinnamudelit ja panna see oma peadesse. Väike kompilaator, mis sobiks mikrolaineahi programmeerimiseks – see on algaja ülesanne. No, ma pean silmas, et kui sul juba on programmeerimisoskused, siis peaks sellest piisama. Kõik need asjad nagu stringi parsimine, mis sul on mingisugune algebraline väljend, võtta sealt välja matemaatiliste operatsioonide juhised õiges järjekorras, võtta õiged väärtused registritest – kõik see on lihtne. Ja samal ajal, kui sa seda teed, jääb see sinu ajju. Ma arvan, et kõik teavad, millega kompilaator tegeleb. Ja see annab arusaama hinnamudelist.
Practical examples of performance improvements
Andrey: Millele veel tähelepanu pöörata tulemuste parandamise töö käigus?
Cliff: Andmestruktuurid. Muide, ma pole ammu neid tunde pidanud... . See oli naljakas, aga nõudis nii palju vaeva, aga mul on ju ka elukäik! Noh, nii et üks suur ja huvitav tund, "Kuhu läheb teie tulemus", andsin üliõpilastele näite: kaks ja pool gigabaiti finantsteabeandmeid loeti CSV-failist ja siis tuli arvestada müüdavate toodete arvu. Tavalised turuandmed. UDP-paketid, muudetud tekstivormingusse, alates 70ndatest. Chicago Mercantile Exchange – igasugused asjad nagu õli, mais, sojaoad ja nii edasi. Tuli arvestada neid tooteid, tehingute arvu, keskmist rahaliikumist ja kaupu jne. See on üsna lihtne kaubandusteadus: leida toote kood (see on 1-2 sümbolit hash-tabelis), saada summa, lisada see ühe tehingu kogumisse, lisada maht, lisada hind ja paar muud asja. Väga lihtne matemaatika. Mänguklassi teostus oli väga otsekohene: kõik on failis, ma loen faili ja liigun selle kaudu, jagades üksikud kirjed Java stringideks, otsin neist vajalikke asju ja liidan vastavalt ülaltoodud matemaatikale. Ja see töötab mingi väikese kiirusaga.
Sellest lähenemisest on kõik selge, mis toimumas on, ja paralleelsed arvutused ei aita siin, eks? Selgub, et viiekordset jõudluse suurenemist saab saavutada lihtsalt õige andmestruktuuride valimisega. See üllatab isegi kogenud programmeerijaid! Minu konkreetses olukorras oli keskpunkt see, et mälu eraldamine kuumas tsüklis ei ole hea. No, see ei ole kogu tõde, aga üldiselt – ei tohi eraldada 'iga X' puhul, kui X on piisavalt suur. Kui X on kaks ja pool gigabaiti, siis ei peaks midagi 'iga tähe' või 'iga rea' või 'iga välja' jaoks eraldama, mitte midagi sellist. Just sellele kulubki aega. Kuidas see üldse töötab? Kujutage ette, et kutsun esile String.split() või BufferedReader.readLine(). Readline moodustab stringi andmebaidist, mis on saadetud võrgu kaudu, igaks read ühe korra, iga sada miljonit read jaoks. Ma võtan selle stringi, analüüsin seda et ja viskan ära. Miks viskan ära – noh, olen selle juba töötlenud, kõik. Seega igasuguse byte'i kohta, mis on loetud nendest 2.7G-st, salvestatakse kaks sümbolit stringis, st juba 5.4G, ja need ei ole mulle enam millekski vajalikud, seega visatakse ära. Kui vaadata mälu läbilaskevõimet, laeme 2.7G, mis läbib mälu ja protsessori mäbusid, ning seejärel saadetakse kaks korda rohkem stringiks, mis asub mälus, ja kõik see purustatakse iga uue stringi loomise käigus. Kuid ma pean selle lugema, raud loeb selle, isegi kui seejärel kõik purustatakse. Ja ma pean selle kirjutama, sest ma olen loonud stringi ja vahemälu on täis - vahemälu ei saa mahutada 2.7G. Seega igasuguse loetud byte'i kohta loen veel kaks lisabyte'i ja kirjutan kaks lisabyte'i, ja lõpuks on nende suhe 4:1 - sellises suhtes raiskame mälu läbilaskevõimet. Ja edasi selgub, et kui ma teen String.split() – teen seda kaugel mitte viimast korda, seal sees võib olla veel 6-7 välja. Seetõttu toob klassikaline CSV lugemis- ja stringide parsimise kood kaasa mälu läbilaskevõime kadu umbes 14:1 võrreldes sellega, mida te tegelikult sooviksite omada. Kui need eraldamised ära visata, siis võib saavutada viiekordse kiiruse.
Ja see ei ole üldse nii keeruline. Kui vaatate koodi õigelt kõrvalt, muutub see üsna lihtsaks kohe, kui olete probleemist aru saanud. Ei tasu üldse mäludelegatsiooni lõpetada: probleem on ainult selles, et te jagate mäluruumi ja see sureb kohe, samal ajal põletades olulist ressurssi, mis antud juhul on mälu ribalaius. Ja kogu see toob kaasa jõudluse languse. x86 puhul tuleb tavaliselt aktiivselt põletada protsessoritsükleid, kuid siin põletate kogu mälu palju varem. Lahendus on vähendada mäludelegatsioonide arvu.
Teine probleem on see, et kui käivitada profiler, kui mälu ribalaius on lõppenud, just siis, kui see juhtub, siis ootad tavaliselt mälukahe tagasitulekut, sest see on täis prügi, mille just ise genereerisite, kõigi nende ridadega. Seetõttu iga load või store operatsioon muutub aeglaseks, kuna need toovad kaasa mälukaevu tabamised — kogu vahemälu muutub aeglaseks, oodates, millal siitä prügistas. Seetõttu näitab profiler vaid sooja juhuslikku müra, mis on hajutatud kogu tsükli ulatuses — ei tule ühtegi eraldi kuumast instruktsioonist ega koha koodis. Ainult müra. Ja kui vaatad GC tsükleid, siis kõik need on noortele, ja ülivõimsad — mikrosekundid või millisekundid maksimum. Sest kogu see mälu sureb hetkega. Sa jaotad miljard gigabaiti ja see kärbib ja kärbib ning kärbib jälle. Kõik see toimub väga kiiresti. Saadud tulemuseks on madala hinnaga GC tsüklid, soe müra kogu tsükli ulatuses, kuid me soovime saada 5-kordset kiirendust. Sel hetkel peaks midagi olema selge ja kostma: «miks nii?!». Mälu ribalaiuse ületamine ei kuvata klassikalisest silurist, tuleb käivitada riistvara jõudluse arvutite silur ja näha seda iseseisvalt ja otse. Ja mitte otse ei saa kahtlustada neid kolme sümptomit. Kolmas sümptom on siis, kui vaatad, mida jagad, küsid profilerilt ja ta vastab: «Sa tegid miljoneid ridu, kuid GC töötas tasuta». Kui see juhtub, saad aru, et moodustasid liiga palju objekte ja põletad kogu mälu ribalaiuse. On viis sellest aru saada, kuid see pole ilmne.
Andmete struktuuri probleem: alusskeem, mis on kogu toimingu taga, on liiga suur, see on 2,7 G kettal, seetõttu ei ole selle koopia tegemine soovitatav – soovitakse laadida see otse võrgu baidipuhvrist registritesse, et mitte lugeda ja kirjutada sinna-sinna viis korda. Kahjuks ei paku Java vaikimisi sellist teeki JDK osana. Kuid see on tõeliselt triviaalne, eks? Sisuliselt on see 5-10 rida koodi, mis viib sellise oma puhverdatud stringi laadija loomise, mis kordab stringiklassi käitumist, olles samas madalamate baidipuhvrite ümber. Tulemusena näib, et töötad peaaegu nagu stringidega, kuid tegelikult liiguvad seal viidud puhvri näitajad, ja toored baidid ei kopeerita kuhugi, seega kasutatakse samu puhverid korduvalt, samas kui operatsioonisüsteem on õnnelik selle üle, et võtab enda kanda asjad, milleks ta on mõeldud, näiteks peidetud topeltpuvverdus nende baidipuhvrite puhul, ja sina ise ei purustagi lõputut voolu mittevajalikest andmetest. Muide, kas te mõistate, et GC töötamisel garanteeritakse, et iga mälu eraldamine ei ole protsessorile nähtav pärast viimast GC ringi? Seetõttu ei saa see kõik kuidagi olla vahemälus ja edasi toimub 100% garanteeritud möödalaskmine. Viidiku kasutamisel võtab x86-l registreist mälu lugemine 1-2 tsüklit ja kohe kui see juhtub, maksad, maksad, maksad, kuna mälu kõik on – ja see on mälu eraldamise hind. Reaalne hind.
Teisisõnu, andmestruktuurid on need, mida on kõige raskem muuta. Ja kui te olete aru saanud, et olete valinud vale andmestruktuuri, mis tapab tulevikus jõudluse, vajate tavaliselt palju tööd selle parandamiseks; kui seda ei tehta, läheb asi halvemaks. Kõigepealt tuleb mõelda andmestruktuuridele, see on oluline. Peamine kulu seisneb nende rikkaid andmestruktuuride peale, mida hakata kasutama stiilis „ma kopeerisin andmestruktuuri X andmestruktuuri Y, sest Y meeldib mulle rohkem kujunduse poolest”. Kuid kopeerimisoperatsioon (mis tundub odav) kasutab tegelikult mäluressursse ja just siin peitub kogu kadunud tööaeg. Kui mul on hiiglaslik JSON-string ja ma tahan selle muuta struktureeritud DOM-puudeks POJO või midagi sarnast, siis selle stringi parseerimise ja POJO ehitamise operatsioon ning hiljem uue pöördumise POJO juurde lisanduvad toimetamis- ja kulud – see ei ole odav. Välja arvatud juhul, kui te jooksete POJO-d palju sagedamini kui stringide kaudu. Kohapeal võib proovida stringi dekodeerida ja sealt ainult vajalik ära võtta, ilma et see muudetaks mingiks POJO-ks. Kui kõik see toimub teel, kus nõutakse maksimaalset jõudlust, mingeid POJO-sid ei ole – peate kuidagi otse stringis kaevama.
Why create your own programming language
Andrey: Te ütlesite, et kulumudeli mõistmiseks peate kirjutama oma väikese keele…
Cliff: Mitte keel, vaid kompilaator. Keel ja kompilaator on erinevad asjad. Peamine erinevus on teie peas.
Andrey: Üldiselt tean, et eksperimenteerite oma keelte loomisega. Miks?
Cliff: Sest, et saan! Olen poolselt pensionil, nii et see on minu hobi. Olen kogu elu realiseerinud kellegi teise keeli. Ja ma olen palju töötanud kodeerimisstiili kallal. Samuti näen ma probleeme muudes keeltes. Näen, et on paremaid viise tuttavate asjade tegemiseks. Ja ma kasutan neid. Olen lihtsalt kurnatud, et näha probleeme endas, Java's, Pythonis või mis tahes muus keeles. Hetkel kirjutan React Native, JavaScripti ja Elmiga hobi korras, mis ei ole pensionile jäämise, vaid aktiivse töö tegemisega seotud. Ja ma kirjutan ka Pythonis ning tõenäoliselt jätkan masinõppe arendamist Java-taustade jaoks. Populaarseid keeli on palju ning igalühel neist on huvitavad omadused. Igaüks on hea millegi poolest ja võib proovida nende kõigi nippe kokku viia. Seega, uurin huvitavaid asju, keele käitumist, püüan välja mõelda arusaadavat semantikat. Ja seni on mul see õnnestunud! Praegu võitlen mälu semantika kallal, kuna soovin, et see oleks nagu C ja Java, saavutades tugeva mälumudeli ja mälu semantika laadimiste ja salvestuste jaoks. Samuti, et oleks automaatne tüüpide järeldus nagu Haskellis. Nii et üritan segada Haskell'i sarnast tüübi järeldust mäluga, mis töötab nagu C ja Java. Sellega olen viimased 2-3 kuud tegelenud näiteks.
Andrey: Kui te ehitate keelt, mis võtab teisi keeli parimaid aspekte, kas olete mõelnud, et keegi teeb vastupidist: võtab teie ideed ja kasutab neid enda keeles?
Cliff: Just nii sünnivad uued keeled! Miks Java sarnaneb C-ga? Sest C-l oli hea süntaks, mida kõik mõistsid ja Java sai inspiratsiooni sellest süntaksist, lisades sinna tüübikeerukuse, massiivi piiride kontrolli, GC ja nad parandasid ka mõningaid C omadusi. Nad lisasid omi. Kuid nad said tõeliselt tugevat inspiratsiooni, eks? Kõik seisavad enne sind olnud hiiglaste õlgadel - just nii toimub areng.
Andrey: Nagu ma aru saan, on teie keel mälu kasutamise osas ohutu. Kas olete mõelnud midagi sarnast Rusti borrow checker'ile rakendada? Olete seda vaadanud, kuidas see teile meeldib?
Cliff: Ma olen juba ammu C keeles programmeerinud, kasutades kõiki neid malloc ja free abil mälu haldamist käsitsi. Teate, 90-95% käsitsi haldatud elueast järgib sama struktuuri. Ja see on väga, väga tüütu, kui seda käsitsi teha. Tahaks, et kompilaator lihtsalt teataks, mis toimub ja mida olete oma toimingutega saavutanud. Mõned asjad teeb borrow checker seda vaikimisi. Lisaks peaks see automaatselt andma teavet, mõistma kõike ja mitte koormama mind selle arusaamise edasiandmisega. See peaks teostama vähemalt kohaliku põgenemisanalüüsi, ja ainult siis, kui see ei õnnestu, tuleks lisada tüüpi annotatsioone, mis kirjeldavad eluea - ja selline skeem on oluliselt keerulisem kui borrow checker või mõni muu olemasolev mälutest. Valik „kõik on korras” ja „ma ei saanud üldse aru” — ei, peaks olema midagi paremat.
Nii et, inimesena, kes on kirjutanud palju C keeles, arvan, et automaatse elutsükli halduse tugi on äärmiselt oluline. Ja veel, mind häirib, kui palju mälu Java kasutab ja peamine kaebus on GC. Kui Java-s mälu eraldatakse, ei tule tagasi mälu, mis oli kohaliku GC viimases tsüklis. Keeledes, kus mälu haldus on täpsem, ei ole see nii. Kui kutsud malloc'i, siis saad kohe mälu, mida tavaliselt just kasutati. Üldiselt teed mäluga mingid ajutised asjad ja annad kohe tagasi. Ja see naaseb koheselt malloc'i basseinile, ning järgmine malloc'i tsükkel tõmbab selle jälle välja. Seega väheneb tegelik mälu kasutamine elavate objektide kogumini konkreetsel hetkel, pluss leke. Ja kui sul ei leki kõik täiesti kohutavalt, siis jääb enamus mälu vahemäludesse ja protsessorisse, ning see töötab kiiresti. Kuid see nõuab palju käsi mäluhaldust malloc'i ja free abil, mis on kutsutud õigesti õigesse kohta. Rust suudab sellega ise õigesti hakkama saada ja paljudel juhtudel pakkuda isegi suuremat jõudlust, kuna mälu tarbimine kitseneb ainult praegustele arvutustele – vastupidiselt ootusele, et järgmine GC tsükkel vabastab mälu. Lõppkokkuvõttes saime väga huvitava viisi jõudluse parandamiseks. Ja üsna võimas – mõtlesin, et olen tegelenud selliste asjadega andmete töötlemisel finantstehnoloogias, ning see võimaldas saavutada kiirusetõusu ligi viie korra. See on üsna suur kiirusetõus, eriti maailmas, kus protsessorid ei muutu kiiremateks ja me kõik ootame endiselt täiustusi.
The career of a performance engineer
Andrey: Samuti tahaksin küsida karjääri üldiselt. Te saate tuntuks JIT-i töötamise kaudu HotSpot'is, seejärel liigutasite Azulisse – ja see on samuti JVM-ettevõte. Kuid tegelesite nüüd rohkem riistvaraga kui tarkvaraga. Ja siis järsku pöördusite Big Data ja Masinõppe poole, seejärel petuskatsete avastamisele. Kuidas see nii juhtus? Need on väga erinevad arenduse valdkonnad.
Cliff: Olen juba üsna pikka aega programmeerimisega tegelenud ja olen jõudnud paljudele erinevatele tegevustele. Ja kui inimesed ütlevad: „oh, sina olid see, kes tegi JIT Java jaoks!”, siis see on alati naljakas. Enne seda tegelesin PostScripti klooni loomisega – selle keele, mida Apple kunagi kasutas oma laserprinterites. Ja enne seda tegin Forth keele rakendust. Ma arvan, et minu ühine teema on tööriistade arendamine. Olen kogu elu teinud tööriistu, millega teised inimesed kirjutavad oma ägedaid programme. Kuid olen tegelenud ka operatsioonisüsteemide, draiverite, tuumadebbugerite, operatsioonisüsteemide arendamiseks mõeldud keelte arendamisega, mis algasid triviaalsetena, kuid aja jooksul muutusid järjest keerulisemaks. Kuid põhiline teema on ikkagi tööriistade arendamine. Suur osa elust möödus Azuli ja Sunny vahel, ja see oli seotud Java’ga. Kuid kui ma hakkasin tegelema suurandmete ja masinõppega, panin jälle oma piduliku mütsi pähe ja ütlesin: „Oh, nüüd on meil keeruline probleem, ja siin toimub palju huvitavaid asju ja inimesi, kes midagi teevad.” See on suurepärane arengutee, mida tasub läbida.
Jah, ma armastan tõeliselt hajusalt arvutamist. Minu esimene töö oli ülikooliajal C keeles reklaamiprojektis. See hõlmas hajusalt arvutamist Zilog Z80 kiipide peal, mis kogusid andmeid analoogsete optiliste tekstide tuvastamiseks, mida tegi tõeline analooganalüsaator. See oli äge ja täiesti ebanormaalne teema. Kuid seal olid probleemid, mingi osa ei tuvastatud õigesti, seega tuli võtta pilt ja näidata seda inimesele, kes oli juba silmadega lugenud ja teatada, mis seal kirjas on, ja seetõttu olid seal andmetööd, milles olid oma keel. Oli tagaplaan, mis kõike seda töötles – paralleelselt töötavad Z80, millel olid aktiivsed vt100 terminalid – igaühele üks, ja Z80 ebaühtne programmeerimisviis. Teatud ühine mälutükk, mida jagasid kõik Z80 'd konfiguratsioonis „täht“; jagati ka tagaplaan ja pool RAM 'ist jagati võrgus, samas kui teine pool jäi privaatseks või läks millegi muu jaoks. Mõistlikult keeruline paralleelne hajus jaotussüsteem jagatud... osaliselt jagatud mäluga. Millal see juhtus... Ei mäleta isegi, kuskil 80-ndate keskpaiku. Üsna kaua tagasi.
Jah, oletame, et 30 aastat on piisavalt kaua. Hajusalt arvutamisega seotud ülesanded eksisteerivad piisavalt kaua, inimesed on ammusest ajast selle kallal vaeva näinud. -klasteritega. Sellised klastrid näevad välja nagu... Näiteks, kui sul on Ethernet ja sinu kiire x86 on sellele Ethernetile ühendatud, siis soovid nüüd saada vale jagatud mälu, sest tol ajal ei saanud keegi tegeleda jaotatud arvutamise kodeerimisega, see oli liiga keeruline, ja seetõttu oli vale jagatud mälu x86 lehekülgede kaitsega. Kui sa kirjutasid sellele leheküljele, siis ütlesime teistele protsessoritele, et kui nad saavad ligipääsu samale jagatud mälule, tuleb see sinult laadida. Nii tekkis midagi, mis meenutas vahemälu järjepidevuse protokolli ja selle tarkvara. Huvi pakkuda kontseptsioon. Tõeline probleem oli muidugi teises. Kõik see töötas, kuid sa said kiiresti jõudlusprobleeme, sest keegi ei mõistnud jõudlusmudelit piisavalt hästi – millised olid seal mälule juurdepääsu mustrid, kuidas teha nii, et node'id ei pingsimoodi üksteist ei kontrolliks, jne.
H2O-s mõtlesin välja järgmise: arendajad vastutavad selle eest, et tuvastada, kus paralleelsus on olemas ja kus see puudub. Olen välja töötanud kodeerimismudeli, mis lihtsustab kõrge jõudlusega koodi kirjutamist. Küll aga on aeglase toimimisega koodi kirjutamine keeruline ja see näeb halb välja. Aeglase koodi kirjutamiseks tuleb tõsiselt pingutada, kasutada tuleb ebatavalisi meetodeid. Aeglaselt toimiv kood on näha esimesest pilgust. Seetõttu kirjutatakse tavaliselt kood, mis töötab kiiresti, kuid tihti tuleb tegeleda jagatud mälu probleemidega. See kõik on seotud suurte massiividega ja käitumine seal sarnaneb mittesüntaktiliste suurte massiividega paralleelses Javas. Kujutage ette, et kaks lõime kirjutavad paralleelsesse massiivi, üks neist võidab ja teine kaotab, kuid te ei tea, kumb on kumb. Kui nad ei ole mittesünteetilised, võib järjekord olla mistahes – ja see tõepoolest töötab. Inimesed tõepoolest hoolivad operatsioonide järjekorrast, nad paigutavad volatiilseid muutujaid õigesti ja õigetes kohtades ootavad nad mäluga seotud jõudluse probleeme. Vastupidisel juhul kirjutaksid nad lihtsalt koodi tsüklitena 1-st N-ni, kus N on mingid triljonid, lootes, et kõik keerulised juhtumid muutuvad automaatselt paralleelseks – ja seal see ei toimi. Kuid H2O ei ole ei Java ega Scala, seda võib pidada „Java miinus miinus”, kui tahad. See on väga arusaadav programmeerimisstiil ja see sarnaneb lihtsa C või Java koodi kirjutamisele tsüklite ja massiividega. Samas saab mälu hallata terabaitidega. Ma kasutan endiselt H2O-d. Aeg-ajalt kasutan seda erinevates projektides – ja see on endiselt kõige kiirem lahendus, mis on kümneid kordi konkurentidest parem. Kui teete Big Data kolonnandmetega, on H2O-d väga keeruline ületada.
Technical challenges
Andrey: Mis on olnud teie suurim väljakutse kogu teie karjääri jooksul?
Cliff: Kas arutame tehnilist või mitte-tehnilist küsimust? Ütleksin, et suurimad väljakutsed on mitte-tehnilised.
Mis tehnilised väljakutsed. Ma lihtsalt võitsin need. Ma isegi ei tea, mis neist kõige suurem oli, kuid mitu neist olid üsna huvitavad, mille jaoks kulus palju aega ja vaimset võitlust. Kui ma tulin Suni, olin kindel, et teen kiire kompilaatori, kuid hulk kogenud spetsialiste küsis, et mul ei õnnestu kunagi midagi. Kuid läksin selle teed, kirjutasin kompilaatori, sealhulgas registreerimisallokaatori, ja see oli üsna kiire. See oli sama kiire nagu kaasaegne C1, kuid tol ajal oli allokaator palju aeglasem ja tagasi vaadates – see oli probleeme suurte and Structuresiga. Mul oli seda vaja, et kirjutada graafiline registreerimisallokaator, ja ma ei mõistnud dilemma koodi väljendusvõime ja kiirus, mis tollal eksisteeris ja oli väga oluline. Selgus, et andmestruktuur ületab tavaliselt x86 ajal suuruse vahemälu ja seetõttu, kui ma algselt arvasin, et registreerimisallokaator töötab 5-10 protsenti kogu JIT-imise ajast, siis tegelikult osutus see 50 protsendiks.
Aeg möödus, kompilaator muutus järjest selgemaks ja tõhusamaks, lõpetas eemaldamise tekitamise halva koodi suuremas osas juhtudel ning tootlikkus hakkas üha enam meenutama seda, mida C kompilaator pakub. Kui sa muidugi ei kirjuta mingit jama, mida isegi C ei kiirenda. Kui kirjutad koodi nagu C, saad ka tootlikkuse, mis on C sarnane suuremas osas juhtudel. Ja mida kaugemale, seda sagedamini saadud kood oli asümptootiliselt C tasemetega, registreerimisallokaator hakkas meenutama midagi lõpetatut… sõltumata sellest, kas sinu kood töötab kiiresti või aeglaselt. Jätkasin allokaatori kallal töötamist, et see teeks paremaid eraldamisi. See muutus järjest aeglasemaks, kuid andis üha paremat tegevust olukordades, kus keegi ei suutnud enam hakkama saada. Ma sain sukelduda registreerimisallokaatorisse, matta sinna kuu töö ja äkitselt hakkas kogu kood töötama 5% kiiremini. See juhtus korduvasti ja registreerimisallokaatorist sai midagi kunstiteose taolist – kõik armastasid või vihkasid seda, ning akadeemia inimesed küsisid, miks kõik täpselt nii tehakse, miks mitte , ja mis on erinevus. Vastus on endiselt sama: graafi värvimise põhine allocator pluss väga hoolikas töötamine puhverkoodiga on triumfi relv, parim kombinatsioon, mida keegi ei suuda ületada. Ja see on üsna mitteilmne asi. Kõik muu, mida kompilaator seal teeb – on üsna õppinud asjad, kuigi need on samuti viidud kunstitasemele. Olen alati teinud asju, mis pidid muutma kompilaatori kunstiteoseks. Kuid midagi sellest ei olnud midagi erakordset – välja arvatud registrite allocator. Kinnitatud on see, et tuleb hoolikalt koormuse all ja kui see juhtub (ma võin selgitada lähemalt, kui see on huvitav), siis tähendab see, et saab agressiivsemalt sissetoomist teha, ilma et riskiks suureneda jõudlusgraafi katkestamise üle. Tol ajal oli palju täismahus kompilaatoreid, mis olid ehtitud nipsasjade ja vidinatega, kus olid registrite allocatorid, kuid keegi ei suutnud enam nii teha.
Probleem on see, et kui lisad meetodeid, mis sobivad inlininguks, suurendades ja suurendades inlininguala, siis kasutatavate väärtuste kogum ületab kohe registreid ning peab toimuma spilling. Kriitiline tase jõuab tavaliselt kätte siis, kui allocator annab alla, ja üks hea kandidaadi spilling viib teise, ning sa spilling mingeid täiesti kummalisi asju. Inliningu väärtus seisneb selles, et kaotad osa overhead’ist, mis tuleb kutse ja salvestamise peale, sa saad näha väärtusi sees ja saad neid veel optimeerida. Inliningu maksumus seisneb selles, et tekib suur hulk elavaid väärtusi, ja kui su registri allocator spilling rohkem, kui vajalik, kaotad kohe. Seetõttu on enamikul allocatoritest probleem: kui inlining läheb teatud piirist üle, hakkavad kõik asjad spilling ja jõudlus võib olla kui vette visatud. Need, kes rakendavad kompileerijat, lisavad mõningaid heuristikat: näiteks, et peatada inlining, kui see saavutab mingi piisavalt suure suuruse, kuna allocatsioonid rikuvad kõik ära. Nii tekib jõudluse graafiku murdumine – sa inliningid, inliningid, jõudlus kasvab vaikselt – ja siis plõks! – see kukub järsku, sest sa oled inlininginud liiga palju. Nii toimis kõik enne Java saabumist. Java nõuab palju rohkem inliningut, seega pidin oma allocatorit tegema palju agressiivsemaks, et see ei kukuks ja kui sa oled inlininginud liiga palju – hakkab see spillingima, kuid siiski tuleb hetk, mil „enam ei spillingi“. See on huvitav tähelepanek ja see tuli mulle lihtsalt kuskilt, mitte ilmne, kuid hästi tasuv. Ma asusin ründava inlininguga ja see viis mind kohtadesse, kus Java ja C jõudlus käivad kõrvuti. Nad on tõeliselt lähedal – ma saan kirjutada Java koodi, mis on palju kiirem kui C kood, ja nii edasi, kuid keskmiselt, suure pildi kontekstis, on nad ligikaudu võrdsed. Arvatavasti on osa sellest teenest registri allocator, mis võimaldab mul inliningida maksimaalselt lolli moodi. Ma inliningin lihtsalt kõik, mida näen. Küsimus on selles, kas allocator töötab hästi, kas tulemusena on mõistlikult töötav kood. See oli suur väljakutse: mõista seda kõike ja sundida see tööle.
A bit about register allocation and multithreading
Vladimir: Probleemid, nagu registrite jaotamine, tunduvad olema igavene teema. Kas on kunagi olnud nii, et mõni idee tundus lootustandev, kuid kukkus praktikas läbi?
Cliff: Loomulikult! Registrite jaotamine on valdkond, kus NP-täieliku probleemi lahendamiseks üritad leida mingeid heuristikaid. Ja sa ei saa kunagi ideaalsele lahendusele jõuda, eks? See on lihtsalt võimatu. Vaata, Ahead of Time kompileerimine – see töötab ka halvasti. Räägime siin mingitest keskmistest juhtudest. Tüüpilisest jõudlusest, nii et saad minna ja mõõta midagi, mida pead heaks tüüpiliseks jõudluseks – lõpuks, sa ju töötad selle parandamise nimel! Registrite jaotamine on teema, mis on täielikult pühendatud jõudlusele. Niipea kui sul on esimene prototüüp, mis töötab ja värvib, nagu vaja, algab töö jõudluse kallal. Pead õppima hästi mõõtma. Miks see oluline on? Kui on selged andmed, saad vaadata erinevaid osi ja näha: aha, see aitas siin, aga seal läks kõik katki! Tulevad välja head ideed, sa lisad uue heuristika ja äkki hakkab kõik keskmiselt veidi paremini tööle. Või mitte. Mul oli rohkesti juhtumeid, kus me võitlesime viie protsendi jõudluse nimel, mis eristas meie arendust eelmisest jaoturist. Ja iga kord näeb see välja nii: kuskil võitsime, kuskil kaotasime. Kui sul on head jõudluse analüüsi tööriistad, saad leida kaotavad ideed ja mõista, miks nad kaotavad. Võib-olla tasub jätta kõik nii, nagu on, aga võib-olla peaks tõsisemalt jõudluse häälestamiseks järele mõtlema või minema ja midagi muud parandama. See on terve hulk asju! Ma tegin selle toreda nippi, aga mulle on vaja ka seda, ja seda, ja seda – ja nende kogusumma toob kaasa teatavaid parandusi. Ja üksikud võivad ebaõnnestuda. Nii see on NP-täielike probleemide jõudlusega töötamise loomuses.
Vladimir: Tundub, et asjad nagu jaotajate värvimine on juba lahendatud ülesanne. Noh, teie jaoks lahendatud, arvestades, mida te räägite, nii et kas siis üldse tasub…
Cliff: See, it's not solved as such. It's you who must turn it into a 'solved' one. There are hard problems, and they need solving. When that's done, it's time to work on performance. This work needs to be approached appropriately — conduct benchmarks, gather metrics, explain situations where reverting to a previous version made your old hack work again (or vice versa, caused it to stop working). And don’t give up until you achieve something. As I’ve said, there are great ideas that didn’t work out, but in the realm of register allocation, ideas are practically infinite. For instance, you can read scientific publications. Although this area has started moving much slower now and has become clearer than in its early days. Nevertheless, an entire infinity of people works in this field, and all their ideas are worth trying, they are all waiting for their moment. You can't tell how good they are unless you try them out. How well they integrate with everything else in your allocator, since an allocator does a multitude of things, and some ideas might not work in your specific allocator but could flourish in another. The main way for an allocator to win is to pull the slow stuff out of the main path and force a split across the boundaries of slow paths. So, if you want to trigger GC, take the slow path, deoptimize, throw exceptions, everything in that vein — you know these things are relatively rare. And they really are rare; I’ve checked. You put in extra work, and as a result, numerous constraints on these slow paths disappear, but that’s not very significant because they are slow and rarely traversed. For example, a null pointer — it never happens, right? You need to have several paths for different things, but they shouldn't interfere on the main one.
Vladimir: What do you think about multi-core processing when there are thousands of cores at once? Is it a useful thing?
Cliff: The success of GPUs shows that it's quite beneficial!
Vladimir: They are quite specialized. What about general-purpose processors?
Cliff: Noh, see oli Azul'i ärimudel. Vastus tuli veel ajal, mil inimesed armastasid väga prognoositavat jõudlust. Sel ajal oli paralleelse koodi kirjutamine keeruline. H2O kodeerimismudel skaleerub hästi, kuid see ei ole üldotstarbeline mudel. Ainult veidi üldisem kui GPU kasutamisel. Kas me räägime sellise asja arendamise keerukusest või selle kasutamise keerukusest? Näiteks, huvitav õppetund, mille Azul mulle andis, sai siit: väikesed vahemälud on täiesti okei.
The biggest challenge in life
Vladimir: Ent kuidas on asjadega, mis ei ole tehnilised väljakutsed?
Cliff: Suurim väljakutse oli mitte olla... lahke ja hea inimeste suhtes. Selle tagajärjel satun pidevalt äärmiselt konfliktsetesse olukordadesse. Situatsioonides, kus ma teadsin, et kõik läheb valesti, kuid ei teadnud, kuidas edasi liikuda nende probleemide lahendamisel ja ei saanud nendega hakkama. Palju pikaajalisi probleeme, mis kestsid kümneid aastaid, ilmus just sellisel viisil. See, et Java-s on C1 ja C2 kompilaatorid, on sellest otsene tagajärg. See, et Java-s ei olnud kümme aastat mitmeastmelist kompileerimist, on samuti otsene tagajärg. On tõenäoline, et meil sellist süsteemi tõeliselt vajasime, kuid pole selge, miks seda ei olnud. Mul olid probleemid ühe inseneriga... või inseneride grupiga. Aastaid tagasi, kui ma hakkasin töötama Sunis, olin ma... Okei, mitte ainult siis, mul on tegelikult alati oma arvamus. Ja ma pean tõeks, et saaks lihtsalt võtta oma tõe ja öelda seda otse. Eriti arvestades, et mul oli enamikul ajast šokeerivalt õigus. Ja kui sulle see lähenemine ei meeldi... eriti kui sa oled ilmselgelt vale ja teed rumalusi... Üldiselt, mitte paljud inimesed ei suutnud sellist suhtlemisviisi taluda. Kuigi mõned suudavad, näiteks mina. Olen kogu elu ehitanud meritokraatia põhimõtetele. Kui sa näitad mulle midagi vale, pöördun ma kohe ringi ja ütlen: sa ütlesid rumalust. Sellega loomulikult palun vabandust ja kõik selline, tunnustan saavutusi, kui neid tõesti on, ja teen teisi õigeid samme. Teiselt poolt, mul on šokeerivalt palju õigus šokeerivalt suure osa ajast. Ja see ei mõju suhetes inimestega hästi. Ma ei proovi isegi olla armas, vaid panen küsimuse teravalt ette. „See ei tööta kunagi, sest põhjus, kaks ja kolm.” Ja nemad on sellised: „Oi!”. Oli ka teisi tagajärgi, mida oleks parem ilmselt vahele jätta: näiteks need, mis viisid lahutuseni oma naisest ja kümne aasta jooksul kestnud depressioonini pärast seda.
Väljakutse on võitlus inimestega, nende arusaamadega, mida sa saad teha või mitte, mis on oluline ja mis mitte. On olnud palju väljakutseid koodimistüübi osas. Ma kirjutan endiselt palju koodi, ja tookord pidin isegi aeglustama, sest tegin liiga palju paralleelseid ülesandeid ja tegin need halvasti, selle asemel et keskenduda ühele. Tagasi vaadates kirjutasin ma poole Java JIT meeskonna koodist, C2 meeskonnast. Kiiruselt järgmine koodija kirjutas poole aeglasemalt, järgmine veel poole aeglasemalt ja see oli eksponentsiaalne langus. Seitsmes inimene selles reas oli väga, väga aeglane – nii see ikka kipub olema! Ma puutusin kokku hulga koodiga. Ma vaatasin, kes mida kirjutab, ilma eranditeta, uurisin nende koodi, tegin igaühe ülevaate ja kirjutasin endiselt rohkem, kui keegi nendest. Inimeste jaoks selline lähenemine ei toimi väga hästi. Mõned ei armasta seda. Ja kui nad ei suuda sellega toime tulla, siis hakkavad igasugused kaebused. Näiteks öeldi mulle kord, et pean lõpetama koodi kirjutamise, kuna ma kirjutan liiga palju koodi ja see seab meeskonna ohtu, ja minu jaoks kõlas see nagu nalja: kutid, kui kogu ülejäänud meeskond kaob ja ma jätkan koodi kirjutamist, kaotad sa ainult poole meeskonnast. Teisest küljest, kui ma jätkan koodi kirjutamist ja sa kaotad poole meeskonna – see kõlab väga halva juhtimise poolena. Ma ei ole kunagi eriti mõelnud sellele, kunagi sellest rääkinud, aga see oli ikkagi kuskil mu peas. Teadvuse tagahoovis keerles mõte: "Kas te lõbutsete või?" Seega, kõige suurem probleem olin mina ja minu suhted inimestega. Nüüd mõistan ennast palju paremini, olen pikalt juhtinud programmiere ning nüüd ütlen otse inimestele: tead, ma olen selline, nagu olen, ja teil tuleb minuga leppida – kas saan siin seista? Ja kui nad hakkasid sellega toime tulema, hakkas kõik paremini toimima. Ma ei ole tegelikult ei halb ega hea, mul pole mingeid halbu kavatsusi või isekaid eesmärke, see on lihtsalt minu olemus ja sellega tuleb kuidagi elada.
Andrey: Alles hiljuti hakati rääkima eneseteadlikkusest introvertide jaoks ja üldiselt pehmetest oskustest. Mida selle kohta öelda?
Cliff: Jah, see oli arusaam ja õppetund, mille ma oma lahutusest naisega sain. Mida ma oma lahutusest õppisin – see on arusaam enda kohta. Nii hakkasin ma mõistma teisi inimesi. Mõistma, kuidas see interaktsioon toimib. See tõi endaga kaasa avastusi järjestikku. Ilmnes arusaam, kes ma olen ja mida endast kujutan. Mida ma teen: kas olen mures ülesande pärast, väldin konflikti või midagi muud – ja selline eneseteadlikkuse tase aitab tõeliselt hoida end kontrolli all. Pärast seda läheb kõik palju lihtsamalt. Üks asi, mille ma avastasin mitte ainult enda, vaid ka teiste programmeerijate juures – on võimetus väljendada mõtteid, kui sa oled emotsionaalses stressis. Näiteks istud ja kodeerid, oled voos ja siis jookseb keegi sinu juurde ning hakkab hüsteeriliselt karjuma, et midagi on katki ja nüüd rakendatakse sinule äärmuslikke meetmeid. Ja sa ei suuda sõnagi öelda, sest oled emotsionaalses stressis. Omandatud teadmised võimaldavad valmistuda selle hetkeks, seda üle elada ja liikuda taganemisplaani juurde, pärast mida on võimalik midagi ette võtta. Nii et jah, kui hakkad aru saama, kuidas see kõik toimib – see on suur elu muutnud sündmus.
Mina ise ei suutnud leida õiged sõnad, kuid mäletan tegevuste järjekorda. Üksikasjalikult on see reaktsioon – füüsiliselt sama palju kui verbaalselt, ja sul on vaja ruumi. Sellist ruumi, zen-mõttes. Just seda tuleb seletada ja seejärel kohe kõrvale astuda – füüsiliselt kõrvale astuda. Kui ma ei räägi, saan ma olukorda emotsioonide osas töödelda. Kui adrenaliin jõuab ajju, lülitab sind see režiimisse 'löö või põgene', sa ei saa enam midagi rääkida, ei – nüüd oled sa idiot, löömisteks mõeldud insener, kes ei suuda anda väärilist vastust ega isegi peatada rünnakut, ja ründaja saab vabalt uuesti ja uuesti rünnata. Esiteks pead taas iseendaks saama, taastama kontrolli, väljumiseks režiimist 'löö või põgene'.
Ja selleks on vajalik verbaalne ruum. Lihtsalt vaba ruum. Kui midagi öelda, siis saabki just seda väita, ja seejärel minna tõeliselt leida endale "ruum": jalutada pargis, sulguda duši alla – pole tähtis. Peamine on ajutiselt end olukorrast eemaldada. Niipea kui sa vähemalt paariks sekundiks eemaldud, naaseb kontroll, hakkad mõtlema selgelt. "Noh, ma ei ole ju mingi idioot, ma ei tee rumalaid asju, ma olen üsna kasulik inimene." Niipea kui sa suudad end veenda, on aeg liikuda järgmisele etapile: mõista, mis juhtus. Sulle rünnati, rünnak tuli kohast, kust ei oodatud, see oli ebaaus ja küüniline lõks. See on halb. Järgmine samm on mõista, miks ründajal seda vajalik oli. Tõeliselt, miks? Võib-olla sellepärast, et ta ise on vihane? Miks ta on vihane? Näiteks sellepärast, et ta eksis ja ei saa vastutust võtta? Nii tuleb hoolikalt käsitleda kogu olukorda. Kuid selleks on vaja manööverdusruumi, verbaalset ruumi. Kõige esimene samm on katkestada verbaalne kontakt. Eemale minna aruteludest sõnades. Tühistada see, minna minema nii kiiresti kui võimalik. Kui see on telefonikõne - lihtsalt pane toru ära - see on oskus, mille ma sain suhtlemisest oma endise abikaasaga. Kui kõne ei vii kuskile headesse kohtadesse, lihtsalt ütle "head aega" ja pane toru ära. Teiselt poolt toru: "bla-bla-bla", sa vastad: "aha, tšau!" ja paned toru ära. Lihtsalt katkestad vestluse. Viie minuti pärast, kui su mõtlemisvõime naaseb, oled sa natuke jahtunud, on võimalik mõelda, mis siis ikkagi juhtus ja mis edasi saab. Ja hakata formuleerima mõeldud vastust, mitte lihtsalt reageerida emotsioonidele. Minu jaoks oli läbimurdeks eneseteadvuses see, et emotsionaalse stressi korral ei suuda ma rääkida. Sellest seisundist välja astuda, mõelda ja planeerida, kuidas vastata ja kompenseerida probleeme – need on õiged sammud, kui sa ei saa rääkida. Lihtsaim viis on põgeneda olukorrast, kus emotsionaalne stress ilmneb ja lihtsalt lõpetada selles stressis osalemine. Pärast seda saad sa mõelda, kui sa suudad mõelda, tekib võimalus rääkida ja nii edasi.
Muide, kohtus üritab vastaspoole advokaat seda sinuga teha – nüüd on juba selge, miks. Sest tal on võimalus sind nii maha suruda, et sa ei suuda isegi oma nime välja öelda, näiteks. Kirja sõnas mõttes, sa ei saa rääkida. Kui sinuga juhtub nii ja kui sa tead, et jõuad kohta, kus käivad sõnalised lahingud, kohtusse, siis võib tulla oma juristiga. Jurist kaitseb sind ja lõpetab sõnalise rünnaku ning teeb seda täiesti seaduslikul teel, ja sinu kadunud zen-ruum naaseb. Näiteks, mul oli paar korda vaja helistada perele, kohtunik suhtus sellesse üsna sõbralikult, kuid vastaspoole advokaat karjus ja karjus mulle peale, ma ei saanud isegi sõna vahele öelda. Sellistes olukordades töötab minu jaoks kõige paremini vahendaja kasutamine. Vahendaja lõpetab kogu selle pideva surve, mis sinu peale voolab, sa avastad vajaliku zen-ruumi, koos sellega naaseb ka rääkimisvõime. See on terve teadmiste valdkond, milles tuleb palju õppida, palju endas avastada, ja kõik see muutub kõrgetasemelisteks strateegilisteks otsusteks, mis on erinevad eri inimeste vahel. Mõnel inimesel ei ole eespool kirjeldatud probleeme, tavaliselt puuduvad need inimestel, kes tegelevad professionaalselt müügiga. Kõik need inimesed, kes teenivad elatist sõnadega – tuntud lauljad, luuletajad, usujuhid ja poliitikud, neil on alati, mida öelda. Neil ei ole selliseid probleeme, kuid mul on need olemas.
Andrey: See oli… ootamatu. Suurepärane, me oleme juba päris palju rääkinud ja on aeg see intervjuu lõpetada. Me kohtume kindlasti konverentsil ja saame seda dialooge jätkata. Kohtume Hydra konverentsil!
Jätkata suhtlemist Cliffiga saab Hydra 2019 konverentsil, mis toimub 11.-12. juulil 2019 Peterburis. Ta tuleb ettekandega . Piletid on saadaval .
Allikas: habr.com
