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 multiple successful exits. Cliff wrote his first compiler at 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 became one of the factors for Java's emergence as one of the main 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. Overall, Cliff has worked on all aspects of the JVM.
This post on Habrahabr is a large interview with Cliff. We will discuss the following topics:
- Transitioning to low-level optimizations
- How to conduct large refactoring
- Cost model
- Learning low-level optimizations
- Practical examples of performance improvements
- Why create your own programming language
- Career of a performance engineer
- Technical challenges
- A bit about register allocation and multithreading
- The biggest challenge in life
The interviewers are:
- Andrey Satarin from Amazon Web Services. He has worked in a variety of projects throughout his career: 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. Interested in testing large-scale backend and distributed systems.
- Vladimir Sitnikov from Netcracker. He has worked for ten years on the performance and scalability of NetCracker OS — software used by telecom operators for automating network management processes and network equipment. He is passionate about Java and Oracle Database performance. Author of over a dozen performance improvements in the official PostgreSQL JDBC driver.
Transitioning to low-level optimizations
Andrey: You are a well-known figure in the world of JIT compilation, Java, and performance work in general, right?
Cliff: Absolutely!
Andrey: Let's start with general questions about working on performance. What do you think about choosing between high-level and low-level optimizations like working at the CPU level?
Cliff: Tukaj je vse preprosto. Najhitrejša koda je tista, ki nikoli ne zažene. Zato je treba vedno začeti z visokim nivojem, delati na algoritmih. Boljša O-notacija premaga slabšo O-notacijo, razen če se ne vmešajo velike konstance. Nizkonivojski elementi pridejo na vrsto šele na koncu. Običajno, če ste optimizirali preostanek sklada dovolj dobro in ostane še kaj zanimivega – to je nizka raven. Kako pa začeti z visokim nivojem? Kako vedeti, da ste opravili dovolj dela na visoki ravni? No… nikakor. Ni pripravljenih receptov. Treba je razumeti problem, rešiti, kaj nameravate narediti (da ne naredite nepotrebnih korakov), in šele nato lahko vzamete profiler, ki vam lahko pove kaj koristnega. V nekem trenutku sami ugotovite, da ste se znebili nepotrebnih stvari in prišel je čas za fino nastavitev nizke ravni. To je brez dvoma posebna vrsta umetnosti. Veliko ljudi dela nepotrebne stvari, a se gibljejo tako hitro, da nimajo časa skrbeti za zmogljivost. Vendar to velja, dokler se ne pojavi nujna težava. Običajno 99% časa nikogar ne zanima, s čim se ukvarjam, do trenutka, ko na kritični poti ne pride do pomembne stvari, za katero je nekomu mar. In takrat vsi začnejo spraševati: »Zakaj ni bilo od začetka popolno?« Na splošno vedno obstaja kaj, kar je treba izboljšati v zmogljivosti. Vendar pa 99% časa nimate nikakršnih namigov! Preprosto poskušate nekaj spraviti v delo in ob tem razumete, kaj je pomembno. Nikoli ne morete vnaprej vedeti, kateri kos je treba narediti popoln, zato morate v bistvu biti popolni v vsem. Kar je nemogoče, in tako ne delate. Vedno je veliko stvari, ki jih je treba popraviti – in to je povsem normalno.
How to conduct large refactoring
Andrey: Kako delate na zmogljivosti? To je vendar prečkalna težava. Na primer, ali ste se kdaj ukvarjali s problemi, ki izhajajo iz prekrivanja velike količine že obstoječe funkcionalnosti?
Cliff: Trudim se da to izbegnem. Ako znam da će performanse postati problem, razmišljam o tome pre nego što počnem da pišem kod, posebno o strukturama podataka. No, često sve to otkrijete kasnije. I tada morate preći na ekstremne mere i uraditi ono što nazivam „prepisuj i vladaj”: treba uhvatiti dovoljno veliki deo. Deo koda takođe morate prepisati zbog problema sa performansama ili iz nekog drugog razloga. Koji god razlog za prepisivanje koda bio, skoro uvek je bolje prepisati veći deo nego manji. U tom trenutku, svi počinju da se boje: „oh, Bože, ne možemo dirati toliko koda!”. Ali, zapravo, takav pristup obično funkcioniše mnogo bolje. Treba odmah uhvatiti veliki problem, obeležiti ga velikim krugom i reći: sve unutar kruga, ja ću prepisati. Granica je mnogo manja od sadržaja unutar nje koji treba zameniti. I ako takvo ocrtavanje granica omogući savršeno obavljanje posla unutar – ruke su ti slobodne, radi šta želiš. Kada shvatiš problem, proces prepisivanja ide mnogo lakše, zato uzmi veliki zalogaj!
U isto vreme, kada prepisuješ veliki deo i shvatiš da će performanse postati problem, možeš odmah početi da brineš o tome. Obično se to pretvara u jednostavne stvari poput „ne kopiraj podatke, upravljaj podacima što je jednostavnije moguće, smanjite ih”. U velikim prepisivanjima, postoje standardni načini za poboljšanje performansi. I oni se skoro uvek vrte oko podataka.
Cost model
Andrey: U jednom od podkasta ste govorili o modelima troškova u kontekstu performansi. Možete li objasniti na šta ste mislili?
Cliff: Seveda. Rojen sem bil v času, ko je bila zmogljivost procesorja izjemno pomembna. In ta doba se ponovno vrača - usoda ni brez ironije. Začel sem živeti v času osmibitnih računalnikov, moj prvi računalnik je delal z 256 bajti. Točno tako, bajti. Vse je bilo zelo majhno. Moral si šteti ukaze in takoj ko smo začeli napredovati po lestvici programskih jezikov, so jeziki prevzeli vse več obveznosti. Bil je Assembler, nato Basic, nato C, in C je prevzel naloge z mnogimi podrobnostmi, kot sta dodelitev registrov in optimizacija ukazov. Vse to je bilo precej jasno in če sem naredil kazalec na instanco spremenljivke, sem dobil load, in za ta ukaz je bila cena znana. Strojna oprema da določen broj strojnih ciklov, tako da lahko hitro izračunaš čas izvajanja različnih stvari samo s seštevanjem vseh ukazov, ki jih nameravaš zagnati. Vsak compare/test/branch/call/load/store se je lahko seštel in si si rekel: tu je tvoj čas izvajanja. Ko se ukvarjaš z izboljšanjem zmogljivosti, zagotovo opaziš, kakšne številke pripadajo majhnim vročim ciklom.
Toda takoj ko preklopiš na Java, Python in podobne zadeve, se hitro oddaljiš od nizkoločljivih strojnih komponent. Kakšna je cena klica getterja v Javi? Če je JIT v HotSpot vse pravilno , bo to load, če pa tega ni storil - bo to klic funkcije. Ker je klic v vročem ciklu, bo odpravil vse druge optimizacije v tem ciklu. Zato bo dejanska cena veliko višja. In takoj izgubiš sposobnost, da pogledaš na del kode in razumeš, koliko nas bo stalo izvajanje v smislu frekvence procesorja, uporabljene pomnilnika in predpomnilnika. Vse to postane zanimivo šele, ko se resno poglobiš v zmogljivost.
Trenutno smo se našli u situaciji gdje brzine procesora već više od deset godina gotovo da ne rastu. Stara vremena se vraćaju! Vi više ne možete računati na dobru jednonitnu izvedbu. No, ako se slučajno odlučite za paralelno računalstvo – to je nevjerojatno složeno, svi vas gledaju kao da ste James Bond. Deset puta bolji ishodi obično se javljaju na mjestima gdje je netko nešto propustio. Paralelnost zahtijeva puno posla. Da biste dobili to desetostruko poboljšanje, morate razumjeti model troškova. Što i koliko košta. A za to morate razumjeti kako se jezik uklapa u temeljni hardver.
Martin Thompson je pronašao odličnu riječ za svoj blog ! Potrebno je razumjeti što će hardver raditi, kako će to raditi i zašto uopće radi to što radi. Koristeći to, prilično je jednostavno početi brojati instrukcije i utvrditi kamo vrijeme izvršenja odlazi. Ako nemate odgovarajuću izobrazbu, samo tražite crnu mačku u tamnoj sobi. Konstantno vidim ljude koji optimiziraju performanse, a nemaju ni najmanju ideju što zapravo rade. Veoma se muče i ne napreduju nigdje. A kada preuzmem isti komad koda, ubacim nekoliko sitnih trikova i dobijem petostruko ili desetostruko poboljšanje, oni kažu: pa, to nije pošteno, znali smo da si bolji. Nevjerojatno. O čemu sam to... model troškova – to je o tome što kod pišete i kako brzo on u prosjeku radi u cijeloj slici.
Andrey: I kako takav volumen zadržati u glavi? To se postiže velikim iskustvom, zar ne? Gdje se to iskustvo stječe?
Cliff: Pa, svoje iskustvo stekao sam na prilično težak način. Programirao sam u Assembleru još u vrijeme kada se moglo razumjeti svaka pojedinačna instrukcija. Ovo zvuči glupo, ali od tada mi je u glavi, u sjećanju, zauvijek ostao skup instrukcija Z80. Ne sjećam se imena ljudi već minutu nakon razgovora, ali pamtim kod napisan prije 40 godina. Zabavno, izgleda kao sindrom „».
Learning low-level optimizations
Andrey: Postoji li neki jednostavniji način da se uđe u poslovanje?
Cliff: Da i ne. Oprema koju svi koristimo tokom ovog vremena nije se mnogo promenila. Svi koriste x86, osim pametnih telefona koji se oslanjaju na Arm. Ako se ne baviš nečim ozbiljnim u embedded svetu, koristiš sve isto. Dobro, idemo dalje. Uputstva se takođe vekovima nisu menjala. Treba da odeš i napišeš nešto na Assembleru. Malo, ali dovoljno da počneš da razumeš. Vi se smeškate, ali ja to govorim sasvim ozbiljno. Potrebno je razumeti odnos između jezika i hardvera. Posle toga, trebalo bi da kreneš, napišeš malo i napraviš mali igračka kompajler za mali igračka jezik. „Igračka“ znači da ga treba napraviti u razumnom vremenskom okviru. Može biti super jednostavan, ali mora generisati instrukcije. Akt generisanja instrukcija pomoći će razumevanju modela troškova za most između visoko-nivnog koda, na kojem svi pišu, i mašinskog koda koji se izvršava na hardveru. Ova korelacija urezat će se u umu dok pišeš kompajler. Čak i najjednostavniji kompajler. Nakon toga možeš početi da posmatraš Javu i uočiš da njen semantički razmak je mnogo dublji, i ulogu izgradnje mostova preko toga je mnogo teže. U Javi je mnogo teže shvatiti da li je naš most dobar ili loš, što će ga dovesti do propadanja ili ne. Ali ti je potrebna određena polazna tačka kada gledaš kod i razumeš: „aha, ovaj getter bi trebao da se inline-uje svaki put“. A onda se ispostavi da se to ponekad dešava, osim kada metoda postane prevelika i JIT počne da inline-uje sve. Performanse takvih mesta mogu se predvideti trenutno. Obično getteri dobro funkcionišu, ali onda gledaš velike vruće petlje i shvataš da tu plivaju neki pozivi funkcija čija svrha nije jasna. U tome i jeste problem sa sveprisutnom upotrebom gettera, razlog zbog kojeg se ne inline-ju – nije jasno da li je to getter. Ako imaš super malu bazu koda, možeš je jednostavno zapamtiti i kasnije reći: ovo je getter, a ovo je setter. U velikoj bazi koda svaka funkcija ima svoju priču koja, u suštini, nikome nije poznata. Profajler govori da smo izgubili 24% vremena na nekoj petlji i da bi shvatio šta ta petlja radi, moraš pogledati svaku funkciju unutar nje. Nemoguće je to razumeti bez proučavanja funkcije, što ozbiljno usporava proces razumevanja. Zato ja ne koristim gettere i settere, prešao sam na novi nivo!
Kje pridobiti model cen? No, lahko preberem kaj, seveda ... Ampak mislim, da je najboljši način ukrepanje. Narediti majhen prevajalnik in to bo najboljši način, da se zavedamo modela cen in ga umestimo v svoje misli. Majhen prevajalnik, ki bi bil uporaben za programiranje mikrovalovke - to je naloga za začetnika. Mislim, da ti bodo tvoje programske veščine že dovolj. Vse te stvari, kot je razčlenitev niza, ki bo kakšno algebraično izraze, pridobivanje navodil matematičnih operacij v pravilnem zaporedju, pridobitev pravih vrednosti iz registrov - vse to se lahko naredi zlahka. In medtem ko to počneš, se to vtisne v spomin. Mislim, da vsi vedo, s čem se ukvarja prevajalnik. In to bo dalo razumevanje modela cen.
Practical examples of performance improvements
Andrey: Na kaj še moramo biti pozorni pri delu na zmogljivosti?
Cliff: Strukture podatkov. Mimogrede, že dolgo nisem imel teh predavanj ... . Bila je zabavna, a je zahtevala toliko truda, pa imam vendarle tudi svoje življenje! No. Torej, na enem od velikih in zanimivih predavanj, "Kam gre vaša zmogljivost", sem študentom dal primer: dva in pol gigabajta finančnih podatkov, ki so jih prebrali iz CSV datoteke in nato je bilo treba izračunati število prodanih izdelkov. Običajni podatki o trgovanju. UDP paketi, spremenjeni v besedilni format, od 70-ih let naprej. Chicago Mercantile Exchange - različne stvari, kot so olje, koruza, sojini stroki in podobno. Treba je bilo prešteti te izdelke, število trgov, povprečno vrednost gibanja sredstev in blaga itd. To je razmeroma preprosta trgovinska matematika: najti kodo izdelka (to je 1-2 znaka v haš tabeli), pridobiti vsoto, jo dodati v en nabor trgov, dodati obseg, dodati vrednost in še nekaj drugih stvari. Zelo preprosta matematika. Igračka implementacija je bila zelo enostavna: vse je bilo v datoteki, prebral sem datoteko in se gibal po njej, razdelil posamezne zapise na Java nize, iskal potrebne stvari in sešteval po zgoraj opisani matematiki. In to deluje z neko majhno hitrostjo.
S tovrim pristupom je sve jasno, što se događa, i paralelno računanje ovde ne može pomoći, zar ne? Ispostavlja se da se petostruko povećanje performansi može postići samo izborom pravih struktura podataka. To čak iznenađuje i iskusne programere! U mom konkretном slučaju, fokus je bio na tome da ne treba raditi alokacije memorije u vrućem ciklusu. Pa, to nije cela istina, ali u suštini – ne treba alocirati 'jednom u X', kada je X dovoljno veliko. Kada je X dva i po gigabajta, ne treba alocirati ništa 'jednom po znaku', ili 'jednom po redu', ili 'jednom po polju', ništa u tom smislu. Tačno na to odlazi vreme. Kako to zapravo funkcioniše? Zamislite, ja pozivam String.split() ili BufferedReader.readLine(). Readline pravi string iz skupa bajtova koji je stigao kroz mrežu, jednom za svaku liniju, za svaku od stotina miliona linija. Uzimam ovaj string, analiziram ga i odbacujem. Zašto odbacujem – pa, već sam ga obradio, to je to. Dakle, za svaku bajt koji je pročitan iz tih 2.7G, biće upisani dva karaktera u stringu, to jest već 5.4G, i oni mi više nisu potrebni, tako da se odbacuju. Ako pogledamo propusnost memorije, učitavamo 2.7G, koji prolaze kroz memoriju i memorijsku magistralu u procesoru, i zatim duplo više odlazi u string koji leži u memoriji, i sve to se miješa prilikom kreiranja svake nove linije. Ali ja moram to pročitati, hardver to čita, čak i ako će sve kasnije biti prebrisan. I moram to zapisati, jer sam kreirao string i keševi su se napunili – keš ne može smestiti 2.7G. Stoga, za svaki očitani bajt, ja čitam još dva dodatna bajta i upisujem dva dodatna bajta, i na kraju imamo odnos 4:1 – u tom odnosu bespotrebno trošimo propusnost memorije. A ispostavlja se da ako radim String.split() – radim to daleko ne poslednji put, unutra može biti još 6-7 polja. Zato klasični kod za čitanje CSV-a sa naknadnim parsiranjem linija dovodi do gubitaka propusne trake memorije od oko 14:1 u odnosu na ono što biste zapravo želeli da imate. Ako se odbace te alokacije, može se postići petostruko ubrzanje.
In fact, it's not very complicated. If you look at the code from the right angle, it becomes quite straightforward as soon as you grasp the essence of the problem. You should never stop allocating memory: the issue is merely that you allocate something and it immediately dies, consuming vital resources in the process, which in this case is memory bandwidth. All of this results in a drop in performance. On x86, you generally need to actively burn CPU cycles, whereas here you’ve wasted all your memory much earlier. The solution is to reduce the number of allocations.
Another part of the problem is that if you run a profiler when the memory bandwidth is exhausted, right at that moment, you usually expect the cache to return because it's filled with garbage created by all those strings you just produced. Therefore, every load or store operation becomes slow, as they lead to cache misses— the entire cache becomes sluggish, waiting for the garbage to be cleared out. Consequently, the profiler will mainly show warm random noise spread across the entire cycle— there will be no distinct hot instruction or section in the code. Just noise. And if you look at the GC cycles, they will all be on the Young Generation and super fast—microseconds or milliseconds at most. This is because all that memory dies instantly. You're allocating billions of gigabytes, and it slices and slices and slices again. It all happens very quickly. So, there are cheap GC cycles, warm noise throughout the cycle, but we want to achieve a 5x acceleration. At this moment, something should click in your mind and resonate: "Why is this happening?!" Memory bandwidth overflow isn’t represented in a classic debugger; you need to run a performance hardware counter debugger and see it directly for yourself. However, indirectly you can suspect it from these three symptoms. The third symptom is when you look at what you allocate, ask the profiler, and it replies: "You created a billion strings, but the GC executed for free." Once this happens, you realize you've produced too many objects and burned through all the memory bandwidth. There is a way to figure this out, but it’s not obvious.
Težava je v strukturi podatkov: gola struktura, ki stoji za vsem, kar se dogaja, je prevelika, zaseda 2.7G na disku, zato je zelo nezaželeno narediti kopijo te stvari – želeli bi jo takoj naložiti iz omrežnega bajtnega predpomnilnika v registre, da je ne bi brali in zapisovali petkrat nazaj in naprej. Na žalost Java privzeto ne nudi takšne knjižnice v okviru JDK. Ampak to je przecież trivialno, ane? V bistvu gre za 5-10 vrstic kode, ki bodo izvedle lastnega predpomnjenega nalagalnika vrstic, ki ponavlja vedenje razreda vrstic, hkrati pa predstavlja ovitek okoli osnovnega bajtnega predpomnilnika. Posledično deluješ skoraj kot bi delal z vrsti, vendar v resnici premikajo pokazatelji na predpomnilnik, surovi bajti pa se nikamor ne kopirajo, tako da se znova in znova uporabljajo isti predpomnilniki, operacijski sistem pa je vesel, da prevzame naloge, za katere je namenjen, kot je skrito dvojno predpomnjenje teh bajtnih predpomnilnikov, ti pa več ne predeluješ neskončnega toka nepotrebnih podatkov. Mimogrede, razumete, da pri delu z GC zagotavlja, da vsako dodeljevanje pomnilnika ne bo vidno procesorju po zadnjem ciklu GC? Zato vse to ne more biti v predpomnilniku, in potem pride do 100%-garantiranega zadetka. Pri delu s kazalcem na x86 branje registra iz pomnilnika traja 1-2 cikla, in takoj, ko se to zgodi, plačaš, plačaš, plačaš, ker je celotna pomnilnik na – in to je strošek dodeljevanja pomnilnika. Pravi strošek.
Drugimi besedami, podatkovne strukture so tisto, kar je najtežje spremeniti. In ko enkrat spoznate, da ste izbrali napačno podatkovno strukturo, ki bo kasneje uničila učinkovitost, običajno zahteva veliko dela, da to spremenite, vendar, če tega ne storite, bo še slabše. Najprej morate razmišljati o podatkovnih strukturah, to je pomembno. Glavni strošek tukaj pade na kompleksne podatkovne strukture, ki jih začnemo uporabljati v slogu „kopiral sem podatkovno strukturo X v podatkovno strukturo Y, ker mi je Y bolj všeč v obliki“. Ampak operacija kopiranja (ki se zdi poceni) dejansko porabi pomnilniški pas in tukaj se skriva celotna izguba časa izvajanja. Če imam ogromno vrstico z JSON in jo želim spremeniti v strukturirano DOM drevo iz POJO ali kaj takega, bo operacija razčlenitve te vrstice in ustvarjanje POJO ter nato novo klicanje POJO pomenilo dodatne stroške – to ni poceni. Razen, če boste klicali POJO bistveno pogosteje kot vrstico. Približno, lahko namesto tega poskusite razvozlati vrstico in izvleči le tisto, kar potrebujete, ne da bi to pretvorili v kakršne koli POJO. Če se vse to dogaja na poti, kjer je potrebna najvišja učinkovitost, nobenih POJO, – morate nekako neposredno kopati v vrstici.
Why create your own programming language
Andrey: Rekli ste, da je za razumevanje modela stroškov potrebno napisati svoj majhen jezik...
Cliff: Ne jezik, ampak prevajalnik. Jezik in prevajalnik sta različni stvari. Najpomembnejša razlika je v vaši glavi.
Andrey: Mimogrede, kolikor vem, eksperimentirate pri ustvarjanju svojih jezikov. Zakaj?
Cliff: Ker ker jih lahko! Sem pol upokojen, zato je to moje hobiranje. Vse življenje sem uresničeval jezike nekoga drugega. Tudi veliko sem delal na slogu kodiranja. In tudi zato, ker vidim težave v drugih jezikih. Vidim, da so boljši načini za opravljanje običajnih nalog. In jih z veseljem uporabim. Preprosto me utrujajo težave, ki jih imam v sebi, v Javi, v Pythona, v katerem koli drugem jeziku. Zdaj pišem v React Native, JavaScript in Elm kot hobi, ki ni povezano z upokojitvijo, ampak z aktivnim delom. Tudi v Pythonu pišem in verjetno bom še naprej delal na strojni inteligenci za Java backende. Obstaja veliko priljubljenih jezikov, vsaka ima svoje zanimive značilnosti. Vsak je dober po svoje, in mogoče poskusiti združiti te funkcije. Tako da se ukvarjam z učenjem stvari, ki so mi zanimive, obnašanjem jezika, poskušam razviti smiselno semantiko. In zaenkrat mi gre dobro! Trenutno se borim s semantiko spomina, ker želim imeti takšen spomin kot v C in Javi, in izvesti močan model spomina in semantiko za nalaganja in shranjevanja. Hkrati pa želim imeti avtomatsko izpeljavo tipov kot v Haskell. Tukaj poskušam združiti Haskell-podobno izpeljavo tipov s spominom, ki deluje kot v C in Javi. S tem se ukvarjam zadnje 2-3 mesece, na primer.
Andrey: Če gradite jezik, ki jemlje najboljše vidike iz drugih jezikov, ste pomislili, da bi nekdo lahko naredil obratno: vzel vaše ideje in jih uporabil pri sebi?
Cliff: Prav tako nastajajo novi jeziki! Zakaj je Java podobna C? Ker je C imel dober sintaktični način, ki so ga vsi razumeli, in Java se je navdihnila pri tej sintaksi, dodala tipno varnost, preverjanje meja nizov, GC, poleg tega pa so izboljšali nekatere stvari iz C. Dodali so svoje. A močno so se navdihovali, mar ne? Vsi stojimo na ramah velikanov, ki so bili pred nami – tako se ustvarja napredek.
Andrey: Kolikor razumem, bo vaš jezik varen glede uporabe spomina. Ste pomislili, da bi implementirali nekaj takega kot je borrow checker iz Rust? Ste ga gledali, kakšno je vaše mnenje o njem?
Cliff: No, že pišem v C že celo večnost, s temi malloc in free, ter ročno upravljam življenjsko dobo. Veste, 90-95 % ročno upravljane življenjske dobe ima enako strukturo. In to je zelo, zelo boleče ukvarjati se s tem ročno. Rad bi, da bi prevajalnik preprosto povedal, kaj se dogaja in česa sem s svojimi dejanjimi dosegel. Za nekatere stvari to preverjanje zadolžitev stori kar samodejno. Poleg tega mora avtomatsko črpati informacije, vse razumeti in me niti ne obremeniti s potrebo po razlagi tega razumevanja. Mora izvesti vsaj lokalno analizo uhajanja, in šele če mu to ne uspe, je treba dodati oznake tipov, ki bodo opisovale življenjsko dobo – takšna shema je veliko zahtevnejša od preverjanja zadolžitev, ali sploh katerega koli obstoječega preverjevalnika pomnilnika. Izbira med 'vse je v redu' in 'nisem ničesar razumel' — ne, moralo bi biti kaj boljšega.
Kot sem človek, ki je napisal veliko kode v C, menim, da je podpora za samodejno upravljanje življenjske dobe ključnega pomena. Poleg tega me moti, kako močno Java uporablja pomnilnik, in moja glavna pritožba je glede GC. Pri dodeljevanju pomnilnika v Javi ti ne vrne pomnilnika, ki je bil lokalno uporabljen v zadnjem ciklu GC. V jezikih z natančnejšim upravljanjem pomnilnika to ni tako. Ko pokličeš malloc, dobiš pomnilnik, ki je običajno ravno bil uporabljen. Običajno delaš z pomnilnikom kakšne začasne stvari in ga takoj vrneš nazaj. In takoj se vrne v pool malloc-a in naslednji klic malloc-a jo spet potegne ven. Zato se dejanska poraba pomnilnika zmanjša na nabor aktivnih objektov v določenem trenutku, poleg puščanj. In če ne puščaš pomnilnika na neprimeren način, se večina pomnilnika usede v predpomnilnikih in procesorju, kar deluje hitro. Vendar zahteva veliko ročnega upravljanja pomnilnika z malloc in free, kjer moraš skrbeti za pravilno zaporedje in mesto klicanega. Rust to lahko sama pravilno upravlja in v mnogih primerih celo doseže večjo zmogljivost, saj se poraba pomnilnika zmanjšuje le na trenutne izračune – nasproti pričakovanju naslednjega cikla GC, ki bo osvobodil pomnilnik. Na koncu smo dobili zelo zanimiv način za izboljšanje zmogljivosti. In precej močno – v smislu, da sem se ukvarjal s takšnimi zadevami pri obdelavi podatkov za fintech, kar je omogočilo petkratno pospešitev. To je precej velika pospešitev, še posebej v svetu, kjer procesorji ne postajajo hitrejši, medtem ko še vedno čakamo na izboljšave.
Career of a performance engineer
Andrey: Rad bi še vprašal o karieri na splošno. Postali ste znani po delu na JIT v HotSpot, nato ste se preselili v Azul – in to je tudi podjetje, ki se ukvarja z JVM. Toda bolj ste se ukvarjali s strojno opremo kot s programsko opremo. Potem pa ste nenadoma prešli na Big Data in strojno učenje, nato pa na odkrivanje goljufij. Kako je do tega prišlo? To so zelo različna področja razvoja.
Cliff: Že dolgo se ukvarjam s programiranjem in sem se uspel izkazati na zelo različnih področjih. In ko ljudje rečejo: „Oh, ti si tisti, ki je naredil JIT za Javo!“, je to vedno smešno. Pred tem sem se ukvarjal z klonom PostScript – jezika, ki ga je Apple nekoč uporabljal za svoje laserske tiskalnike. Pred tem sem delal implementacijo jezika Forth. Mislim, da je skupna tema zame razvoj orodij. Vse življenje delam orodja, s katerimi drugi ljudje pišejo svoje velike programe. A sem se ukvarjal tudi z razvojem operacijskih sistemov, gonilnikov, razhroščevalnikov nivoja jedra, jezikov za razvoj OS, ki so se sprva zdeli enostavni, a so skozi čas postajali vse bolj kompleksni. A glavna tema je vseeno – razvoj orodij. Velik del življenja je minil med Azulom in Sunom, in to je bilo o Javi. A ko sem se začel ukvarjati z Big Data in strojim učenjem, sem znova oblekel svojo svečano čepico in rekel: „Oh, zdaj imamo nenavadno težavo, in tukaj se dogaja cela vrsta zanimivih stvari in ljudi, ki nekaj delajo.“ To je odlična pot za razvoj, po kateri se je vredno podati.
Da, zelo imam rad porazdeljene izračune. Moje prvo delo je bilo v študentstvu na C, pri oglaševalski kampanji. To so bili porazdeljeni izračuni na čipih Zilog Z80, ki so zbirali podatke za analogno optično prepoznavanje besedil, ki ga je izvajal pravi analogni analizator. Bila je zanimiva in povsem nenavadna tema. Vendar so se pojavile težave, nekateri deli niso bili pravilno prepoznani, zato je bilo treba zajeti sliko in jo pokazati osebi, ki jo je brala z očmi in poročala, kaj piše, zato so bile naloge s podatki in te naloge so imele svoj jezik. Imeli smo backend, ki je vse to obdeloval – Z80, ki so delovali vzporedno z aktivnimi terminali vt100 – en za vsakega človeka, in imeli smo model vzporednega programiranja na Z80. Nek delovni spomin, ki so ga delili vsi Z80 znotraj konfiguracije tipa 'zvezda'; delila sta se tudi backplane in polovica RAM-a se je delila znotraj omrežja, druga polovica pa je bila zasebna ali uporabljena za kaj drugega. Smiselno kompleksni vzporedni porazdeljeni sistem z deljenjem… polodeljenim spominom. Kdaj je to bilo… Že ne morem več spomniti, nekje sredi 80-ih. Zelo dolgo nazaj.
Da, lahko rečemo, da je 30 let dovolj dolgo. Naloge, povezane s porazdeljenimi izračuni, obstajajo že dolgo, ljudje že od nekdaj se borijo s -klasterji. Ti klasterji izgledajo kot... Na primer: obstaja Ethernet in tvoj hitri x86 je povezan s tem Ethernetom, in zdaj si želiš pridobiti fake shared memory, ker takrat nihče ni mogel programirati razdeljenih izračunov, to je bilo preveč zapleteno in zato je obstajala fake shared memory z zaščito strani pomnilnika na x86, in če si pisal v to stran, smo drugim procesorjem sporočili, da če pridobijo dostop do iste shared memory, jo morajo naložiti od tebe, in tako se je pojavilo nekaj, kar je izgledalo kot protokol za podporo koherentnosti predpomnilnika in programske opreme za to. Zanimiva koncept. Prava težava pa je bila v drugem. Vse je delovalo, a kmalu si imel težave s hitrostjo, saj nihče ni razumel modela zmogljivosti na dovolj dobri ravni – kakšni so tam vzorci dostopa do pomnilnika, kako poskrbeti, da se vozlišča neskončno ne pingajo med seboj, in tako naprej.
V H2O sem razvijalci sami odgovorni za to, da določijo, kje je prisoten paralelizem in kje ga ni. Ustvaril sem model kodiranja, pri katerem je pisanje visoko performantne kode enostavno in preprosto. Pisanje slabo delujoče kode pa je zapleteno, takšna koda bo slabo izgledala. Potrebno se je resno potruditi, da napišeš počasi delujočo kodo, uporaba nestandardnih metod je nujna. Kodo, ki upočasnjuje delovanje, je mogoče opaziti že na prvi pogled. Posledično se običajno piše koda, ki deluje hitro, vendar se morate ukvarjati s tem, kako ravnati v primeru deljene pomnilnika. Vse to je povezano z velikimi tabelami, njihovo delovanje pa je podobno nevolatilnim velikim tabelam v paralelni Javi. Predstavljajte si dva niti, ki pišeta v paralelno tabelo; ena zmaga, druga izgubi, ne veste pa, kdo je kdo. Če nista volatilni, lahko red pride do kakršnegakoli – in to res dobro deluje. Ljudje dejansko skrbijo za red operacij, pravilno postavijo volatile in na pravih mestih pričakujejo težave z zmogljivostjo, povezane s pomnilnikom. V nasprotnem primeru bi preprosto pisali kodo v obliki zank od 1 do N, kjer je N kakšnih trilijonov, v upanju, da bodo vsi zahtevni primeri samodejno postali paralelni – kar pa ne deluje. A v H2O to ni Java, niti Scala, lahko bi rekli
Technical challenges
Andrey: Kakšen je bil vaš največji izziv v karieri?
Cliff: Ali razpravljamo o tehničnem ali ne tehničnem delu vprašanja? Rekel bi, da so največji izzivi ne-tehnični.
Kar zadeva tehnične izzive. Preprosto sem jih premagal. Niti ne vem, kateri je bil največji, a bilo je nekaj zelo zanimivih, pri katerih je bilo potrebnega kar precej časa in mentalne borbe. Ko sem šel v Sun, sem bil prepričan, da bom naredil hiter prevajalnik, a vrsta starejših je proti temu rekla, da mi nikoli ne bo uspelo. Vendar sem se odločil, da bom šel v to smer, napisal prevajalnik vse do dodeljevalnika registrov, in bil precej hiter. Bil je tako hiter kot sodobni C1, vendar je takrat dodeljevalnik deloval veliko počasneje, in ko gledam nazaj - to je bila težava velike podatkovne strukture. Potreboval sem jo, da napišem grafični dodeljevalnik registrov in nisem razumel dilem med izrazitostjo kode in hitrostjo, ki sta obstajali v tistem času in sta bili zelo pomembni. Izkazalo se je, da je podatkovna struktura običajno večja od velikosti predpomnilnika na x86 tistega časa, zato, če sem sprva predvideval, da bo dodeljevalnik registrov porabil 5-10 odstotkov celotnega časa JIT, se je v resnici to izkazalo za 50 odstotkov.
Čas je mineval, prevajalnik je postal vse bolj jasen in zmogljiv, prenehal je generirati grozno kodo v večini primerov, in zmogljivost je vse pogosteje zašla v območje, ki ga je podal prevajalnik C. Če seveda ne pišeš nečesa slabega, kar niti C ne pospeši. Če pišeš kodo, kot na C, dobiš tudi učinkovitost kot na C v večini primerov. In čedalje pogosteje je bila koda asimptotsko primerna s tisto na ravni C, dodeljevalnik registrov pa je začel delovati kot končna stvar… ne glede na to, ali tvoja koda deluje hitro ali počasi. Nadaljeval sem z delom na dodeljevalniku, da bi dobil boljša dodeljevanja. Postal je vse počasnejši, a je omogočal vedno boljšo zmogljivost v primerih, kjer se nihče drug ni znašel. Lahko sem se poglobil v dodeljevalnik registrov, vložil mesec dela, in nenadoma se je vsa koda začela izvajati 5% hitreje. To se je dogajalo znova in znova in dodeljevalnik registrov je postal nekakšno umetniško delo – vsi so ga imeli radi ali ga sovražili, ljudje iz akademskega sveta pa so postavljali vprašanja o tem, zakaj je vse tako narejeno, zakaj ne. , in de waarom is het verschil. Het antwoord is hetzelfde: een allocator op basis van grafkleuring plus zeer nauwkeurige werkzaamheden met buffer-code gelijk aan een wapen van overwinning, de beste combinatie die niemand kan verslaan. En het is een behoorlijk onduidelijk iets. Alles wat de compiler daar verder doet, zijn vrij goed bestudeerde zaken, hoewel ze ook tot een kunstniveau zijn gebracht. Ik heb altijd dingen gedaan die de compiler in een kunstwerk moesten veranderen. Maar niets van dat alles was iets buitengewoons - behalve de register-allocator. De focus ligt in het feit dat je zorgvuldig onder belasting en, als dit gebeurt (ik kan het verder uitleggen als dat interessant is), betekent dit dat je agressiever kunt inlineen, zonder het risico te lopen over de prestatiegrafiek heen te gaan. In die tijd waren er veel volledig uitgeruste compilers, vol met franje en toeters, waarin register-allocators zaten, maar niemand kon dat nog meer.
Težava je v tem, da, če dodaš metode, ki jih je mogoče inlineati, povečuješ in povečuješ področje inlineanja, se niz uporabljenih vrednosti takoj poveča nad število registrov, kar povzroči spill. Kritična meja ponavadi nastopi, ko se alokator predaja, in en dober kandidat za spill stane drugega, ti spillš nekakšne povsem nenavadne stvari. Vrednost inlineanja tukaj je v tem, da izgubiš del overheada, overheada na klic in shranjevanje, lahko vidiš vrednosti znotraj in jih lahko dodatno optimiziraš. Stroški inlineanja pa so v tem, da se ustvari veliko živih vrednosti, in če tvoj alokator registrov spilla več, kot je potrebno, takoj izgubiš. Zato ima večina alokatorjev težavo: ko inlineanje preide določeno mejo, začne spillati vse na svetu in zmogljivost se lahko spere stran. Tisti, ki razvijajo prevajalnik, dodajajo nekatere heuristike: na primer, da ustavijo inlineanje, ko doseže dovolj veliko velikost, saj alokacije vse pokvarijo. Tako se pojavi zlom grafikon zmogljivosti – inlineš, inlineš, zmogljivost počasi raste – in nato bum! – pade hitro, ker si inlineal preveč. Tako je vse delovalo do prihoda Jave. Java zahteva precej več inlineanja, zato sem moral svoj alokator narediti veliko bolj agresiven, da se izravna, namesto da pade, in če si preveč inlineal – začne spillati, a nato vseeno pride trenutek, ko 'ni več spillanja'. To je zanimivo spoznanje in prišlo je do mene kar iz nič, ni očitno, a se je dobro obrestovalo. Lotil sem se agresivnega inlineanja in to me je pripeljalo na področja, kjer sta zmogljivost Jave in C blizu jedna drugi. Resnično sta si blizu – lahko pišem kodo v Javi, ki je precej hitrejša od kode v C in podobno, a povprečno, v veliki sliki stvari, sta si približno enaki. Zdi se, da je del te zasluge alokator registrov, ki mi omogoča, da inlineam kar se da naivno. Simply inlineam vse, kar vidim. Vprašanje pa je, ali alokator deluje dobro, ali dobiš razumljivo delujočo kodo. To je bil velik izziv: razumeti vse to in ga spraviti v delovanje.
A bit about register allocation and multithreading
Vladimir: Težave, kot je dodelitev registrov, se zdijo neskončna tema. Zanimivo bi bilo vedeti, ali je bila kdaj neka ideja obetavna, a je kasneje v praksi propadla?
Cliff: Seveda! Dodelitev registrov je področje, kjer se pri reševanju NP-polne naloge trudite najti nekatere heuristicne pristope. In nikoli ne morete doseči popolne rešitve, kajne? To je preprosto nemogoče. Poglejte, predkompilacija – tudi ta slabo deluje. Tukaj gre za neke povprečne primere. O tipični zmogljivosti, tako da lahko greste meriti nekaj, kar menite, da je dobra tipična zmogljivost – na koncu koncev, delate na njenem izboljšanju! Dodelitev registrov je tema, ki je v celoti posvečena zmogljivosti. Takoj ko imate prvi prototip, ki deluje in barva tisto, kar mora, se začnejo dela na zmogljivosti. Naučiti se morate dobro meriti. Zakaj je to pomembno? Če so na voljo jasni podatki, lahko pogledate na različna področja in vidite: aha, to je tukaj pomagalo, a tukaj se je vse podrl! Pojavijo se dobre ideje, dodate novo heuristiko in nenadoma vse začne delovati povprečno nekoliko bolje. Ali pa ne. Imel sem veliko primerov, ko smo se borili za pet odstotkov zmogljivosti, ki so našo rešitev ločevala od prejšnjega dodeljevanja. In vsakič to izgleda tako: kje ste zmagali, kje izgubili. Če imate dobre analitične instrumente zmogljivosti, lahko najdete poražene ideje in razumete, zakaj so izgubile. Mogoče je bolje pustiti stvari takšne, kot so, ali pa se resneje lotiti fine nastavitve, ali pa iti in popraviti nekaj drugega. To je cel sklop stvari! Naredil sem ta odličen trik, a potrebujem še tega, tega in tega – in njihova skupna kombinacija prinaša določene izboljšave. Posamezne ideje lahko propadejo. Takšna je narava dela na NP-polnih nalogah.
Vladimir: Zdi se, da so stvari, kot so barvanje v dodeljevanju, že rešene naloge. No, za vas so po tem, kar pripovedujete, a se sploh splača…
Cliff: Ni se riješi sama od sebe. Ti to moraš pretvoriti u «riješeno». Postoje teški zadaci i njih treba riješiti. Kada se to učini, dolazi vrijeme za rad na performansama. Na tu se zadaću treba pristupiti odgovarajuće – raditi benchmarke, prikupljati mjere, objašnjavati situacije kada se povratkom na prethodnu verziju tvoj stari haker ponovno aktivira (ili obrnuto, prestane raditi). I ne odustati dok ne postigneš nešto. Kao što sam već rekao, ako su cool ideje koje nisu uspjele, u području alokacije registara ideja je otprilike beskonačno mnogo. Na primjer, možeš čitati znanstvene radove. Iako je to područje sada postalo sporije i jasnije nego u vrijeme svoje mladosti. Ipak, u tom području radi cijela beskonačnost ljudi i sve njihove ideje vrijedi isprobati, sve čekaju svoj trenutak. I ne možeš reći koliko su dobre ako ih ne probaš. Koliko se dobro integriraju s ostalim dijelovima tvoje alokacije, jer alokator radi mnoge stvari, i neke ideje u tvom specifičnom alokatoru neće raditi, dok u drugom alokatoru lako mogu. Glavni način pobjede za alokator je izvući sporu stvar iz glavne staze i prisiliti je na razdvajanje po granicama sporih staza. Zato, ako želiš pokrenuti GC, ići sporom stazom, deoptimizirati se, baciti iznimku, sve u tom smislu – znaš da su te stvari relativno rijetke. I zaista su rijetke, provjeravao sam. Činiš dodatni rad i zbog toga nestaje mnogo ograničenja na tim sporim stazama, ali to nije isuviše važno, jer su spore i rijetko ih koriste. Na primjer, nulti pokazivač – on se nikada ne događa, zar ne? Treba imati nekoliko staza za različite stvari, ali se ne bi trebale miješati na glavnom.
Vladimir: Što mislite o višekontrolernoj obradi, kada odmah imaš tisuće jezgri? Je li to korisna stvar?
Cliff: Uspjeh GPU-a pokazuje da je prilično korisno!
Vladimir: Oni su prilično specijalizirani. A što je s procesorima opće namjene?
Cliff: No, to je bila poslovna modela Azul. Odgovor je došao još u eri kada su ljudi jako voljeli predvidivu performansu. Tada je bilo teško pisati paralelni kod. H2O model kodiranja se dobro skalira, ali nije model opće namjene. Možda je malo općenitiji od korištenja GPU-a. Govorimo o složenosti razvoja takve stvari ili o složenosti njezine upotrebe? Na primjer, zanimljiva lekcija koju mi je dala Azul, prilično ne očigledna: mali keševi su u redu.
The biggest challenge in life
Vladimir: Što je s netehničkim izazovima?
Cliff: Največji izziv je bil ne biti... prijazen in dober do ljudi. Kot posledica sem se nenehno nahajal v izjemno konfliktnih situacijah. V takšnih, kjer sem vedel, da stvari ne gredo prav, a nisem vedel, kako napredovati pri reševanju teh težav in se z njimi spoprijeti. Številne dolgoletne težave, ki so trajale desetletja, so se pojavile prav na ta način. To, da v Javi obstajata kompilatorja C1 in C2, je neposredna posledica tega. Da v Javi več kot deset let ni bilo večstopenjske kompilacije, je prav tako neposredna posledica. Očitno smo potrebovali tak sistem, a ni jasno, zakaj ga ni bilo. Imel sem težave z enim inženirjem... ali skupino inženirjev. Pred davnimi časi, ko sem začel delati v Sunu, sem bil... No, ne samo takrat, vedno imam svoje mnenje o vsem. In verjel sem, da lahko preprosto vzamem to svojo resnico in jo povem naravnost. Še posebej, ker sem bil velikokrat šokantno prav. In če ti ta pristop ni všeč... še posebej, če si očitno v zmoti in delaš neumnosti... Na splošno je le redko kdo lahko prenašal to obliko komunikacije. Čeprav so nekateri, recimo, jaz. Celotno življenje sem gradil na meritokratičnih načelih. Če mi pokažete nekaj narobe, se takoj obrnem in rečem: rekel si neumnost. Pri tem se seveda opravičim in vse to, priznam zasluge, če te sploh imate, in opravim druge pravilne korake. Po drugi strani pa sem šokantno prav v šokantno velikem odstotku skupnega časa. In to ne deluje najbolje v odnosih z ljudmi. Ne trudim se biti prijazen, ampak postavljam vprašanje na jasno. 'To nikoli ne bo delovalo, ker en, dva in tri.' In oni: 'Oh!'. Bile so tudi druge posledice, ki jih je bolje verjetno preskočiti: na primer, ki so pripeljale do ločitve z ženo in desetletja depresije po tem.
Izziv je boj s ljudmi in njihovim dojemanjem tega, kar lahko ali ne moreš početi, kar je pomembno in kar ni. Bilo je veliko izzivov glede sloga kodiranja. Še vedno pišem veliko kode, a v tistih časih sem se moral celo upočasniti, ker sem delal preveč nalog hkrati in sem jih opravljal slabo, namesto da bi se osredotočil na eno samo nalogo. Ko gledam nazaj, sem napisal polovico kode ekipe Java JIT, ekipe C2. Naslednji najhitrejši kodir je pisal polovico počasneje, naslednji še enkrat počasneje in to je bil eksponenten upad. Sedmi človek v vrsti je bil zelo, zelo počasen – tako je vedno! Dotaknil sem se kup kode. Gledal sem, kdo kaj piše, brez izjem, buljil sem v njihovo kodo, pregledoval vsakega od njih, in kljub temu še vedno pisal več kot kdorkoli od njih. Z ljudmi takšen pristop ni preveč uspešen. Nekaterim to ni všeč. In ko se s tem ne morejo soočiti, se začnejo različne pritožbe. Na primer, enkrat so mi rekli, da naj preneham pisati kodo, ker pišem preveč kode in to ogroža ekipo, in za mene je to zvenelo kot šala: ej, če bo celotna preostala ekipa izginila in bom jaz še naprej pisal kodo, boš izgubil samo polovico ekipe. Po drugi strani, če bom še naprej pisal kodo in boš izgubil polovico ekipe – zveni kot zelo slabo vodenje. Nikoli se nisem pretirano spraševal o tem, nikoli o tem govoril, a je kljub temu bilo nekje v moji glavi. V ozadju zavesti je krožila misel: „Ali ste vsi res resni?“ Tako da je bila največja težava jaz in moje odnose z ljudmi. Zdaj se mnogo bolj razumem, dolgo sem bil tim lider med programerji in zdaj ljudem odprto povem: veš, takšen sem, kot sem, in morali se boste spoprijeti z mano – ali je v redu, če tukaj malo stojim? In ko so se s tem naučili spoprijeti, je vse začelo delovati. Namreč, nisem ne slab ne dober, nimam slabih namenov ali egoističnih težav, to je le moja narava in nekako se je potrebno s tem spoprijeti.
Andrey: Pred kratkim je bilo veliko govora o samospoznanju za introverte in sploh o mehkih spretnostih. Kaj bi se o tem dalo povedati?
Cliff: Ja, to było zrozumienie i lekcja, którą wyniosłem z rozwodu z żoną. Czego się nauczyłem z rozwodu – to zrozumienie samego siebie. Tak zacząłem rozumieć innych ludzi. Rozumieć, jak działa ta interakcja. To doprowadziło do odkryć jedno po drugim. Pojawiła się świadomość, kim jestem i co sobą reprezentuję. Co robię: albo skupiam się na zadaniu, albo unikam konfliktu, albo coś innego – a taki poziom samoświadomości naprawdę pomaga utrzymać się w ryzach. Po tym wszystkim wszystko staje się prostsze. Jedna rzecz, którą odkryłem nie tylko u siebie, ale i u innych programistów – to niemożność werbalizacji myśli, kiedy jesteś w stanie stresu emocjonalnego. Na przykład, siedzisz, kodujesz, jesteś w stanie przepływu, a tu podbiegają do ciebie i zaczynają krzyczeć w histerii, że coś się zepsuło, i teraz będą zastosowane skrajne środki. I nie możesz wydusić ani słowa, bo jesteś w stanie emocjonalnego stresu. Nabyte umiejętności pozwalają przygotować się na ten moment, przeżyć go i przejść do planu wycofania, po którym można już coś zrobić. Więc tak, kiedy zaczynasz zdawać sobie sprawę, jak to wszystko działa – to ogromne, życiowe wydarzenie.
Sam nie potrafiłem znaleźć odpowiednich słów, ale zapamiętałem ciąg działań. Istota jest taka, że ta reakcja jest tak samo fizyczna, jak i werbalna, i potrzebujesz przestrzeni. Takiej przestrzeni, w zenowym sensie. Właśnie to trzeba wyjaśnić, a potem od razu się cofnąć – czysto fizycznie się cofnąć. Kiedy milczę słowami, mogę przetworzyć sytuację w części emocjonalnej. Kiedy adrenalina dociera do mózgu, przełącza cię w tryb „walcz lub uciekaj”, już nie możesz nic powiedzieć, nie – teraz jesteś idiotą, inżynierem do bicia, niezdolnym do godnej odpowiedzi czy przynajmniej do zatrzymania ataku, a atakujący może swobodnie zaatakować znowu i znowu. Najpierw musisz znów stać się sobą, odzyskać kontrolę, wyjść z trybu „walcz lub uciekaj”.
In order to achieve this, a verbal space is necessary. Just a free space. If you are going to say something, you might as well state this and then go and really find yourself a 'space': take a walk in the park, lock yourself in the shower – it doesn't matter. The main thing is to temporarily disconnect from the situation. As soon as you disconnect for at least a few seconds, control returns, and you start to think clearly. 'Okay, I'm not some idiot; I'm not doing stupid things; I'm a fairly useful person.' Once you manage to convince yourself, it's time to move on to the next step: to understand what happened. You were attacked, the attack came unexpectedly, it was an unfair, sneaky ambush. That’s bad. The next step is to understand why the attacker needed this. Really, why? Maybe because he is furious himself? Why is he furious? For instance, because he messed up and cannot take responsibility? This is how you need to carefully process the whole situation. But for that, you need space to maneuver, a verbal space. The very first step is to break the verbal contact. Move away from the discussion verbally. Cancel it, leave as quickly as possible. If it's a phone call – just hang up – it's a skill I learned from communicating with my ex-wife. If the conversation is leading nowhere good, just say 'goodbye' and hang up. On the other end of the line: 'blah-blah-blah', you respond: 'uh-huh, bye!' and hang up. You just cut off the conversation. Five minutes later, when your ability to think clearly returns, you have cooled down a bit, and it becomes possible to consider what actually happened and what to do next. And start formulating a thoughtful response rather than just reacting on emotion. For me, a breakthrough in self-awareness was realizing that in the case of emotional stress, I cannot speak. Getting out of that state, thinking over and planning how to respond and address the problems – those are the right steps when you cannot talk. The easiest way is to run away from the situation that manifests emotional stress and simply stop participating in that stress. After that, you regain the ability to think, when you can think, the ability to speak arises, and so on.
Poleg tega, v sodnem postopku odvetnik nasprotne strani skuša to narediti s tabo – zdaj je že jasno, zakaj. Ker ima možnost, da te potlači do takega stanja, da ne moreš niti izgovoriti svojega imena. V dobesednem pomenu, ne moreš govoriti. Če se ti to dogaja in če veš, da se boš znašel na mestu, kjer se odvijajo verbalne bitke, na mestu, kot je sodišče, lahko prideš s svojim odvetnikom. Odvetnik te bo zagovarjal in ustavil verbalni napad, in to bo storil na povsem zakonit način, in vrnilo se ti bo izgubljeno zenovsko prostor. Na primer, nekajkrat sem moral poklicati družino, sodnik je nanje gledal povsem prijazno, medtem ko je odvetnik nasprotne strani kričal name in sem se celo moral zadržati, da sem lahko vtaknil besedo. V takih primerih se zame najbolje obnese uporaba posrednika. Posrednik prekine vso to nenehno obremenitev, ki te pritisne s neprekinjenim tokom, odkrivaš potrebno zenovsko prostor, skupaj s tem se vrne sposobnost govora. To je celo področje znanja, kjer se je treba veliko naučiti, veliko odkriti v sebi, in vse to se pretvori v visoko kakovostne strateške odločitve, ki so različne za različne ljudi. Nekateri nimajo zgoraj opisanih težav, običajno jih nimajo ljudje, ki se profesionalno ukvarjajo s prodajo. Vsi ti ljudje, ki si služijo kruh z besedami – znani pevci, pesniki, religiozni voditelji in politiki, imajo vedno kaj povedati. Nimajo takih težav, jaz pa jih imam.
Andrey: To je bilo … presenetljivo. Odlično, že smo precej govorili in čas je, da zaključimo to intervju. Obvezno se srečamo na konferenci in lahko nadaljujemo ta dialog. Se vidimo na Hydri!
Nadaljevati komunikacijo s Cliffom bo mogoče na konferenci Hydra 2019, ki bo potekala 11.-12. julija 2019 v Sankt Peterburgu. Prišel bo s predstavitvijo . Vstopnice je mogoče kupiti .
Izvor: habr.com
