Geweldig interview met Cliff Click, de vader van JIT-compilatie op Java

Geweldig interview met Cliff Click, de vader van JIT-compilatie op JavaKlif Klik — CTO van Cratus (IoT-sensoren voor procesverbetering), oprichter en mede-oprichter van verschillende startups (waaronder Rocket Realtime School, Neurensic en H2O.ai) met verschillende succesvolle exits. Cliff schreef zijn eerste compiler op 15-jarige leeftijd (Pascal voor de TRS Z-80)! Hij is vooral bekend van zijn werk aan C2 in Java (de Sea of ​​Nodes IR). Deze compiler liet de wereld zien dat JIT code van hoge kwaliteit kon produceren, wat een van de factoren was bij de opkomst van Java als een van de belangrijkste moderne softwareplatforms. Vervolgens hielp Cliff Azul Systems met het bouwen van een mainframe met 864 kernen met pure Java-software die GC-pauzes op een hoop van 500 gigabyte binnen 10 milliseconden ondersteunde. Over het algemeen slaagde Cliff erin om aan alle aspecten van de JVM te werken.

 
Deze habrapost is een geweldig interview met Cliff. We zullen praten over de volgende onderwerpen:

  • Overgang naar optimalisaties op laag niveau
  • Hoe je een grote refactoring uitvoert
  • Kostenmodel
  • Optimalisatietraining op laag niveau
  • Praktijkvoorbeelden van prestatieverbetering
  • Waarom uw eigen programmeertaal maken?
  • Carrière als prestatie-ingenieur
  • Technische uitdagingen
  • Een beetje over registertoewijzing en multi-cores
  • De grootste uitdaging in het leven

Het interview wordt afgenomen door:

  • Andrei Satarin van Amazon Web Services. In zijn carrière slaagde hij erin om in totaal verschillende projecten te werken: hij testte de gedistribueerde database NewSQL in Yandex, een clouddetectiesysteem in Kaspersky Lab, een multiplayer-game in Mail.ru en een dienst voor het berekenen van valutaprijzen bij Deutsche Bank. Geïnteresseerd in het testen van grootschalige backend- en gedistribueerde systemen.
  • Vladimir Sitnikov van Netcracker. Tien jaar werk aan de prestaties en schaalbaarheid van NetCracker OS, software die door telecomoperatoren wordt gebruikt om netwerk- en netwerkapparatuurbeheerprocessen te automatiseren. Geïnteresseerd in prestatieproblemen met Java en Oracle Database. Auteur van meer dan een dozijn prestatieverbeteringen in het officiële PostgreSQL JDBC-stuurprogramma.

Overgang naar optimalisaties op laag niveau

Andrew: Je bent een grote naam in de wereld van JIT-compilatie, Java en performancewerk in het algemeen, toch? 

klif: Zo is het!

Andrew: Laten we beginnen met enkele algemene vragen over performancewerk. Wat vind je van de keuze tussen optimalisaties op hoog en laag niveau, zoals werken op CPU-niveau?

klif: Ja, alles is hier eenvoudig. De snelste code is degene die nooit wordt uitgevoerd. Daarom moet je altijd vanaf een hoog niveau beginnen en aan algoritmen werken. Een betere O-notatie zal een slechtere O-notatie verslaan, tenzij er voldoende grote constanten tussenbeide komen. Dingen op een laag niveau gaan als laatste. Als je de rest van je stapel goed genoeg hebt geoptimaliseerd en er nog een aantal interessante dingen over zijn, is dat doorgaans een laag niveau. Maar hoe begin je vanaf een hoog niveau? Hoe weet je dat er voldoende werk op hoog niveau is gedaan? Nou... absoluut niet. Er zijn geen kant-en-klare recepten. Je moet het probleem begrijpen, beslissen wat je gaat doen (om in de toekomst geen onnodige stappen te ondernemen) en dan kun je de profiler ontdekken, die iets nuttigs kan zeggen. Op een gegeven moment realiseer je je zelf dat je onnodige dingen hebt weggedaan en dat het tijd is om wat fijnafstemming op laag niveau te doen. Dit is absoluut een speciaal soort kunst. Er zijn veel mensen die onnodige dingen doen, maar die zo snel gaan dat ze geen tijd hebben om zich zorgen te maken over de productiviteit. Maar dit is totdat de vraag botweg opkomt. Normaal gesproken kan het 99% van de tijd niemand iets schelen wat ik doe, tot het moment waarop er iets belangrijks op het kritieke pad komt waar niemand om geeft. En hier begint iedereen je te zeuren over “waarom het vanaf het begin niet perfect werkte.” Over het algemeen valt er altijd iets te verbeteren in de prestaties. Maar 99% van de tijd heb je geen leads! Je probeert gewoon iets te laten werken en gaandeweg kom je erachter wat belangrijk is. Je kunt nooit van tevoren weten dat dit stuk perfect moet zijn, dus eigenlijk moet je in alles perfect zijn. Maar dit is onmogelijk en je doet het niet. Er zijn altijd veel dingen die moeten worden opgelost - en dat is volkomen normaal.

Hoe je een grote refactoring uitvoert

Andrew: Hoe werk je aan een voorstelling? Dit is een transversaal probleem. Heeft u bijvoorbeeld ooit moeten werken aan problemen die voortkomen uit het kruispunt van veel bestaande functionaliteit?

klif: Ik probeer het te vermijden. Als ik weet dat de prestaties een probleem zullen zijn, denk ik erover na voordat ik begin met coderen, vooral als het gaat om datastructuren. Maar vaak ontdek je dit allemaal pas later. En dan moet je extreme maatregelen nemen en doen wat ik ‘herschrijven en veroveren’ noem: je moet een stuk pakken dat groot genoeg is. Een deel van de code zal nog steeds herschreven moeten worden vanwege prestatieproblemen of iets anders. Wat de reden voor het herschrijven van code ook is, het is bijna altijd beter om een ​​groter stuk te herschrijven dan een kleiner stuk. Op dit moment begint iedereen te trillen van angst: “Oh mijn God, je kunt niet zoveel code aanraken!” Maar in feite werkt deze aanpak bijna altijd veel beter. Je moet meteen een groot probleem aanpakken, er een grote cirkel omheen tekenen en zeggen: ik zal alles binnen de cirkel herschrijven. De rand is veel kleiner dan de inhoud erin die moet worden vervangen. En als je met een dergelijke afbakening van grenzen het werk binnenin perfect kunt doen, heb je je handen vrij en kun je doen wat je wilt. Als je het probleem eenmaal begrijpt, is het herschrijfproces veel eenvoudiger, dus neem een ​​grote hap!
Tegelijkertijd, wanneer u een grote herschrijving uitvoert en beseft dat de prestaties een probleem gaan vormen, kunt u zich daar onmiddellijk zorgen over gaan maken. Meestal gaat dit over in eenvoudige zaken als ‘kopieer geen gegevens, beheer gegevens zo eenvoudig mogelijk, maak ze klein’. Bij grote herschrijvingen zijn er standaardmanieren om de prestaties te verbeteren. En die draaien bijna altijd om data.

Kostenmodel

Andrew: In een van de podcasts had je het over kostenmodellen in de context van productiviteit. Kunt u uitleggen wat u hiermee bedoelde?

klif: Zeker. Ik ben geboren in een tijdperk waarin processorprestaties extreem belangrijk waren. En dit tijdperk keert weer terug - het lot is niet zonder ironie. Ik begon te leven in de tijd van 256-bits machines; mijn eerste computer werkte met XNUMX bytes. Precies bytes. Alles was heel klein. Instructies moesten worden geteld, en naarmate we hogerop gingen in de stapel programmeertalen, kregen de talen steeds meer aandacht. Er was Assembler, daarna Basic, daarna C, en C zorgde voor veel details, zoals registertoewijzing en instructieselectie. Maar daar was alles heel duidelijk, en als ik een verwijzing zou maken naar een instantie van een variabele, dan zou ik belasting krijgen, en de kosten van deze instructie zijn bekend. De hardware produceert een bepaald aantal machinecycli, dus de uitvoeringssnelheid van verschillende dingen kan eenvoudig worden berekend door alle instructies die u gaat uitvoeren bij elkaar op te tellen. Elke vergelijking/test/branch/call/load/store kan bij elkaar worden opgeteld en gezegd: dat is de uitvoeringstijd voor u. Wanneer u werkt aan het verbeteren van de prestaties, zult u zeker letten op welke cijfers overeenkomen met kleine warme cycli. 
Maar zodra je overstapt op Java, Python en dergelijke, stap je al snel af van low-level hardware. Wat zijn de kosten voor het aanroepen van een getter op Java? Als JIT in HotSpot correct is inlined, het zal laden, maar als het dit niet doet, zal het een functieaanroep zijn. Omdat de oproep zich in een hot-loop bevindt, worden alle andere optimalisaties in die lus overschreven. De werkelijke kosten zullen dus veel hoger zijn. En je verliest onmiddellijk het vermogen om naar een stukje code te kijken en te begrijpen dat we het moeten uitvoeren in termen van processorkloksnelheid, geheugen en gebruikte cache. Dit alles wordt pas interessant als je echt in de voorstelling verdiept.
Nu bevinden we ons in een situatie waarin de processorsnelheden de afgelopen tien jaar nauwelijks zijn toegenomen. De oude tijd is terug! Op goede single-threaded prestaties kun je niet meer rekenen. Maar als je plotseling aan parallel computergebruik begint, is het ongelooflijk moeilijk; iedereen kijkt naar je als James Bond. Tienvoudige versnellingen komen hier meestal voor op plaatsen waar iemand iets heeft verprutst. Gelijktijdigheid vergt veel werk. Om die XNUMXx versnelling te krijgen, moet je het kostenmodel begrijpen. Wat en hoeveel kost het? En om dit te doen, moet je begrijpen hoe de tong op de onderliggende hardware past.
Martin Thompson heeft een geweldig woord gekozen voor zijn blog Mechanische sympathie! Je moet begrijpen wat de hardware gaat doen, hoe hij dat precies gaat doen en waarom hij überhaupt doet wat hij doet. Hiermee is het vrij eenvoudig om instructies te tellen en uit te zoeken waar de uitvoeringstijd naartoe gaat. Als je niet de juiste training hebt, ben je gewoon op zoek naar een zwarte kat in een donkere kamer. Ik zie voortdurend mensen die de prestaties optimaliseren, maar geen idee hebben wat ze doen. Ze lijden veel en boeken niet veel vooruitgang. En als ik hetzelfde stukje code neem, er een paar kleine hacks in doe en een vijf- of tienvoudige versnelling krijg, zeggen ze: nou, dat is niet eerlijk, we wisten al dat jij beter was. Verbazingwekkend. Waar heb ik het over... het kostenmodel gaat over wat voor soort code je schrijft en hoe snel deze gemiddeld draait in het grote geheel.

Andrew: En hoe kun je zo’n volume in je hoofd houden? Wordt dit bereikt met meer ervaring, of? Waar komt zo’n ervaring vandaan?

klif: Nou, ik heb mijn ervaring niet op de gemakkelijkste manier gekregen. Ik programmeerde in Assembly in de tijd dat je elke afzonderlijke instructie kon begrijpen. Het klinkt stom, maar sindsdien is de Z80-instructieset altijd in mijn hoofd en in mijn geheugen gebleven. Ik kan me de namen van mensen niet binnen een minuut na het praten herinneren, maar ik herinner me code die 40 jaar geleden is geschreven. Het is grappig, het lijkt op een syndroom "idiote wetenschapper.

Optimalisatietraining op laag niveau

Andrew: Is er een makkelijkere manier om binnen te komen?

klif: Ja en nee. De hardware die we allemaal gebruiken is in de loop van de tijd niet zoveel veranderd. Iedereen gebruikt x86, met uitzondering van Arm-smartphones. Als je niet aan een of andere vorm van hardcore inbedding doet, doe je hetzelfde. Oké, de volgende. Ook de instructies zijn al eeuwenlang niet veranderd. Je moet iets gaan schrijven in Assembly. Niet veel, maar genoeg om het te beginnen begrijpen. Je lacht, maar ik spreek volkomen serieus. U moet de overeenkomst tussen taal en hardware begrijpen. Daarna moet je een beetje gaan schrijven en een kleine speelgoedcompiler maken voor een beetje speelgoedtaal. Speelgoedachtig betekent dat het binnen een redelijke tijd moet worden gemaakt. Het kan supereenvoudig zijn, maar het moet instructies genereren. Door een instructie te genereren, krijgt u inzicht in het kostenmodel voor de brug tussen de code op hoog niveau die iedereen schrijft en de machinecode die op de hardware draait. Deze correspondentie zal in de hersenen worden gebrand op het moment dat de compiler wordt geschreven. Zelfs de eenvoudigste compiler. Daarna kun je naar Java gaan kijken en zien dat de semantische kloof veel dieper is, en dat het veel moeilijker is om er bruggen over te bouwen. Op Java is het veel moeilijker te begrijpen of onze brug goed of slecht is geworden, waardoor hij uit elkaar zal vallen en wat niet. Maar je hebt een soort startpunt nodig waarbij je naar de code kijkt en begrijpt: "ja, deze getter moet elke keer inline worden geplaatst." En dan blijkt dat dit soms gebeurt, behalve in de situatie waarin de methode te groot wordt en het JIT alles begint in te lijnen. De prestaties van dergelijke plaatsen kunnen onmiddellijk worden voorspeld. Meestal werken getters goed, maar dan kijk je naar grote hot loops en besef je dat daar een aantal functieaanroepen rondzweven die niet weten wat ze doen. Dit is het probleem met het wijdverbreide gebruik van getters; de reden waarom ze niet inline zijn, is dat het niet duidelijk is of ze een getter zijn. Als je een superkleine codebasis hebt, kun je die eenvoudig onthouden en dan zeggen: dit is een getter, en dit is een setter. In een grote codebasis leeft elke functie zijn eigen geschiedenis, die over het algemeen bij niemand bekend is. De profiler zegt dat we 24% van de tijd verloren op een lus en om te begrijpen wat deze lus doet, moeten we naar elke functie binnenin kijken. Het is onmogelijk om dit te begrijpen zonder de functie te bestuderen, en dit vertraagt ​​het begripsproces ernstig. Daarom gebruik ik geen getters en setters, ik heb een nieuw niveau bereikt!
Waar kunt u het kostenmodel verkrijgen? Nou ja, je kunt natuurlijk iets lezen... Maar ik denk dat de beste manier is om te handelen. Het maken van een kleine compiler is de beste manier om het kostenmodel te begrijpen en in uw eigen hoofd te passen. Een kleine compiler die geschikt zou zijn voor het programmeren van een magnetron is een klus voor een beginner. Nou, ik bedoel, als je al programmeervaardigheden hebt, dan zou dat genoeg moeten zijn. Al deze dingen, zoals het ontleden van een string die je hebt als een soort algebraïsche uitdrukking, het extraheren van instructies voor wiskundige bewerkingen daaruit in de juiste volgorde, het nemen van de juiste waarden uit registers - dit alles gebeurt in één keer. En terwijl je het doet, wordt het in je hersenen ingeprent. Ik denk dat iedereen weet wat een compiler doet. En dit geeft inzicht in het kostenmodel.

Praktijkvoorbeelden van prestatieverbetering

Andrew: Waar moet je nog meer op letten als je aan productiviteit werkt?

klif: Data structuren. Trouwens, ja, ik heb deze lessen al een hele tijd niet meer gegeven... Raketschool. Het was leuk, maar het kostte veel moeite, en ik heb ook een leven! OK. Dus in een van de grote en interessante lessen, ‘Waar gaan je prestaties naartoe’, gaf ik studenten een voorbeeld: twee en een halve gigabyte aan fintech-gegevens werden uit een CSV-bestand gelezen en vervolgens moesten ze het aantal verkochte producten berekenen. . Reguliere tick-marktgegevens. UDP-pakketten worden sinds de jaren 70 naar tekstformaat geconverteerd. Chicago Mercantile Exchange - allerlei dingen zoals boter, maïs, sojabonen, dat soort dingen. Het was noodzakelijk om deze producten te tellen, het aantal transacties, het gemiddelde volume van de beweging van fondsen en goederen, enz. Het is vrij eenvoudige handelswiskunde: zoek de productcode (dat is 1-2 tekens in de hashtabel), haal het bedrag op, voeg het toe aan een van de handelssets, voeg volume toe, voeg waarde toe en een paar andere dingen. Heel eenvoudige wiskunde. De speelgoedimplementatie was heel eenvoudig: alles staat in een bestand, ik lees het bestand en blader er doorheen, verdeel individuele records in Java-strings, zoek de noodzakelijke dingen erin en tel ze op volgens de hierboven beschreven wiskunde. En het werkt op een lage snelheid.

Met deze aanpak is het duidelijk wat er aan de hand is, en parallel computing zal niet helpen, toch? Het blijkt dat een vijfvoudige prestatieverbetering kan worden bereikt door simpelweg de juiste datastructuren te kiezen. En dit verrast zelfs ervaren programmeurs! In mijn specifieke geval was de truc dat je geen geheugentoewijzingen moest doen in een hot loop. Nou, dit is niet de hele waarheid, maar in het algemeen moet je "eenmaal in X" niet markeren als X groot genoeg is. Als X twee en een halve gigabyte bedraagt, mag u niets “eenmaal per letter”, of “eenmaal per regel”, of “eenmaal per veld”, of zoiets toewijzen. Hier wordt tijd aan besteed. Hoe werkt dit eigenlijk? Stel je voor dat ik bel String.split() of BufferedReader.readLine(). Readline maakt een string van een set bytes die over het netwerk zijn gekomen, één keer voor elke lijn, voor elk van de honderden miljoenen lijnen. Ik neem deze zin, ontleed hem en gooi hem weg. Waarom gooi ik het weg - nou, ik heb het al verwerkt, dat is alles. Dus voor elke byte die van deze 2.7G wordt gelezen, worden er twee tekens in de regel geschreven, dat wil zeggen al 5.4G, en ik heb ze verder nergens voor nodig, dus worden ze weggegooid. Als je naar de geheugenbandbreedte kijkt, laden we 2.7G die door het geheugen en de geheugenbus in de processor gaat, en dan wordt er twee keer zoveel naar de lijn in het geheugen gestuurd, en dit alles wordt gerafeld wanneer elke nieuwe lijn wordt gemaakt. Maar ik moet het lezen, de hardware leest het, ook al is alles later gerafeld. En ik moet het opschrijven omdat ik een regel heb gemaakt en de caches vol zijn - de cache kan geen 2.7 G bevatten. Dus voor elke byte die ik lees, lees ik nog twee bytes en schrijf ik nog twee bytes, en uiteindelijk hebben ze een verhouding van 4:1 - in deze verhouding verspillen we geheugenbandbreedte. En dan blijkt dat als ik dat doe String.split() – dit is niet de laatste keer dat ik dit doe, er kunnen nog 6-7 velden in zitten. Dus de klassieke code van het lezen van CSV en het vervolgens parseren van de strings resulteert in een verspilling van geheugenbandbreedte van ongeveer 14:1 in verhouding tot wat je eigenlijk zou willen hebben. Als je deze selecties weggooit, kun je een vijfvoudige versnelling krijgen.

En het is niet zo moeilijk. Als je de code vanuit de juiste hoek bekijkt, wordt het allemaal vrij eenvoudig zodra je het probleem beseft. Je moet niet helemaal stoppen met het toewijzen van geheugen: het enige probleem is dat je iets toewijst en het onmiddellijk sterft, en gaandeweg verbrandt het een belangrijke bron, in dit geval de geheugenbandbreedte. En dit alles resulteert in een daling van de productiviteit. Op x86 moet je normaal gesproken actief processorcycli branden, maar hier heb je al het geheugen veel eerder opgebrand. De oplossing is om de hoeveelheid afscheiding te verminderen. 
Het andere deel van het probleem is dat als je de profiler uitvoert wanneer de geheugenstreep op is, je meestal op het moment dat dit gebeurt, wacht tot de cache terugkomt, omdat deze vol zit met rommel die je zojuist hebt geproduceerd, al die regels. Daarom wordt elke laad- of opslagbewerking traag, omdat deze tot cache-missers leiden - de hele cache is traag geworden en wacht tot afval deze verlaat. Daarom zal de profiler alleen warme, willekeurige ruis weergeven die door de hele lus wordt uitgesmeerd - er zal geen afzonderlijke hot-instructie of plaats in de code zijn. Alleen maar lawaai. En als je naar de GC-cycli kijkt, zijn ze allemaal van de jonge generatie en supersnel - maximaal microseconden of milliseconden. Al deze herinneringen sterven tenslotte onmiddellijk. Je wijst miljarden gigabytes toe, en hij knipt ze af, en knipt ze, en knipt ze nog eens. Dit alles gebeurt heel snel. Het blijkt dat er goedkope GC-cycli zijn, warm geluid gedurende de hele cyclus, maar we willen een versnelling van 5x bereiken. Op dit moment zou er iets in je hoofd moeten sluiten en klinken: "waarom is dit ?!" De overloop van de geheugenstrip wordt niet weergegeven in de klassieke debugger; u moet de debugger voor de hardwareprestatieteller uitvoeren en deze zelf en rechtstreeks zien. Maar dit kan niet direct worden vermoed op basis van deze drie symptomen. Het derde symptoom is wanneer je kijkt naar wat je benadrukt, het aan de profiler vraagt, en hij antwoordt: “Je hebt een miljard rijen gemaakt, maar de GC werkte gratis.” Zodra dit gebeurt, besef je dat je te veel objecten hebt gemaakt en de hele geheugenstrook hebt verbrand. Er is een manier om dit te achterhalen, maar die is niet voor de hand liggend. 

Het probleem zit in de datastructuur: de kale structuur die ten grondslag ligt aan alles wat er gebeurt, is te groot, er staat 2.7 G op schijf, dus een kopie hiervan maken is zeer ongewenst - je wilt het onmiddellijk uit de netwerkbytebuffer laden in de registers, zodat u niet vijf keer heen en weer naar de regel hoeft te lezen en schrijven. Helaas biedt Java u standaard niet zo'n bibliotheek als onderdeel van de JDK. Maar dit is triviaal, toch? In wezen zijn dit 5-10 regels code die zullen worden gebruikt om uw eigen gebufferde stringloader te implementeren, die het gedrag van de stringklasse herhaalt, terwijl het een wrapper rond de onderliggende bytebuffer is. Als gevolg hiervan blijkt dat je bijna werkt alsof je met strings werkt, maar in feite verplaatsen verwijzingen naar de buffer daarheen, en de onbewerkte bytes worden nergens gekopieerd, en dus worden dezelfde buffers keer op keer hergebruikt, en het besturingssysteem neemt graag de dingen over waarvoor het is ontworpen, zoals verborgen dubbele buffering van deze bytebuffers, en je hoeft niet langer door een eindeloze stroom onnodige gegevens te slenteren. Begrijp je trouwens dat wanneer je met GC werkt, het gegarandeerd is dat elke geheugentoewijzing na de laatste GC-cyclus niet zichtbaar zal zijn voor de processor? Daarom kan dit onmogelijk allemaal in de cache staan, en dan vindt er een 100% gegarandeerde misser plaats. Als je met een pointer werkt, op x86, duurt het aftrekken van een register uit het geheugen 1-2 klokcycli, en zodra dit gebeurt, betaal, betaal, betaal, omdat het geheugen allemaal aan staat NEGEN caches – en dit zijn de kosten van geheugentoewijzing. Werkelijke waarde.

Met andere woorden: datastructuren zijn het moeilijkst te veranderen. En als je je eenmaal realiseert dat je de verkeerde datastructuur hebt gekozen, wat de prestaties later ten goede zal komen, moet er meestal nog veel werk worden verzet, maar als je dat niet doet, worden de zaken alleen maar erger. Allereerst moet je nadenken over datastructuren, dit is belangrijk. De grootste kosten hier vallen op dikke datastructuren, die beginnen te worden gebruikt in de stijl van "Ik heb datastructuur X gekopieerd naar datastructuur Y omdat ik de vorm van Y beter vind." Maar de kopieeroperatie (die goedkoop lijkt) verspilt feitelijk geheugenbandbreedte en dat is waar alle verspilde uitvoeringstijd wordt begraven. Als ik een gigantische reeks JSON heb en deze wil veranderen in een gestructureerde DOM-boom van POJO's of zoiets, zal het parseren van die reeks en het bouwen van de POJO, en het later opnieuw benaderen van de POJO, resulteren in onnodige kosten - het is niet goedkoop. Behalve als je veel vaker rond POJO's rent dan rond een touwtje. In plaats daarvan kun je in plaats daarvan proberen de string te ontsleutelen en daar alleen datgene uit te halen wat je nodig hebt, zonder er een POJO van te maken. Als dit allemaal gebeurt op een pad waarvan maximale prestaties vereist zijn, geen POJO's voor jou, moet je op de een of andere manier direct in de lijn graven.

Waarom uw eigen programmeertaal maken?

Andrew: Je zei dat je, om het kostenmodel te begrijpen, je eigen taaltje moet schrijven...

klif: Geen taal, maar een compiler. Een taal en een compiler zijn twee verschillende dingen. Het belangrijkste verschil zit in je hoofd. 

Andrew: Trouwens, voor zover ik weet experimenteer je met het creëren van je eigen talen. Waarvoor?

klif: Omdat ik het kan! Ik ben semi-gepensioneerd, dus dit is mijn hobby. Ik heb mijn hele leven de talen van anderen geïmplementeerd. Ik heb ook veel aan mijn codeerstijl gewerkt. En ook omdat ik problemen zie in andere talen. Ik zie dat er betere manieren zijn om vertrouwde dingen te doen. En ik zou ze gebruiken. Ik ben het gewoon zat om problemen in mezelf te zien, in Java, in Python, in welke andere taal dan ook. Ik schrijf nu in React Native, JavaScript en Elm als hobby die niet over pensioen gaat, maar over actief werk. Ik schrijf ook in Python en zal hoogstwaarschijnlijk blijven werken aan machine learning voor Java-backends. Er zijn veel populaire talen en ze hebben allemaal interessante kenmerken. Iedereen is goed op zijn eigen manier en je kunt proberen al deze kenmerken samen te brengen. Dus ik bestudeer dingen die mij interesseren, het gedrag van taal, en probeer een redelijke semantiek te bedenken. En tot nu toe lukt het mij! Op dit moment worstel ik met geheugensemantiek, omdat ik het wil hebben zoals in C en Java, en een sterk geheugenmodel en geheugensemantiek wil krijgen voor ladingen en winkels. Zorg tegelijkertijd voor automatische type-inferentie, zoals in Haskell. Hier probeer ik Haskell-achtige type-inferentie te combineren met geheugenwerk in zowel C als Java. Dit is wat ik bijvoorbeeld de afgelopen 2-3 maanden heb gedaan.

Andrew: Als je een taal bouwt die betere aspecten uit andere talen overneemt, denk je dan dat iemand het tegenovergestelde zal doen: jouw ideeën overnemen en gebruiken?

klif: Dit is precies hoe nieuwe talen verschijnen! Waarom lijkt Java op C? Omdat C een goede syntaxis had die iedereen begreep en Java werd geïnspireerd door deze syntaxis, door het toevoegen van typeveiligheid, het controleren van array-grenzen, GC, en ze verbeterden ook een aantal dingen van C. Ze voegden hun eigen dingen toe. Maar ze waren behoorlijk geïnspireerd, toch? Iedereen staat op de schouders van de reuzen die je voorgingen - zo wordt vooruitgang geboekt.

Andrew: Zoals ik het begrijp, zal uw taalgeheugen veilig zijn. Heb je erover nagedacht om zoiets als een leenchecker van Rust te implementeren? Heb je naar hem gekeken, wat vind je van hem?

klif: Nou, ik schrijf al tijden C, met al deze malloc en gratis, en beheer de levensduur handmatig. Weet je, 90-95% van de handmatig gecontroleerde levensduur heeft dezelfde structuur. En het is heel erg pijnlijk om het handmatig te doen. Ik zou graag willen dat de samensteller u eenvoudigweg vertelt wat daar aan de hand is en wat u met uw acties hebt bereikt. Voor sommige dingen doet leenchecker dit kant-en-klaar. En het zou automatisch informatie moeten weergeven, alles moeten begrijpen, en mij niet eens moeten belasten met het presenteren van dit begrip. Het moet op zijn minst een lokale ontsnappingsanalyse uitvoeren, en alleen als het faalt, moet het type-annotaties toevoegen die de levensduur beschrijven - en zo'n schema is veel complexer dan een leencontrole, of zelfs welke bestaande geheugencontrole dan ook. De keuze tussen "alles is in orde" en "ik begrijp niets" - nee, er moet iets beters zijn. 
Dus als iemand die veel code in C heeft geschreven, denk ik dat ondersteuning voor automatische levensduurcontrole het allerbelangrijkste is. Ik ben het ook beu hoeveel Java geheugen gebruikt en de belangrijkste klacht is de GC. Wanneer u geheugen toewijst in Java, krijgt u niet het geheugen terug dat lokaal was tijdens de laatste GC-cyclus. Dit is niet het geval in talen met nauwkeuriger geheugenbeheer. Als je Malloc belt, krijg je meteen het geheugen dat normaal gesproken net werd gebruikt. Meestal doe je wat tijdelijke dingen met het geheugen en geef je het meteen weer terug. En het keert onmiddellijk terug naar de malloc-poel, en de volgende malloc-cyclus trekt het er weer uit. Daarom wordt het feitelijke geheugengebruik beperkt tot de verzameling levende objecten op een bepaald moment, plus lekken. En als alles niet op een volkomen onfatsoenlijke manier lekt, komt het grootste deel van het geheugen terecht in de caches en de processor, en werkt het snel. Maar vereist veel handmatig geheugenbeheer met malloc en gratis opgeroepen in de juiste volgorde, op de juiste plaats. Rust kan dit op eigen kracht goed aan, en levert in veel gevallen zelfs betere prestaties, omdat het geheugengebruik wordt beperkt tot alleen de huidige berekening - in plaats van te wachten op de volgende GC-cyclus om geheugen vrij te maken. Als gevolg hiervan kregen we een zeer interessante manier om de prestaties te verbeteren. En behoorlijk krachtig: ik bedoel, ik deed zulke dingen bij het verwerken van gegevens voor fintech, en hierdoor kon ik een versnelling van ongeveer vijf keer behalen. Dat is een behoorlijk grote opsteker, vooral in een wereld waar processors niet sneller worden en we nog steeds wachten op verbeteringen.

Carrière als prestatie-ingenieur

Andrew: Ik zou ook graag rondvragen over carrières in het algemeen. Je kreeg bekendheid met je JIT-werk bij HotSpot en verhuisde vervolgens naar Azul, dat ook een JVM-bedrijf is. Maar we werkten al meer met hardware dan met software. En toen schakelden ze plotseling over op Big Data en Machine Learning, en vervolgens op fraudedetectie. Hoe is dit gebeurd? Het zijn heel verschillende ontwikkelingsgebieden.

klif: Ik programmeer al een hele tijd en heb veel verschillende lessen kunnen volgen. En als mensen zeggen: “oh, jij bent degene die JIT voor Java heeft gedaan!”, is dat altijd grappig. Maar daarvoor werkte ik aan een kloon van PostScript, de taal die Apple ooit gebruikte voor zijn laserprinters. En daarvoor deed ik een implementatie van de Forth-taal. Ik denk dat het gemeenschappelijke thema voor mij de ontwikkeling van tools is. Mijn hele leven maak ik al tools waarmee andere mensen hun coole programma's schrijven. Maar ik was ook betrokken bij de ontwikkeling van besturingssystemen, stuurprogramma's, debuggers op kernelniveau, talen voor OS-ontwikkeling, die triviaal begonnen, maar na verloop van tijd steeds complexer werden. Maar het hoofdonderwerp is nog steeds de ontwikkeling van tools. Een groot deel van mijn leven speelde zich af tussen Azul en Sun, en het ging over Java. Maar toen ik met Big Data en Machine Learning begon, zette ik mijn mooie hoed weer op en zei: "Oh, nu hebben we een niet-triviaal probleem, en er zijn veel interessante dingen aan de hand en mensen die dingen doen." Dit is een geweldig ontwikkelingspad om te volgen.

Ja, ik hou echt van gedistribueerd computergebruik. Mijn eerste baan was als student in C, bij een reclameproject. Dit was gedistribueerd computergebruik op Zilog Z80-chips die gegevens verzamelden voor analoge OCR, geproduceerd door een echte analoge analysator. Het was een cool en volkomen gek onderwerp. Maar er waren problemen, een deel werd niet correct herkend, dus moest je er een foto uit halen en deze laten zien aan iemand die al met zijn ogen kon lezen en kon rapporteren wat er stond, en daarom waren er banen met gegevens, en deze banen hadden hun eigen taal. Er was een backend die dit allemaal verwerkte - Z80's draaiden parallel met draaiende vt100-terminals - één per persoon, en er was een parallel programmeermodel op de Z80. Een gemeenschappelijk stukje geheugen dat door alle Z80's binnen een sterconfiguratie wordt gedeeld; De backplane werd ook gedeeld en de helft van het RAM-geheugen werd gedeeld binnen het netwerk, en de andere helft was privé of ging naar iets anders. Een betekenisvol complex parallel gedistribueerd systeem met gedeeld... semi-gedeeld geheugen. Wanneer was dit... Ik kan het me niet eens herinneren, ergens halverwege de jaren 80. Heel lang geleden. 
Ja, laten we aannemen dat 30 jaar behoorlijk lang geleden is. Problemen met betrekking tot gedistribueerd computergebruik bestaan ​​al geruime tijd; mensen zijn al lang in oorlog met Beowulf-clusters. Dergelijke clusters zien eruit als... Bijvoorbeeld: er is Ethernet en je snelle x86 is verbonden met dit Ethernet, en nu wil je nep gedeeld geheugen krijgen, omdat niemand toen gedistribueerde computercodering kon doen, het was te moeilijk en daarom was er was nep gedeeld geheugen met beveiligingsgeheugenpagina's op x86, en als je naar deze pagina schreef, vertelden we andere processors dat als ze toegang zouden krijgen tot hetzelfde gedeelde geheugen, het van jou zou moeten worden geladen, en dus zoiets als een protocol voor ondersteuning cache-coherentie verscheen en software hiervoor. Interessant concept. Het echte probleem was natuurlijk iets anders. Dit werkte allemaal, maar je kreeg al snel prestatieproblemen, omdat niemand de prestatiemodellen op een voldoende goed niveau begreep - welke geheugentoegangspatronen er waren, hoe je ervoor kon zorgen dat de knooppunten elkaar niet eindeloos pingden, enzovoort.

Wat ik in H2O bedacht, is dat het de ontwikkelaars zelf zijn die verantwoordelijk zijn voor het bepalen waar parallellisme verborgen is en waar niet. Ik bedacht een codeermodel dat het schrijven van hoogwaardige code eenvoudig en eenvoudig maakte. Maar het schrijven van langzaam werkende code is moeilijk, het ziet er slecht uit. Je moet serieus proberen langzame code te schrijven, je zult niet-standaardmethoden moeten gebruiken. De remcode is op het eerste gezicht zichtbaar. Als gevolg hiervan schrijf je meestal code die snel werkt, maar je moet bedenken wat je moet doen in het geval van gedeeld geheugen. Dit alles is gekoppeld aan grote arrays en het gedrag daar is vergelijkbaar met niet-vluchtige grote arrays in parallel Java. Ik bedoel, stel je voor dat twee threads naar een parallelle array schrijven, de ene wint en de andere verliest dienovereenkomstig, en je weet niet welke welke is. Als ze niet vluchtig zijn, kan de volgorde zijn wat je maar wilt - en dit werkt heel goed. Mensen geven echt om de volgorde van de bewerkingen, ze plaatsen vluchtige gegevens op de juiste plaatsen en verwachten geheugengerelateerde prestatieproblemen op de juiste plaatsen. Anders zouden ze simpelweg code schrijven in de vorm van lussen van 1 tot N, waarbij N enkele biljoenen is, in de hoop dat alle complexe gevallen automatisch parallel zullen lopen - en daar werkt het niet. Maar in H2O is dit noch Java, noch Scala; je kunt het als “Java minus min” beschouwen als je wilt. Dit is een zeer duidelijke programmeerstijl en lijkt op het schrijven van eenvoudige C- of Java-code met lussen en arrays. Maar tegelijkertijd kan het geheugen in terabytes worden verwerkt. Ik gebruik nog steeds H2O. Ik gebruik het van tijd tot tijd in verschillende projecten - en het is nog steeds het snelste, tientallen keren sneller dan zijn concurrenten. Als je Big Data gebruikt met kolomgegevens, is het heel moeilijk om H2O te verslaan.

Technische uitdagingen

Andrew: Wat is je grootste uitdaging in je hele carrière geweest?

klif: Bespreken we het technische of niet-technische deel van de kwestie? Ik zou zeggen dat de grootste uitdagingen niet van technische aard zijn. 
Wat de technische uitdagingen betreft. Ik heb ze gewoon verslagen. Ik weet niet eens wat de grootste was, maar er waren een paar behoorlijk interessante die behoorlijk wat tijd vergden, mentale strijd. Toen ik naar Sun ging, wist ik zeker dat ik een snelle compiler zou maken, en een stel senioren zeiden als antwoord dat het me nooit zou lukken. Maar ik volgde dit pad, schreef een compiler naar de registerallocator, en het ging behoorlijk snel. Het was net zo snel als het moderne C1, maar de allocator was toen veel langzamer, en achteraf gezien was het een groot datastructuurprobleem. Ik had het nodig om een ​​grafische registerallocator te schrijven en ik begreep het dilemma tussen code-expressiviteit en snelheid niet, dat in die tijd bestond en erg belangrijk was. Het bleek dat de datastructuur meestal groter is dan de cachegrootte op x86s van die tijd, en daarom, als ik aanvankelijk aannam dat de registerallocator 5-10 procent van de totale jittertijd zou uitwerken, dan bleek het in werkelijkheid zo te zijn 50 procent.

Naarmate de tijd verstreek, werd de compiler schoner en efficiënter, stopte hij in steeds meer gevallen met het genereren van vreselijke code, en begonnen de prestaties steeds meer te lijken op wat een C-compiler produceert. Tenzij je natuurlijk wat onzin schrijft dat zelfs C niet versnelt. . Als u code zoals C schrijft, krijgt u in meer gevallen prestaties zoals C. En hoe verder je ging, hoe vaker je code kreeg die asymptotisch samenviel met niveau C, de registerallocator begon op iets compleets te lijken... ongeacht of je code snel of langzaam werkt. Ik bleef aan de allocator werken om betere selecties te maken. Hij werd langzamer en langzamer, maar presteerde steeds beter in gevallen waarin niemand anders het hoofd kon bieden. Ik zou in een registerallocator kunnen duiken, daar een maand werk kunnen begraven, en plotseling zou de hele code 5% sneller worden uitgevoerd. Dit gebeurde keer op keer en de registertoewijzer werd een soort kunstwerk - iedereen vond het geweldig of haatte het, en mensen van de academie stelden vragen over het onderwerp "waarom wordt alles op deze manier gedaan", waarom niet lijn scannen, en wat is het verschil. Het antwoord is nog steeds hetzelfde: een allocator gebaseerd op het tekenen van grafieken plus zeer zorgvuldig werken met de buffercode is gelijk aan een overwinningswapen, de beste combinatie die niemand kan verslaan. En dit is nogal een niet voor de hand liggend iets. Al het andere dat de compiler daar doet, zijn redelijk goed bestudeerde dingen, hoewel ze ook naar het niveau van de kunst zijn gebracht. Ik deed altijd dingen die van de compiler een kunstwerk moesten maken. Maar niets van dit alles was iets bijzonders, behalve de registerallocator. De truc is om voorzichtig te zijn bezuinigen onder belasting en als dit gebeurt (ik kan het in meer detail uitleggen als je geïnteresseerd bent), betekent dit dat je agressiever kunt inlineen, zonder het risico dat je over een knik in het prestatieschema valt. In die tijd waren er een aantal volwaardige compilers, vol met snuisterijen en fluitjes, die registertoewijzers hadden, maar niemand anders kon het doen.

Het probleem is dat als je methoden toevoegt die onderhevig zijn aan inlining, het vergroten en verkleinen van het inlininggebied, de set gebruikte waarden onmiddellijk het aantal registers overtreft en je ze moet knippen. Het kritieke niveau komt meestal wanneer de allocator het opgeeft, en de ene goede kandidaat voor een lekkage de andere waard is, je verkoopt een aantal over het algemeen wilde dingen. De waarde van inlining hier is dat je een deel van de overhead, overhead voor bellen en opslaan kwijtraakt, je de waarden binnenin kunt zien en deze verder kunt optimaliseren. De kosten van inlining zijn dat er een groot aantal live-waarden wordt gevormd, en als uw registerallocator meer dan nodig opbrandt, verliest u onmiddellijk. Daarom hebben de meeste allocators een probleem: wanneer inlining een bepaalde lijn overschrijdt, begint alles in de wereld te worden bezuinigd en kan de productiviteit door het toilet worden gespoeld. Degenen die de compiler implementeren, voegen wat heuristieken toe: bijvoorbeeld om te stoppen met inlinen, te beginnen met een voldoende grote omvang, aangezien toewijzingen alles zullen verpesten. Zo ontstaat er een knik in de prestatiegrafiek - jij inline, inline, de prestatie groeit langzaam - en dan boem! – het valt als een snelle jack naar beneden omdat je te veel hebt gelined. Zo werkte alles vóór de komst van Java. Java vereist veel meer inline, dus moest ik mijn allocator veel agressiever maken, zodat deze vlak wordt in plaats van crasht, en als je te veel inline inline, begint deze te morsen, maar dan komt het moment van 'niet meer morsen' nog steeds. Dit is een interessante observatie en het kwam zomaar uit het niets bij me op, niet voor de hand liggend, maar het heeft goed uitgepakt. Ik begon met agressieve inlining en het bracht me naar plaatsen waar Java- en C-prestaties naast elkaar werken. Ze komen heel dicht bij elkaar: ik kan Java-code schrijven die aanzienlijk sneller is dan C-code en dat soort dingen, maar over het algemeen zijn ze in het grote geheel ongeveer vergelijkbaar. Ik denk dat een deel van deze verdienste de registerallocator is, waardoor ik zo stom mogelijk kan inlineen. Ik zet gewoon alles wat ik zie op een rij. De vraag hier is of de allocator goed werkt, of het resultaat intelligent werkende code is. Dit was een grote uitdaging: dit allemaal begrijpen en laten werken.

Een beetje over registertoewijzing en multi-cores

Vladimir: Problemen als registertoewijzing lijken een eeuwig, eindeloos onderwerp. Ik vraag me af of er ooit een idee is geweest dat veelbelovend leek en vervolgens in de praktijk faalde?

klif: Zeker! Registertoewijzing is een gebied waarin u een aantal heuristieken probeert te vinden om een ​​NP-volledig probleem op te lossen. En een perfecte oplossing kun je nooit bereiken, toch? Dit is simpelweg onmogelijk. Kijk, Ahead of Time-compilatie - het werkt ook slecht. Het gesprek gaat hier over enkele gemiddelde gevallen. Over typische prestaties, zodat u iets kunt gaan meten waarvan u denkt dat het goede typische prestaties zijn - u werkt er tenslotte aan om het te verbeteren! Registertoewijzing is een onderwerp dat helemaal over prestaties gaat. Zodra je het eerste prototype hebt, werkt het en schildert het wat nodig is, het prestatiewerk begint. Je moet goed leren meten. Waarom is het belangrijk? Als je duidelijke gegevens hebt, kun je naar verschillende gebieden kijken en zien: ja, hier heeft het geholpen, maar daar ging alles kapot! Er komen een paar goede ideeën naar voren, je voegt nieuwe heuristieken toe en plotseling begint alles gemiddeld een beetje beter te werken. Of hij start niet. Ik heb een aantal gevallen gehad waarin we vochten voor de prestatie van vijf procent die onze ontwikkeling onderscheidde van de vorige allocator. En elke keer ziet het er zo uit: ergens win je, ergens verlies je. Als u over goede prestatieanalysetools beschikt, kunt u de verliezende ideeën vinden en begrijpen waarom ze mislukken. Misschien is het de moeite waard om alles te laten zoals het is, of misschien een serieuzere aanpak te kiezen voor het verfijnen, of eropuit te gaan en iets anders te repareren. Het zijn een hele hoop dingen! Ik heb deze coole hack gemaakt, maar ik heb deze ook nodig, en deze, en deze - en hun totale combinatie levert enkele verbeteringen op. En eenlingen kunnen falen. Dit is de aard van prestatiewerk bij NP-volledige problemen.

Vladimir: Je krijgt het gevoel dat zaken als het schilderen in allocators een probleem zijn dat al is opgelost. Nou, het is voor jou beslist, afgaande op wat je zegt, dus is het het dan wel waard...

klif: Het is niet als zodanig opgelost. Jij bent het die het moet omzetten in “opgelost”. Er zijn moeilijke problemen en die moeten worden opgelost. Zodra dit is gebeurd, is het tijd om aan de productiviteit te werken. U moet dit werk dienovereenkomstig benaderen: benchmarks uitvoeren, statistieken verzamelen, situaties uitleggen waarin, toen u terugkeerde naar een vorige versie, uw oude hack weer begon te werken (of omgekeerd, stopte). En geef niet op totdat je iets bereikt hebt. Zoals ik al zei: als er coole ideeën zijn die niet werken, maar op het gebied van de toewijzing van ideeënregisters is het ongeveer eindeloos. U kunt bijvoorbeeld wetenschappelijke publicaties lezen. Hoewel dit gebied nu veel langzamer begint te bewegen en duidelijker is geworden dan in zijn jeugd. Er zijn echter talloze mensen die op dit gebied werken en al hun ideeën zijn het proberen waard, ze wachten allemaal in de coulissen. En je kunt pas zeggen hoe goed ze zijn als je ze probeert. Hoe goed integreren ze met al het andere in uw allocator, omdat een allocator veel dingen doet, en sommige ideeën niet zullen werken in uw specifieke allocator, maar in een andere allocator wel gemakkelijk. De belangrijkste manier om te winnen voor de allocator is door het langzame spul buiten het hoofdpad te trekken en het te dwingen zich te splitsen langs de grenzen van de langzame paden. Dus als je een GC wilt uitvoeren, kies dan het langzame pad, deoptimaliseer, gooi een uitzondering, al dat soort dingen - je weet dat deze dingen relatief zeldzaam zijn. En ze zijn echt zeldzaam, heb ik gecontroleerd. Je doet extra werk en het neemt veel van de beperkingen op deze langzame paden weg, maar dat maakt niet zoveel uit, want ze zijn langzaam en worden zelden bewandeld. Een nulwijzer bijvoorbeeld: dat gebeurt nooit, toch? Je hebt verschillende paden nodig voor verschillende dingen, maar deze mogen de belangrijkste niet verstoren. 

Vladimir: Wat denk je van multi-cores, als er duizenden cores tegelijk zijn? Is dit een nuttig ding?

klif: Het succes van de GPU laat zien dat deze behoorlijk nuttig is!

Vladimir: Ze zijn behoorlijk gespecialiseerd. Hoe zit het met verwerkers voor algemene doeleinden?

klif: Nou, dat was het bedrijfsmodel van Azul. Het antwoord kwam terug in een tijdperk waarin mensen echt van voorspelbare prestaties hielden. Het was destijds moeilijk om parallelle code te schrijven. Het H2O-coderingsmodel is zeer schaalbaar, maar het is geen model voor algemene doeleinden. Misschien iets algemener dan bij gebruik van een GPU. Hebben we het over de complexiteit van het ontwikkelen van zoiets of de complexiteit van het gebruik ervan? Azul heeft me bijvoorbeeld een interessante les geleerd, die nogal niet voor de hand ligt: ​​kleine caches zijn normaal. 

De grootste uitdaging in het leven

Vladimir: Hoe zit het met niet-technische uitdagingen?

klif: De grootste uitdaging was niet om... aardig en aardig te zijn voor mensen. En als gevolg daarvan bevond ik me voortdurend in extreem conflictsituaties. Degenen waarvan ik wist dat er dingen mis gingen, maar ik wist niet hoe ik verder moest met die problemen en kon ze niet aan. Op deze manier ontstonden veel langetermijnproblemen, die tientallen jaren aanhielden. Het feit dat Java C1- en C2-compilers heeft, is hier een direct gevolg van. Ook het feit dat er tien jaar op rij geen multi-level compilatie in Java plaatsvond, is een direct gevolg. Het is duidelijk dat we een dergelijk systeem nodig hadden, maar het is niet duidelijk waarom het niet bestond. Ik had problemen met één ingenieur... of een groep ingenieurs. Er was eens, toen ik bij Sun begon te werken, ik... Oké, niet alleen dan, ik heb over het algemeen altijd mijn eigen mening over alles. En ik dacht dat het waar was dat je deze waarheid van jezelf gewoon kon nemen en het rechtstreeks kon vertellen. Vooral omdat ik meestal verrassend gelijk had. En als je deze aanpak niet leuk vindt... vooral als je duidelijk ongelijk hebt en onzin doet... Over het algemeen kunnen maar weinig mensen deze vorm van communicatie tolereren. Hoewel sommigen dat wel zouden kunnen, zoals ik. Ik heb mijn hele leven gebouwd op meritocratische principes. Als je me iets verkeerds laat zien, draai ik me meteen om en zeg: je zei onzin. Tegelijkertijd bied ik natuurlijk mijn excuses aan en zo, ik zal de eventuele verdiensten noteren en andere correcte acties ondernemen. Aan de andere kant heb ik schrikbarend gelijk als het gaat om een ​​schrikbarend groot percentage van de totale tijd. En het werkt niet zo goed in relaties met mensen. Ik probeer niet aardig te zijn, maar ik stel de vraag botweg. “Dit zal nooit werken, want één, twee en drie.” En ze zeiden: "Oh!" Er waren nog andere gevolgen die waarschijnlijk beter konden worden genegeerd: bijvoorbeeld de gevolgen die leidden tot een scheiding van mijn vrouw en tien jaar depressie daarna.

Uitdaging is een worsteling met mensen, met hun perceptie van wat je wel of niet kunt doen, wat belangrijk is en wat niet. Er waren veel uitdagingen op het gebied van codeerstijl. Ik schrijf nog steeds veel code, en in die tijd moest ik zelfs langzamer gaan werken omdat ik te veel parallelle taken deed en ze slecht uitvoerde, in plaats van me op één te concentreren. Terugkijkend heb ik de helft van de code geschreven voor het Java JIT-commando, het C2-commando. De volgende snelste codeur schreef half zo langzaam, de volgende half zo langzaam, en het was een exponentiële daling. De zevende persoon in deze rij was heel, heel langzaam - dat gebeurt altijd! Ik heb veel code aangeraakt. Ik keek naar wie wat schreef, zonder uitzondering, ik staarde naar hun code, beoordeelde ze allemaal en bleef nog steeds zelf meer schrijven dan wie dan ook. Deze aanpak werkt niet zo goed bij mensen. Sommige mensen vinden dit niet leuk. En als ze er niet mee om kunnen gaan, ontstaan ​​er allerlei klachten. Ik kreeg bijvoorbeeld ooit te horen dat ik moest stoppen met coderen omdat ik te veel code aan het schrijven was en dat het team in gevaar bracht, en het klonk allemaal als een grap: kerel, als de rest van het team verdwijnt en ik code blijf schrijven, dan Ik verlies slechts halve teams. Aan de andere kant, als ik code blijf schrijven en je verliest de helft van het team, dan klinkt dat als heel slecht management. Ik heb er nooit echt over nagedacht, er nooit over gesproken, maar het zat nog steeds ergens in mijn hoofd. De gedachte draaide in mijn achterhoofd: "Houden jullie me allemaal voor de gek?" Het grootste probleem was dus ikzelf en mijn relaties met mensen. Nu begrijp ik mezelf veel beter, ik ben lange tijd teamleider geweest voor programmeurs en nu zeg ik direct tegen mensen: weet je, ik ben wie ik ben, en jij zult met mij te maken krijgen - is het goed als ik blijf staan hier? En toen ze ermee aan de slag gingen, werkte alles. In feite ben ik noch slecht, noch goed, ik heb geen slechte bedoelingen of egoïstische ambities, het is gewoon mijn essentie, en ik moet er op de een of andere manier mee leven.

Andrew: Onlangs begon iedereen te praten over zelfbewustzijn voor introverte mensen, en zachte vaardigheden in het algemeen. Wat kun je hierover zeggen?

klif: Ja, dat was het inzicht en de les die ik leerde uit mijn scheiding van mijn vrouw. Wat ik van de scheiding heb geleerd, is mezelf begrijpen. Zo begon ik andere mensen te begrijpen. Begrijp hoe deze interactie werkt. Dit leidde tot ontdekkingen de een na de ander. Er was een besef van wie ik ben en wat ik vertegenwoordig. Wat ben ik aan het doen: óf ik ben bezig met de taak, óf ik vermijd conflicten, of iets anders - en dit niveau van zelfbewustzijn helpt echt om mezelf onder controle te houden. Hierna gaat alles veel gemakkelijker. Eén ding dat ik niet alleen bij mezelf, maar ook bij andere programmeurs heb ontdekt, is het onvermogen om gedachten onder woorden te brengen als je onder emotionele stress staat. Je zit daar bijvoorbeeld te coderen, in een staat van flow, en dan komen ze naar je toe rennen en beginnen hysterisch te schreeuwen dat er iets kapot is en dat er nu extreme maatregelen tegen je zullen worden genomen. En je kunt geen woord zeggen omdat je in een staat van emotionele stress verkeert. Met de opgedane kennis kun je je voorbereiden op dit moment, overleven en overgaan tot een retraiteplan, waarna je iets kunt doen. Dus ja, als je begint te beseffen hoe het allemaal werkt, is het een enorme levensveranderende gebeurtenis. 
Zelf kon ik de juiste woorden niet vinden, maar ik herinnerde me de volgorde van acties. Het punt is dat deze reactie zowel fysiek als verbaal is, en dat je ruimte nodig hebt. Wat een ruimte, in de Zen-zin. Dit is precies wat uitgelegd moet worden, en ga dan onmiddellijk opzij – puur fysiek, stap weg. Als ik verbaal stil blijf, kan ik de situatie emotioneel verwerken. Terwijl de adrenaline je hersenen bereikt en je in de vecht- of vluchtmodus schakelt, kun je niets meer zeggen, nee - nu ben je een idioot, een opzwepende ingenieur, niet in staat tot een fatsoenlijk antwoord of zelfs maar tot het stoppen van de aanval, en de aanvaller is vrij om keer op keer aan te vallen. Je moet eerst weer jezelf worden, de controle terugkrijgen, uit de ‘vecht-of-vlucht’-modus komen.

En daarvoor hebben we verbale ruimte nodig. Gewoon vrije ruimte. Als je überhaupt iets zegt, dan kun je precies dat zeggen, en dan echt ‘ruimte’ voor jezelf gaan zoeken: ga wandelen in het park, sluit jezelf op in de douche – het maakt niet uit. Het belangrijkste is om tijdelijk de verbinding met die situatie te verbreken. Zodra je minstens een paar seconden uitschakelt, keert de controle terug, begin je nuchter na te denken. 'Oké, ik ben geen idioot, ik doe geen domme dingen, ik ben een behoorlijk nuttig persoon.' Als je jezelf eenmaal hebt kunnen overtuigen, is het tijd om door te gaan naar de volgende fase: begrijpen wat er is gebeurd. Je werd aangevallen, de aanval kwam van een plek waar je hem niet had verwacht, het was een oneerlijke, gemene hinderlaag. Dit is slecht. De volgende stap is begrijpen waarom de aanvaller dit nodig had. Echt waarom? Misschien omdat hij zelf woedend is? Waarom is hij boos? Bijvoorbeeld omdat hij zichzelf heeft verpest en de verantwoordelijkheid niet kan aanvaarden? Dit is de manier om zorgvuldig met de hele situatie om te gaan. Maar dit vereist manoeuvreerruimte, verbale ruimte. De allereerste stap is het verbreken van het verbale contact. Vermijd discussie met woorden. Annuleer het, loop zo snel mogelijk weg. Als het een telefoongesprek is, hang dan gewoon op. Dit is een vaardigheid die ik heb geleerd door met mijn ex-vrouw te communiceren. Als het gesprek niet goed verloopt, zeg dan gewoon 'tot ziens' en hang op. Vanaf de andere kant van de telefoon: “blah blah blah”, jij antwoordt: “ja, doei!” en ophangen. Je beëindigt gewoon het gesprek. Vijf minuten later, wanneer het vermogen om verstandig te denken bij je terugkeert, ben je een beetje afgekoeld, het wordt mogelijk om over alles na te denken, wat er is gebeurd en wat er daarna zal gebeuren. En begin met het formuleren van een doordachte reactie, in plaats van alleen maar uit emotie te reageren. Voor mij was de doorbraak in zelfbewustzijn juist het feit dat ik bij emotionele stress niet kan praten. Uit deze toestand komen, nadenken en plannen maken hoe u kunt reageren en problemen kunt compenseren - dit zijn de juiste stappen in het geval dat u niet kunt praten. De gemakkelijkste manier is om weg te rennen van de situatie waarin emotionele stress zich manifesteert en eenvoudigweg te stoppen met deelnemen aan deze stress. Daarna kun je denken, als je kunt denken, kun je spreken, enzovoort.

Trouwens, in de rechtbank probeert de advocaat van de tegenpartij je dit aan te doen - nu is het duidelijk waarom. Omdat hij het vermogen heeft om je zo te onderdrukken dat je bijvoorbeeld je naam niet eens kunt uitspreken. In een heel reële zin zul je niet kunnen praten. Als dit u overkomt, en als u weet dat u zich op een plaats zult bevinden waar verbale strijd woedt, op een plaats zoals de rechtbank, dan kunt u met uw advocaat meegaan. De advocaat zal voor je opkomen en de verbale aanval stoppen, en zal dit op een volledig legale manier doen, en de verloren Zen-ruimte zal naar je terugkeren. Ik heb bijvoorbeeld een paar keer mijn familie moeten bellen, de rechter was er heel vriendelijk over, maar de advocaat van de tegenpartij schreeuwde en schreeuwde tegen mij, ik kon er niet eens een woord tussen krijgen. In deze gevallen werkt het inschakelen van een mediator voor mij het beste. De bemiddelaar stopt al deze druk die in een continue stroom op je uitstort, je vindt de nodige Zen-ruimte, en daarmee keert het vermogen om te spreken terug. Dit is een heel kennisgebied waarin veel te bestuderen is, veel te ontdekken in jezelf, en dit alles verandert in strategische beslissingen op hoog niveau die voor verschillende mensen verschillend zijn. Sommige mensen hebben de hierboven beschreven problemen niet; meestal hebben mensen die professionele verkopers zijn deze niet. Al deze mensen die hun brood verdienen met woorden – beroemde zangers, dichters, religieuze leiders en politici, ze hebben altijd iets te zeggen. Zij hebben zulke problemen niet, maar ik wel.

Andrew: Het was... onverwacht. Geweldig, we hebben al veel gepraat en het is tijd om dit interview te beëindigen. Wij zullen elkaar zeker ontmoeten op de conferentie en zullen deze dialoog kunnen voortzetten. Tot ziens bij Hydra!

U kunt uw gesprek met Cliff voortzetten op de Hydra 2019-conferentie, die wordt gehouden op 11-12 juli 2019 in St. Petersburg. Hij komt met een rapport "De Azul Hardware Transactional Memory-ervaring". Er kunnen kaartjes worden gekocht op de officiële website.

Bron: www.habr.com

Voeg een reactie