Flott intervju med Cliff Click, faren til JIT-samlingen i Java

Flott intervju med Cliff Click, faren til JIT-samlingen i JavaCliff klikk — CTO for Cratus (IoT-sensorer for prosessforbedring), grunnlegger og medgründer av flere startups (inkludert Rocket Realtime School, Neurensic og H2O.ai) med flere vellykkede exits. Cliff skrev sin første kompilator i en alder av 15 (Pascal for TRS Z-80)! Han er mest kjent for sitt arbeid med C2 i Java (Sea of ​​​​Nodes IR). Denne kompilatoren viste verden at JIT kunne produsere kode av høy kvalitet, som var en av faktorene i fremveksten av Java som en av de viktigste moderne programvareplattformene. Så hjalp Cliff Azul Systems med å bygge en 864-kjerners stormaskin med ren Java-programvare som støttet GC-pauser på en 500-gigabyte haug innen 10 millisekunder. Generelt klarte Cliff å jobbe med alle aspekter av JVM.

 
Denne habraposten er et flott intervju med Cliff. Vi vil snakke om følgende emner:

  • Overgang til lavnivåoptimaliseringer
  • Hvordan gjøre en stor refaktorering
  • Kostnadsmodell
  • Optimaliseringstrening på lavt nivå
  • Praktiske eksempler på ytelsesforbedring
  • Hvorfor lage ditt eget programmeringsspråk
  • Ytelsesingeniørkarriere
  • Tekniske utfordringer
  • Litt om registertildeling og flerkjerner
  • Den største utfordringen i livet

Intervjuet er utført av:

  • Andrey Satarin fra Amazon Web Services. I sin karriere klarte han å jobbe i helt andre prosjekter: han testet den distribuerte NewSQL-databasen i Yandex, et skydeteksjonssystem i Kaspersky Lab, et flerspillerspill i Mail.ru og en tjeneste for beregning av valutapriser i Deutsche Bank. Interessert i å teste storskala backend og distribuerte systemer.
  • Vladimir Sitnikov fra Netcracker. Ti års arbeid med ytelsen og skalerbarheten til NetCracker OS, programvare som brukes av telekomoperatører for å automatisere administrasjonsprosesser for nettverk og nettverksutstyr. Interessert i ytelsesproblemer i Java og Oracle Database. Forfatter av mer enn et dusin ytelsesforbedringer i den offisielle PostgreSQL JDBC-driveren.

Overgang til lavnivåoptimaliseringer

Andrew: Du er et stort navn i verden av JIT-kompilering, Java og ytelsesarbeid generelt, ikke sant? 

klippe: Det er slik det er!

Andrew: La oss starte med noen generelle spørsmål om prestasjonsarbeid. Hva synes du om valget mellom høynivå- og lavnivåoptimaliseringer som å jobbe på CPU-nivå?

klippe: Ja, alt er enkelt her. Den raskeste koden er den som aldri kjører. Derfor må du alltid starte fra et høyt nivå, jobbe med algoritmer. En bedre O-notasjon vil slå en dårligere O-notasjon, med mindre noen tilstrekkelig store konstanter griper inn. Ting på lavt nivå går sist. Vanligvis, hvis du har optimalisert resten av stabelen din godt nok og det fortsatt er noen interessante ting igjen, er det et lavt nivå. Men hvordan starte fra et høyt nivå? Hvordan vet du at det er gjort nok arbeid på høyt nivå? Vel... ingen måte. Det finnes ingen ferdige oppskrifter. Du må forstå problemet, bestemme deg for hva du skal gjøre (for ikke å ta unødvendige skritt i fremtiden) og så kan du avdekke profileren, som kan si noe nyttig. På et tidspunkt innser du selv at du har blitt kvitt unødvendige ting, og det er på tide å gjøre litt finjustering på lavt nivå. Dette er definitivt en spesiell type kunst. Det er mange som gjør unødvendige ting, men beveger seg så raskt at de ikke har tid til å bekymre seg for produktivitet. Men dette er inntil spørsmålet melder seg rett ut. Vanligvis 99 % av tiden bryr ingen seg om hva jeg gjør, til det øyeblikket når en viktig ting kommer på den kritiske veien som ingen bryr seg om. Og her begynner alle å mase på deg om "hvorfor det ikke fungerte perfekt helt fra begynnelsen." Generelt er det alltid noe å forbedre i ytelsen. Men 99 % av tiden har du ingen potensielle kunder! Du prøver bare å få noe til å fungere, og i prosessen finner du ut hva som er viktig. Du kan aldri vite på forhånd at dette stykket må være perfekt, så du må faktisk være perfekt i alt. Men dette er umulig, og du gjør det ikke. Det er alltid mange ting å fikse – og det er helt normalt.

Hvordan gjøre en stor refaktorering

Andrew: Hvordan jobber du med en forestilling? Dette er et tverrgående problem. Har du for eksempel noen gang måttet jobbe med problemer som oppstår fra skjæringspunktet mellom mye eksisterende funksjonalitet?

klippe: Jeg prøver å unngå det. Hvis jeg vet at ytelsen vil være et problem, tenker jeg på det før jeg begynner å kode, spesielt med datastrukturer. Men ofte oppdager du alt dette veldig senere. Og så må du gå til ekstreme tiltak og gjøre det jeg kaller "rewrite and conquer": du må ta en stor nok brikke. Noe av koden vil fortsatt måtte skrives om på grunn av ytelsesproblemer eller noe annet. Uansett årsak til å omskrive kode, er det nesten alltid bedre å skrive om et større stykke enn et mindre stykke. I dette øyeblikket begynner alle å riste av frykt: "herregud, du kan ikke røre så mye kode!" Men faktisk fungerer denne tilnærmingen nesten alltid mye bedre. Du må umiddelbart ta på deg et stort problem, tegne en stor sirkel rundt det og si: Jeg skal skrive om alt inne i sirkelen. Kanten er mye mindre enn innholdet i den som må byttes ut. Og hvis en slik avgrensning av grenser lar deg gjøre arbeidet på innsiden perfekt, er hendene dine frie, gjør hva du vil. Når du forstår problemet, er omskrivingsprosessen mye enklere, så ta en stor bit!
Samtidig, når du gjør en stor omskriving og innser at ytelsen kommer til å bli et problem, kan du umiddelbart begynne å bekymre deg for det. Dette blir vanligvis til enkle ting som "ikke kopier data, administrer data så enkelt som mulig, gjør dem små." Ved store omskrivinger er det standard måter å forbedre ytelsen på. Og de dreier seg nesten alltid om data.

Kostnadsmodell

Andrew: I en av podcastene snakket du om kostnadsmodeller i sammenheng med produktivitet. Kan du forklare hva du mente med dette?

klippe: Absolutt. Jeg ble født i en tid da prosessorytelse var ekstremt viktig. Og denne epoken vender tilbake igjen - skjebnen er ikke uten ironi. Jeg begynte å leve i dagene med åtte-bits maskiner; min første datamaskin jobbet med 256 byte. Akkurat bytes. Alt var veldig lite. Instruksjoner måtte telles, og etter hvert som vi begynte å bevege oss oppover i programmeringsspråkstabelen, tok språkene på seg mer og mer. Det var Assembler, deretter Basic, så C, og C tok seg av mye av detaljene, som registertildeling og instruksjonsvalg. Men alt var ganske klart der, og hvis jeg gjorde en peker til en forekomst av en variabel, ville jeg få last, og kostnadene for denne instruksjonen er kjent. Maskinvaren produserer et visst antall maskinsykluser, så utførelseshastigheten til forskjellige ting kan beregnes ganske enkelt ved å legge sammen alle instruksjonene du skal kjøre. Hver sammenligning/test/gren/anrop/last/butikk kan legges sammen og si: det er utførelsestiden for deg. Når du jobber med å forbedre ytelsen, vil du definitivt være oppmerksom på hvilke tall som tilsvarer små varme sykluser. 
Men så snart du bytter til Java, Python og lignende ting, beveger du deg veldig raskt bort fra maskinvare på lavt nivå. Hva koster det å ringe en getter i Java? Hvis JIT i HotSpot er riktig innfelt, vil det lastes, men hvis det ikke gjorde dette, vil det være et funksjonskall. Siden samtalen er på en hot loop, vil den overstyre alle andre optimaliseringer i den loopen. Derfor vil den reelle kostnaden være mye høyere. Og du mister umiddelbart muligheten til å se på et stykke kode og forstå at vi bør utføre det når det gjelder prosessorklokkehastighet, minne og cache som brukes. Alt dette blir interessant bare du virkelig kommer inn i forestillingen.
Nå befinner vi oss i en situasjon hvor prosessorhastigheten knapt har økt på et tiår. Gamle dager er tilbake! Du kan ikke lenger regne med god entråds ytelse. Men hvis du plutselig kommer inn i parallell databehandling, er det utrolig vanskelig, alle ser på deg som James Bond. Tidoble akselerasjoner her forekommer vanligvis på steder der noen har rotet til noe. Samtidig krever mye arbeid. For å få den XNUMXx hastigheten, må du forstå kostnadsmodellen. Hva og hvor mye koster det? Og for å gjøre dette, må du forstå hvordan tungen passer på den underliggende maskinvaren.
Martin Thompson valgte et flott ord for bloggen sin Mekanisk sympati! Du må forstå hva maskinvaren skal gjøre, nøyaktig hvordan den vil gjøre det, og hvorfor den gjør det den gjør i utgangspunktet. Ved å bruke dette er det ganske enkelt å begynne å telle instruksjoner og finne ut hvor utførelsestiden går. Hvis du ikke har riktig opplæring, leter du bare etter en svart katt i et mørkt rom. Jeg ser folk som optimaliserer ytelsen hele tiden som ikke aner hva i helvete de driver med. De lider mye og gjør ikke store fremskritt. Og når jeg tar den samme kodebiten, stikker inn et par små hack og får en fem- eller ti-dobling av hastigheten, er de sånn: vel, det er ikke rettferdig, vi visste allerede at du var bedre. Fantastisk. Hva snakker jeg om... kostnadsmodellen handler om hva slags kode du skriver og hvor fort den kjører i gjennomsnitt i det store bildet.

Andrew: Og hvordan kan du holde et slikt volum i hodet? Er dette oppnådd med mer erfaring, eller? Hvor kommer en slik erfaring fra?

klippe: Vel, jeg fikk ikke erfaringen min på den enkleste måten. Jeg programmerte i Assembly på den tiden da du kunne forstå hver enkelt instruksjon. Det høres dumt ut, men siden den gang har Z80 instruksjonssettet alltid holdt seg i hodet mitt, i hukommelsen. Jeg husker ikke folks navn innen et minutt etter å ha snakket, men jeg husker kode skrevet for 40 år siden. Det er morsomt, det ser ut som et syndrom "idiot vitenskapsmann'.

Optimaliseringstrening på lavt nivå

Andrew: Finnes det en enklere måte å komme inn på?

klippe: Ja og nei. Maskinvaren vi alle bruker har ikke endret seg så mye over tid. Alle bruker x86, med unntak av Arm-smarttelefoner. Hvis du ikke driver med en slags hardcore embedding, gjør du det samme. Ok, neste. Instruksjonene har heller ikke endret seg på århundrer. Du må gå og skrive noe i Assembly. Ikke mye, men nok til å begynne å forstå. Du smiler, men jeg snakker helt seriøst. Du må forstå samsvaret mellom språk og maskinvare. Etter det må du gå og skrive litt og lage en liten lekekompilator for et lite lekespråk. Leketøyslignende betyr at den må lages innen rimelig tid. Det kan være superenkelt, men det må generere instruksjoner. Handlingen med å generere en instruksjon vil hjelpe deg å forstå kostnadsmodellen for broen mellom høynivåkoden som alle skriver og maskinkoden som kjører på maskinvaren. Denne korrespondansen vil bli brent inn i hjernen når kompilatoren skrives. Selv den enkleste kompilatoren. Etter det kan du begynne å se på Java og det faktum at dens semantiske kløft er mye dypere, og det er mye vanskeligere å bygge broer over den. I Java er det mye vanskeligere å forstå om broen vår ble bra eller dårlig, hva som vil få den til å falle fra hverandre og hva som ikke vil. Men du trenger et slags utgangspunkt der du ser på koden og forstår: "ja, denne getteren bør være innebygd hver gang." Og så viser det seg at noen ganger skjer dette, bortsett fra situasjonen når metoden blir for stor, og JIT begynner å inline alt. Ytelsen til slike steder kan forutses umiddelbart. Vanligvis fungerer getters bra, men så ser du på store hot loops og innser at det er noen funksjonskall som flyter rundt der som ikke vet hva de gjør. Dette er problemet med den utbredte bruken av gettere, grunnen til at de ikke er inlined er at det ikke er klart om de er en getter. Hvis du har en superliten kodebase, kan du ganske enkelt huske den og så si: dette er en getter, og dette er en setter. I en stor kodebase lever hver funksjon sin egen historie, som generelt ikke er kjent for noen. Profileren sier at vi tapte 24 % av tiden på en eller annen løkke, og for å forstå hva denne løkken gjør, må vi se på hver funksjon inne. Det er umulig å forstå dette uten å studere funksjonen, og dette bremser alvorlig prosessen med å forstå. Derfor bruker jeg ikke gettere og settere, jeg har nådd et nytt nivå!
Hvor får man tak i kostnadsmodellen? Vel, du kan lese noe, selvfølgelig... Men jeg tror den beste måten er å handle. Å lage en liten kompilator vil være den beste måten å forstå kostnadsmodellen og passe den inn i ditt eget hode. En liten kompilator som ville være egnet for programmering av en mikrobølgeovn er en oppgave for en nybegynner. Vel, jeg mener, hvis du allerede har programmeringskunnskaper, så burde det være nok. Alle disse tingene som å analysere en streng som du har som et slags algebraisk uttrykk, trekke ut instruksjoner for matematiske operasjoner derfra i riktig rekkefølge, ta de riktige verdiene fra registre - alt dette gjøres på en gang. Og mens du gjør det, vil det være innprentet i hjernen din. Jeg tror alle vet hva en kompilator gjør. Og dette vil gi en forståelse av kostnadsmodellen.

Praktiske eksempler på ytelsesforbedring

Andrew: Hva annet bør du være oppmerksom på når du arbeider med produktivitet?

klippe: Datastrukturer. Forresten, ja, jeg har ikke undervist i disse timene på lenge... Rakettskole. Det var gøy, men det krevde mye innsats, og jeg har også et liv! OK. Så, i en av de store og interessante timene, «Hvor går ytelsen din», ga jeg elevene et eksempel: to og en halv gigabyte med fintech-data ble lest fra en CSV-fil, og så måtte de beregne antall solgte produkter . Vanlige tick markedsdata. UDP-pakker konvertert til tekstformat siden 70-tallet. Chicago Mercantile Exchange - alle slags ting som smør, mais, soyabønner, slike ting. Det var nødvendig å telle disse produktene, antall transaksjoner, gjennomsnittlig volum av bevegelse av midler og varer, etc. Det er ganske enkel handelsmatematikk: finn produktkoden (det er 1-2 tegn i hash-tabellen), finn beløpet, legg det til et av handelssettene, legg til volum, legg til verdi og et par andre ting. Veldig enkel matematikk. Leketøyimplementeringen var veldig grei: alt er i en fil, jeg leser filen og beveger meg gjennom den, deler individuelle poster inn i Java-strenger, ser etter de nødvendige tingene i dem og legger dem sammen i henhold til matematikken beskrevet ovenfor. Og den fungerer på litt lav hastighet.

Med denne tilnærmingen er det åpenbart hva som skjer, og parallell databehandling vil ikke hjelpe, ikke sant? Det viser seg at en femdobling av ytelsen kan oppnås ganske enkelt ved å velge riktige datastrukturer. Og dette overrasker selv erfarne programmerere! I mitt spesielle tilfelle var trikset at du ikke skulle gjøre minnetildelinger i en hot loop. Vel, dette er ikke hele sannheten, men generelt sett - du bør ikke markere "en gang i X" når X er stor nok. Når X er to og en halv gigabyte, bør du ikke tildele noe "en gang per bokstav", eller "en gang per linje", eller "en gang per felt", noe sånt. Det er her tiden brukes. Hvordan fungerer dette i det hele tatt? Se for deg at jeg ringer String.split() eller BufferedReader.readLine(). Readline lager en streng fra et sett med byte som kom over nettverket, én gang for hver linje, for hver av de hundrevis av millioner linjene. Jeg tar denne linjen, analyserer den og kaster den. Hvorfor kaster jeg det - vel, jeg har allerede behandlet det, det er alt. Så for hver byte som leses fra disse 2.7G, vil to tegn bli skrevet i linjen, det vil si allerede 5.4G, og jeg trenger dem ikke til noe mer, så de blir kastet. Ser man på minnebåndbredden så laster vi inn 2.7G som går gjennom minnet og minnebussen i prosessoren, og så sendes dobbelt så mye til linjen som ligger i minnet, og alt dette frynses når hver ny linje opprettes. Men jeg trenger å lese den, maskinvaren leser den, selv om alt er frynsete senere. Og jeg må skrive det ned fordi jeg opprettet en linje og cachene er fulle - cachen kan ikke romme 2.7G. Så for hver byte jeg leser, leser jeg to byte til og skriver to byte til, og til slutt har de et forhold på 4:1 - i dette forholdet sløser vi med minnebåndbredde. Og så viser det seg at hvis jeg gjør det String.split() – dette er ikke siste gangen jeg gjør dette, det kan være ytterligere 6-7 felt inne. Så den klassiske koden for å lese CSV og deretter analysere strengene resulterer i en sløsing med minnebåndbredde på omtrent 14:1 i forhold til hva du faktisk ønsker å ha. Hvis du kaster disse valgene, kan du få en femdobling av hastigheten.

Og det er ikke så vanskelig. Hvis du ser på koden fra riktig vinkel, blir det hele ganske enkelt når du innser problemet. Du bør ikke slutte å allokere minne helt: det eneste problemet er at du tildeler noe og det dør umiddelbart, og underveis brenner det en viktig ressurs, som i dette tilfellet er minnebåndbredde. Og alt dette resulterer i et fall i produktiviteten. På x86 må du vanligvis aktivt brenne prosessorsykluser, men her brente du opp alt minnet mye tidligere. Løsningen er å redusere utslippsmengden. 
Den andre delen av problemet er at hvis du kjører profileren når minnestripen går tom, akkurat når det skjer, venter du vanligvis på at cachen skal komme tilbake fordi den er full av søppel som du nettopp har produsert, alle disse linjene. Derfor blir hver lasting eller lagringsoperasjon treg, fordi de fører til cache-misser - hele cachen har blitt treg og venter på at søppel skal forlate den. Derfor vil profileren bare vise varm tilfeldig støy smurt utover hele sløyfen - det vil ikke være noen egen varm instruksjon eller plass i koden. Bare støy. Og hvis du ser på GC-syklusene, er de alle Young Generation og superraske - mikrosekunder eller millisekunder maksimalt. Tross alt dør alt dette minnet øyeblikkelig. Du tildeler milliarder av gigabyte, og han kutter dem, og kutter dem, og kutter dem igjen. Alt dette skjer veldig raskt. Det viser seg at det er billige GC-sykluser, varm støy langs hele syklusen, men vi ønsker å få en 5x speedup. I dette øyeblikket bør noe lukke seg i hodet ditt og høres: "hvorfor er dette?!" Minnestrimmeloverflyt vises ikke i den klassiske debuggeren; du må kjøre debuggeren for maskinvareytelsesteller og se den selv og direkte. Men dette kan ikke mistenkes direkte fra disse tre symptomene. Det tredje symptomet er når du ser på det du fremhever, spør profileren, og han svarer: "Du tjente en milliard rader, men GC jobbet gratis." Så snart dette skjer, innser du at du har laget for mange objekter og brent opp hele minnebanen. Det er en måte å finne ut av dette på, men det er ikke åpenbart. 

Problemet ligger i datastrukturen: den nakne strukturen som ligger til grunn for alt som skjer, den er for stor, den er 2.7G på disken, så det er veldig uønsket å lage en kopi av denne tingen - du vil laste den fra nettverkets bytebuffer umiddelbart inn i registrene, for ikke å lese-skrive til linjen frem og tilbake fem ganger. Dessverre gir ikke Java deg et slikt bibliotek som en del av JDK som standard. Men dette er trivielt, ikke sant? I hovedsak er dette 5-10 linjer med kode som skal brukes til å implementere din egen bufrede strenglaster, som gjentar oppførselen til strengklassen, samtidig som den er en innpakning rundt den underliggende bytebufferen. Som et resultat viser det seg at du jobber nesten som med strenger, men faktisk flytter pekere til bufferen dit, og råbytene blir ikke kopiert noe sted, og dermed blir de samme bufferne gjenbrukt om og om igjen, og operativsystemet tar gjerne på seg tingene det er designet for, som skjult dobbelbuffring av disse byte-bufferne, og du sliper ikke lenger gjennom en endeløs strøm av unødvendige data. Forstår du forresten at når du arbeider med GC, er det garantert at hver minneallokering ikke vil være synlig for prosessoren etter siste GC-syklus? Alt dette kan derfor umulig ligge i cachen, og da oppstår en 100% garantert glipp. Når du arbeider med en peker, på x86, tar det å trekke et register fra minnet 1-2 klokkesykluser, og så snart dette skjer, betaler du, betaler, betaler, fordi minnet er på NI cacher – og dette er kostnadene ved minnetildeling. Virkelig verdi.

Datastrukturer er med andre ord det vanskeligste å endre. Og når du først innser at du har valgt feil datastruktur som vil drepe ytelsen senere, er det vanligvis mye arbeid å gjøre, men hvis du ikke gjør det, vil ting bli verre. Først av alt må du tenke på datastrukturer, dette er viktig. Hovedkostnaden her faller på fete datastrukturer, som begynner å bli brukt i stil med "Jeg kopierte datastruktur X inn i datastruktur Y fordi jeg liker formen til Y bedre." Men kopieringsoperasjonen (som virker billig) sløser faktisk med minnebåndbredde, og det er der all bortkastet utførelsestid er begravet. Hvis jeg har en gigantisk streng med JSON og jeg vil gjøre den om til et strukturert DOM-tre med POJO-er eller noe, vil operasjonen med å analysere den strengen og bygge POJO-en, og deretter få tilgang til POJO-en igjen senere, resultere i unødvendige kostnader - det er ikke billig. Bortsett fra hvis du løper rundt POJOs mye oftere enn du løper rundt en streng. På forhånd kan du i stedet prøve å dekryptere strengen og trekke ut bare det du trenger derfra, uten å gjøre det om til noen POJO. Hvis alt dette skjer på en bane der det kreves maksimal ytelse, ingen POJO-er for deg, må du på en eller annen måte grave deg direkte inn i linjen.

Hvorfor lage ditt eget programmeringsspråk

Andrew: Du sa at for å forstå kostnadsmodellen, må du skrive ditt eget lille språk...

klippe: Ikke et språk, men en kompilator. Et språk og en kompilator er to forskjellige ting. Den viktigste forskjellen er i hodet ditt. 

Andrew: Forresten, så vidt jeg vet, eksperimenterer du med å lage dine egne språk. For hva?

klippe: Fordi jeg kan! Jeg er halvpensjonert, så dette er hobbyen min. Jeg har implementert andres språk hele livet. Jeg jobbet også mye med kodestilen min. Og også fordi jeg ser problemer på andre språk. Jeg ser at det finnes bedre måter å gjøre kjente ting på. Og jeg ville brukt dem. Jeg er bare lei av å se problemer i meg selv, i Java, i Python, på et hvilket som helst annet språk. Jeg skriver nå i React Native, JavaScript og Elm som en hobby som ikke handler om pensjonisttilværelse, men om aktivt arbeid. Jeg skriver også i Python og vil mest sannsynlig fortsette å jobbe med maskinlæring for Java-backends. Det er mange populære språk, og de har alle interessante funksjoner. Alle er gode på hver sin måte, og du kan prøve å bringe alle disse funksjonene sammen. Så jeg studerer ting som interesserer meg, språkets oppførsel, og prøver å komme opp med fornuftig semantikk. Og så langt har jeg lykkes! For øyeblikket sliter jeg med minnesemantikk, fordi jeg vil ha det som i C og Java, og få en sterk minnemodell og minnesemantikk for belastninger og lagre. Har samtidig automatisk typeslutning som i Haskell. Her prøver jeg å blande Haskell-lignende typeslutning med minnearbeid i både C og Java. Det er dette jeg har holdt på med de siste 2-3 månedene for eksempel.

Andrew: Hvis du bygger et språk som tar bedre aspekter fra andre språk, tror du at noen vil gjøre det motsatte: ta ideene dine og bruke dem?

klippe: Det er akkurat slik nye språk dukker opp! Hvorfor ligner Java på C? Fordi C hadde en god syntaks som alle forsto og Java ble inspirert av denne syntaksen, la til typesikkerhet, array bounds checking, GC, og de forbedret også noen ting fra C. De la til sine egne. Men de ble inspirert ganske mye, ikke sant? Alle står på skuldrene til gigantene som kom før deg – det er slik fremgang gjøres.

Andrew: Slik jeg forstår det, vil språket ditt være minnesikkert. Har du tenkt på å implementere noe som en lånesjekk fra Rust? Har du sett på ham, hva synes du om ham?

klippe: Vel, jeg har skrevet C i evigheter, med all denne mallocen og gratis, og manuelt administrert levetiden. Du vet, 90-95 % av manuelt kontrollert levetid har samme struktur. Og det er veldig, veldig smertefullt å gjøre det manuelt. Jeg vil gjerne at kompilatoren bare forteller deg hva som skjer der og hva du oppnådde med handlingene dine. For noen ting gjør lånesjekker dette ut av esken. Og den skal automatisk vise informasjon, forstå alt, og ikke engang belaste meg med å presentere denne forståelsen. Den må i det minste gjøre lokal rømningsanalyse, og bare hvis den mislykkes, må den legge til typekommentarer som beskriver levetiden - og et slikt opplegg er mye mer komplekst enn en lånesjekker, eller faktisk en hvilken som helst eksisterende minnesjekker. Valget mellom "alt er bra" og "Jeg forstår ingenting" - nei, det må være noe bedre. 
Så, som en som har skrevet mye kode i C, tror jeg at det å ha støtte for automatisk livstidskontroll er det viktigste. Jeg er også lei av hvor mye Java bruker minne, og hovedklagen er GC. Når du tildeler minne i Java, vil du ikke få tilbake minnet som var lokalt ved siste GC-syklus. Dette er ikke tilfelle i språk med mer presis minnehåndtering. Ringer du malloc får du umiddelbart minnet som vanligvis bare ble brukt. Vanligvis gjør du noen midlertidige ting med minnet og returnerer det umiddelbart. Og den returnerer umiddelbart til malloc-bassenget, og neste malloc-syklus trekker den ut igjen. Derfor reduseres faktisk minnebruk til settet av levende objekter på et gitt tidspunkt, pluss lekkasjer. Og hvis alt ikke lekker på en helt uanstendig måte, ender det meste av minnet i cacher og prosessoren, og det fungerer raskt. Men krever mye manuell minnehåndtering med malloc og gratis kalt i riktig rekkefølge, på rett sted. Rust kan håndtere dette skikkelig på egen hånd, og gir i mange tilfeller enda bedre ytelse, siden minneforbruket er begrenset til kun gjeldende beregning – i motsetning til å vente på neste GC-syklus for å frigjøre minne. Som et resultat fikk vi en veldig interessant måte å forbedre ytelsen på. Og ganske kraftig - jeg mener, jeg gjorde slike ting når jeg behandlet data for fintech, og dette tillot meg å få en speedup på omtrent fem ganger. Det er et ganske stort løft, spesielt i en verden der prosessorer ikke blir raskere og vi fortsatt venter på forbedringer.

Ytelsesingeniørkarriere

Andrew: Jeg vil også spørre rundt om karrierer generelt. Du ble fremtredende med JIT-arbeidet ditt hos HotSpot og flyttet deretter til Azul, som også er et JVM-selskap. Men vi jobbet allerede mer med maskinvare enn programvare. Og så gikk de plutselig over til Big Data og Machine Learning, og deretter til svindeloppdagelse. Hvordan skjedde dette? Dette er svært ulike utviklingsområder.

klippe: Jeg har programmert ganske lenge og har klart å ta mange forskjellige klasser. Og når folk sier: "å, det var du som gjorde JIT for Java!", er det alltid morsomt. Men før det jobbet jeg med en klone av PostScript – språket som Apple en gang brukte for sine laserskrivere. Og før det gjorde jeg en implementering av Forth-språket. Jeg tror det felles temaet for meg er verktøyutvikling. Hele livet har jeg laget verktøy som andre skriver sine kule programmer med. Men jeg var også involvert i utviklingen av operativsystemer, drivere, debuggere på kjernenivå, språk for OS-utvikling, som startet trivielt, men med tiden ble mer og mer komplekst. Men hovedtemaet er fortsatt utvikling av verktøy. En stor del av livet mitt gikk mellom Azul og Sun, og det handlet om Java. Men da jeg kom inn på Big Data og Machine Learning, tok jeg på meg den fancy hatten igjen og sa: "Å, nå har vi et ikke-trivielt problem, og det er mange interessante ting som skjer og folk som gjør ting." Dette er en flott utviklingsvei å ta.

Ja, jeg elsker distribuert databehandling. Min første jobb var som student i C, på et reklameprosjekt. Dette ble distribuert databehandling på Zilog Z80-brikker som samlet inn data for analog OCR, produsert av en ekte analog analysator. Det var et kult og helt sprøtt tema. Men det var problemer, en del ble ikke gjenkjent riktig, så du måtte ta ut et bilde og vise det til en person som allerede kunne lese med øynene og rapportere hva det sto, og derfor var det jobber med data, og disse jobbene hadde sitt eget språk. Det var en backend som behandlet alt dette – Z80-er som kjørte parallelt med vt100-terminaler som kjørte – én per person, og det var en parallell programmeringsmodell på Z80. Noen felles stykke minne som deles av alle Z80-er innenfor en stjernekonfigurasjon; Bakplanet ble også delt, og halvparten av RAM-en ble delt innenfor nettverket, og en annen halvpart var privat eller gikk til noe annet. Et meningsfullt komplekst parallelt distribuert system med delt ... semi-delt minne. Når var dette... Jeg kan ikke engang huske, et sted på midten av 80-tallet. Ganske lenge siden. 
Ja, la oss anta at 30 år er ganske lenge siden. Problemer knyttet til distribuert databehandling har eksistert ganske lenge; folk har lenge vært i krig med Beowulf-klynger. Slike klynger ser ut som... For eksempel: det er Ethernet og din raske x86 er koblet til dette Ethernet, og nå vil du få falsk delt minne, fordi ingen kunne gjøre distribuert databehandling da, det var for vanskelig og derfor var falskt delt minne med beskyttelsesminnesider på x86, og hvis du skrev til denne siden, så fortalte vi andre prosessorer at hvis de får tilgang til det samme delte minnet, må det lastes fra deg, og dermed noe sånt som en protokoll for å støtte cache koherens dukket opp og programvare for dette. Interessant konsept. Det virkelige problemet var selvfølgelig noe annet. Alt dette fungerte, men man fikk fort ytelsesproblemer, fordi ingen forsto ytelsesmodellene på et godt nok nivå – hvilke minnetilgangsmønstre som var der, hvordan man sørget for at nodene ikke pinget hverandre i det uendelige, og så videre.

Det jeg kom frem til i H2O er at det er utviklerne selv som er ansvarlige for å bestemme hvor parallellitet er skjult og hvor det ikke er det. Jeg kom opp med en kodemodell som gjorde det enkelt og enkelt å skrive høyytelseskode. Men å skrive sakte-løpende kode er vanskelig, det vil se dårlig ut. Du må seriøst prøve å skrive treg kode, du må bruke ikke-standardmetoder. Bremsekoden er synlig ved første øyekast. Som et resultat skriver du vanligvis kode som kjører raskt, men du må finne ut hva du skal gjøre ved delt minne. Alt dette er knyttet til store arrays og oppførselen der ligner på ikke-flyktige store arrays i parallell Java. Jeg mener, forestill deg at to tråder skriver til en parallell matrise, en av dem vinner, og den andre taper følgelig, og du vet ikke hvilken som er hvilken. Hvis de ikke er flyktige, så kan bestillingen være hva du vil – og dette fungerer veldig bra. Folk bryr seg virkelig om rekkefølgen av operasjoner, de legger flyktig på de riktige stedene, og de forventer minnerelaterte ytelsesproblemer på de riktige stedene. Ellers ville de ganske enkelt skrive kode i form av løkker fra 1 til N, der N er noen billioner, i håp om at alle komplekse tilfeller automatisk blir parallelle - og det fungerer ikke der. Men i H2O er dette verken Java eller Scala; du kan vurdere det som "Java minus minus" hvis du vil. Dette er en veldig tydelig programmeringsstil og ligner på å skrive enkel C- eller Java-kode med looper og arrays. Men samtidig kan minnet behandles i terabyte. Jeg bruker fortsatt H2O. Jeg bruker det fra tid til annen i forskjellige prosjekter – og det er fortsatt det raskeste, dusinvis av ganger raskere enn konkurrentene. Hvis du gjør Big Data med kolonnedata, er det veldig vanskelig å slå H2O.

Tekniske utfordringer

Andrew: Hva har vært din største utfordring i hele karrieren?

klippe: Diskuterer vi den tekniske eller ikke-tekniske delen av problemet? Jeg vil si at de største utfordringene ikke er tekniske. 
Når det gjelder tekniske utfordringer. Jeg beseiret dem rett og slett. Jeg vet ikke engang hva den største var, men det var noen ganske interessante som tok ganske mye tid, mental kamp. Da jeg dro til Sun, var jeg sikker på at jeg ville lage en rask kompilator, og en gjeng seniorer sa som svar at jeg aldri ville lykkes. Men jeg fulgte denne veien, skrev en kompilator ned til registerallokatoren, og det gikk ganske raskt. Den var like rask som moderne C1, men allokatoren var mye tregere den gang, og i ettertid var det et stort problem med datastrukturen. Jeg trengte det for å skrive en grafisk registerallokator, og jeg forsto ikke dilemmaet mellom kodeekspressivitet og hastighet, som eksisterte i den tiden og var veldig viktig. Det viste seg at datastrukturen vanligvis overskrider cache-størrelsen på x86-er på den tiden, og derfor, hvis jeg først antok at registerallokatoren ville regne ut 5-10 prosent av den totale jittertiden, så viste det seg i virkeligheten å være 50 prosent.

Etter hvert som tiden gikk, ble kompilatoren renere og mer effektiv, sluttet å generere forferdelig kode i flere tilfeller, og ytelsen begynte i økende grad å ligne det en C-kompilator produserer. Med mindre du selvfølgelig skriver noe dritt som ikke engang C øker hastigheten på. . Hvis du skriver kode som C, vil du få ytelse som C i flere tilfeller. Og jo lenger du gikk, jo oftere fikk du kode som asymptotisk falt sammen med nivå C, registerallokatoren begynte å se ut som noe komplett... uansett om koden din kjører raskt eller sakte. Jeg fortsatte å jobbe med fordeleren for å få den til å gjøre bedre valg. Han ble tregere og tregere, men han ga bedre og bedre prestasjoner i tilfeller der ingen andre orket. Jeg kunne dykke ned i en registerallokator, begrave en måneds arbeid der, og plutselig begynte hele koden å kjøre 5 % raskere. Dette skjedde gang på gang og registerfordeleren ble noe av et kunstverk - alle elsket det eller hatet det, og folk fra akademiet stilte spørsmål om temaet "hvorfor gjøres alt på denne måten", hvorfor ikke linjeskanning, og hva er forskjellen. Svaret er fortsatt det samme: en allokator basert på graffarging pluss veldig nøye arbeid med bufferkoden er lik et seiersvåpen, den beste kombinasjonen som ingen kan beseire. Og dette er en ganske uopplagt ting. Alt annet som kompilatoren gjør der er ganske godt studerte ting, selv om de også har blitt brakt til kunstnivå. Jeg gjorde alltid ting som skulle gjøre kompilatoren til et kunstverk. Men ingenting av dette var noe ekstraordinært – bortsett fra registerfordeleren. Trikset er å være forsiktig kutte ned under belastning, og hvis dette skjer (jeg kan forklare mer detaljert hvis du er interessert), betyr dette at du kan inline mer aggressivt, uten risiko for å falle over en knekk i ytelsesplanen. På den tiden var det en haug med kompilatorer i full skala, hengt med kuler og fløyter, som hadde registerfordelere, men ingen andre kunne gjøre det.

Problemet er at hvis du legger til metoder som er gjenstand for inlining, øker og øker inlining-området, overgår settet med brukte verdier umiddelbart antallet registre, og du må kutte dem. Det kritiske nivået kommer vanligvis når tildeleren gir opp, og en god kandidat for et utslipp er verdt en annen, vil du selge noen generelt ville ting. Verdien av inlining her er at du mister en del av overheaden, overhead for calling og lagring, du kan se verdiene inni og kan optimalisere dem ytterligere. Kostnaden for inlining er at det dannes et stort antall levende verdier, og hvis registertildeleren din brenner opp mer enn nødvendig, taper du umiddelbart. Derfor har de fleste allokatorer et problem: når inlining krysser en viss linje, begynner alt i verden å bli kuttet ned og produktiviteten kan skylles ned i toalettet. De som implementerer kompilatoren legger til noen heuristikk: for eksempel å stoppe inlining, og starter med en tilstrekkelig stor størrelse, siden allokeringer vil ødelegge alt. Slik dannes en knekk i ytelsesgrafen - du inline, inline, ytelsen vokser sakte - og så boom! – den faller ned som en rask jekk fordi du foret for mye. Slik fungerte alt før Java kom. Java krever mye mer inlining, så jeg måtte gjøre allokatoren min mye mer aggressiv slik at den jevner seg ut i stedet for å krasjer, og hvis du inliner for mye, begynner den å søle, men så kommer "ikke mer spilling"-øyeblikket. Dette er en interessant observasjon, og det kom bare til meg fra ingensteds, ikke åpenbart, men det lønnet seg godt. Jeg tok opp aggressiv inlining og det tok meg til steder der Java og C-ytelse fungerer side om side. De er veldig nærme - jeg kan skrive Java-kode som er betydelig raskere enn C-kode og slike ting, men i gjennomsnitt, i det store bildet av ting, er de omtrent sammenlignbare. Jeg tror at en del av denne fordelen er registerfordeleren, som lar meg inline så dumt som mulig. Jeg legger bare inn alt jeg ser. Spørsmålet her er om allokatoren fungerer bra, om resultatet er intelligent fungerende kode. Dette var en stor utfordring: å forstå alt dette og få det til å fungere.

Litt om registertildeling og flerkjerner

Vladimir: Problemer som registertildeling virker som et slags evig, uendelig tema. Jeg lurer på om det noen gang har vært en idé som virket lovende og som deretter mislyktes i praksis?

klippe: Absolutt! Registertildeling er et område der man prøver å finne noen heuristikk for å løse et NP-komplett problem. Og du kan aldri oppnå en perfekt løsning, ikke sant? Dette er rett og slett umulig. Se, Ahead of Time-samling – den fungerer også dårlig. Samtalen her handler om noen gjennomsnittssaker. Om typisk ytelse, så du kan gå og måle noe du synes er god typisk ytelse – du jobber tross alt med å forbedre det! Registertildeling er et tema som handler om ytelse. Når du har den første prototypen, fungerer den og maler det som trengs, ytelsesarbeidet starter. Du må lære deg å måle godt. Hvorfor er det viktig? Hvis du har klare data, kan du se på forskjellige områder og se: ja, det hjalp her, men det var der alt brøt! Noen gode ideer dukker opp, du legger til nye heuristikker og plutselig begynner alt å fungere litt bedre i gjennomsnitt. Eller den starter ikke. Jeg hadde en haug med saker der vi kjempet for de fem prosentene som skilte utviklingen vår fra den forrige allokatoren. Og hver gang ser det slik ut: et sted vinner du, et sted taper du. Hvis du har gode verktøy for ytelsesanalyse, kan du finne de tapende ideene og forstå hvorfor de mislykkes. Kanskje det er verdt å la alt være som det er, eller kanskje ta en mer seriøs tilnærming til finjustering, eller gå ut og fikse noe annet. Det er en hel haug med ting! Jeg laget dette kule hacket, men jeg trenger også denne, og denne, og denne - og den totale kombinasjonen deres gir noen forbedringer. Og ensomme kan mislykkes. Dette er karakteren av ytelsesarbeid på NP-komplette problemer.

Vladimir: Man får følelsen av at ting som å male i tildeler er et problem som allerede er løst. Vel, det er bestemt for deg, etter hva du sier, så er det verdt det da...

klippe: Det er ikke løst som sådan. Det er du som må gjøre det til "løst". Det er vanskelige problemer og de må løses. Når dette er gjort, er det på tide å jobbe med produktiviteten. Du må nærme deg dette arbeidet tilsvarende - gjør benchmarks, samle inn beregninger, forklar situasjoner når, når du rullet tilbake til en tidligere versjon, det gamle hacket ditt begynte å fungere igjen (eller omvendt, stoppet). Og ikke gi opp før du oppnår noe. Som jeg allerede sa, hvis det er kule ideer som ikke fungerte, men når det gjelder tildeling av registre over ideer, er det omtrent uendelig. Du kan for eksempel lese vitenskapelige publikasjoner. Selv om nå dette området har begynt å bevege seg mye saktere og har blitt mer tydelig enn i ungdommen. Imidlertid er det utallige mennesker som jobber på dette feltet, og alle ideene deres er verdt å prøve, de venter alle i vingene. Og du kan ikke fortelle hvor gode de er med mindre du prøver dem. Hvor godt integreres de med alt annet i allokatoren din, fordi en tildeler gjør mange ting, og noen ideer vil ikke fungere i din spesifikke tildeler, men i en annen tildeler vil de enkelt. Den viktigste måten å vinne på for tildeleren er å trekke de langsomme tingene utenfor hovedbanen og tvinge den til å dele seg langs grensene til de langsomme banene. Så hvis du vil kjøre en GC, ta den langsomme banen, deoptimer, kast et unntak, alt det der - du vet at disse tingene er relativt sjeldne. Og de er virkelig sjeldne, sjekket jeg. Du gjør ekstra arbeid og det fjerner mye av restriksjonene på disse langsomme stiene, men det spiller ingen rolle fordi de er trege og sjelden reist. For eksempel en null-peker - det skjer aldri, ikke sant? Du må ha flere veier for forskjellige ting, men de bør ikke forstyrre den viktigste. 

Vladimir: Hva synes du om multikjerner, når det er tusenvis av kjerner på en gang? Er dette en nyttig ting?

klippe: Suksessen til GPU viser at den er ganske nyttig!

Vladimir: De er ganske spesialiserte. Hva med prosessorer for generell bruk?

klippe: Vel, det var Azuls forretningsmodell. Svaret kom tilbake i en tid da folk virkelig elsket forutsigbar ytelse. Det var vanskelig å skrive parallell kode den gang. H2O-kodingsmodellen er svært skalerbar, men den er ikke en generell modell. Kanskje litt mer generelt enn når du bruker en GPU. Snakker vi om kompleksiteten ved å utvikle noe slikt eller kompleksiteten ved å bruke det? For eksempel lærte Azul meg en interessant leksjon, en ganske ikke åpenbar lekse: små cacher er normalt. 

Den største utfordringen i livet

Vladimir: Hva med ikke-tekniske utfordringer?

klippe: Den største utfordringen var å ikke være... snill og hyggelig mot folk. Og som et resultat befant jeg meg hele tiden i ekstreme konfliktsituasjoner. De der jeg visste at ting gikk galt, men ikke visste hvordan jeg skulle gå videre med disse problemene og ikke kunne håndtere dem. Mange langsiktige problemer, som varte i flere tiår, oppsto på denne måten. At Java har C1 og C2 kompilatorer er en direkte konsekvens av dette. At det ikke fantes flere nivåer i Java ti år på rad er også en direkte konsekvens. Det er åpenbart at vi trengte et slikt system, men det er ikke åpenbart hvorfor det ikke fantes. Jeg hadde problemer med en ingeniør... eller en gruppe ingeniører. En gang i tiden, da jeg begynte å jobbe i Sun, var jeg... Ok, ikke bare da, jeg har generelt alltid min egen mening om alt. Og jeg trodde det var sant at du bare kunne ta denne sannheten din og fortelle den på strak arm. Spesielt siden jeg hadde sjokkerende rett mesteparten av tiden. Og hvis du ikke liker denne tilnærmingen ... spesielt hvis du åpenbart tar feil og gjør tull ... Generelt er det få mennesker som kan tolerere denne formen for kommunikasjon. Selv om noen kunne, som meg. Jeg har bygget hele mitt liv på meritokratiske prinsipper. Viser du meg noe galt, snur jeg meg umiddelbart og sier: du sa tull. Samtidig beklager jeg selvfølgelig og alt det der, jeg vil notere fordelene, hvis noen, og ta andre korrekte handlinger. På den annen side har jeg sjokkerende rett om en sjokkerende stor prosentandel av den totale tiden. Og det fungerer ikke særlig godt i forhold til mennesker. Jeg prøver ikke å være hyggelig, men jeg stiller spørsmålet rett ut. "Dette vil aldri fungere, fordi en, to og tre." Og de sa: "Å!" Det var andre konsekvenser som sannsynligvis var bedre å ignorere: for eksempel de som førte til en skilsmisse fra min kone og ti år med depresjon etter det.

Utfordring er en kamp med mennesker, med deres oppfatning av hva du kan eller ikke kan gjøre, hva som er viktig og ikke. Det var mange utfordringer rundt kodestil. Jeg skriver fortsatt mye kode, og på den tiden måtte jeg til og med bremse fordi jeg gjorde for mange parallelle oppgaver og gjorde dem dårlig, i stedet for å fokusere på en. Når jeg ser tilbake, skrev jeg halve koden for Java JIT-kommandoen, C2-kommandoen. Den nest raskeste koderen skrev halvparten så sakte, den neste halvparten så sakte, og det var en eksponentiell nedgang. Den syvende personen i denne rekken var veldig, veldig treg - det skjer alltid! Jeg rørte mye kode. Jeg så på hvem som skrev hva, uten unntak, jeg stirret på koden deres, vurderte hver av dem, og fortsatte fortsatt å skrive mer selv enn noen av dem. Denne tilnærmingen fungerer ikke veldig bra med mennesker. Noen mennesker liker ikke dette. Og når de ikke takler det, begynner alle slags klager. For eksempel ble jeg en gang bedt om å slutte å kode fordi jeg skrev for mye kode og det satte laget i fare, og det hele hørtes ut som en spøk for meg: dude, hvis resten av teamet forsvinner og jeg fortsetter å skrive kode, du vil bare miste halve lag. På den annen side, hvis jeg fortsetter å skrive kode og du mister halve laget, høres det ut som veldig dårlig ledelse. Jeg har egentlig aldri tenkt på det, aldri snakket om det, men det var fortsatt et sted i hodet mitt. Tanken snurret i bakhodet: «tuller dere alle sammen?» Så det største problemet var meg og mitt forhold til mennesker. Nå forstår jeg meg selv mye bedre, jeg var teamleder for programmerere i lang tid, og nå sier jeg direkte til folk: du vet, jeg er den jeg er, og du må forholde deg til meg - er det greit hvis jeg står her? Og da de begynte å håndtere det, fungerte alt. Faktisk er jeg verken dårlig eller god, jeg har ingen dårlige intensjoner eller egoistiske ambisjoner, det er bare min essens, og jeg må leve med det på en eller annen måte.

Andrew: Nylig begynte alle å snakke om selvbevissthet for introverte og myke ferdigheter generelt. Hva kan du si om dette?

klippe: Ja, det var innsikten og lærdommen jeg lærte av min skilsmisse fra min kone. Det jeg lærte av skilsmissen var å forstå meg selv. Slik begynte jeg å forstå andre mennesker. Forstå hvordan denne interaksjonen fungerer. Dette førte til funn etter hverandre. Det var en bevissthet om hvem jeg er og hva jeg representerer. Hva gjør jeg: enten er jeg opptatt av oppgaven, eller så unngår jeg konflikt, eller noe annet – og dette nivået av selvinnsikt hjelper virkelig til å holde meg selv i kontroll. Etter dette går alt mye lettere. En ting jeg oppdaget ikke bare hos meg selv, men også hos andre programmerere, er manglende evne til å verbalisere tanker når du er i en tilstand av følelsesmessig stress. For eksempel, du sitter der og koder, i en tilstand av flyt, og så kommer de løpende til deg og begynner å skrike i hysterisk at noe er ødelagt og nå vil ekstreme tiltak bli tatt mot deg. Og du kan ikke si et ord fordi du er i en tilstand av følelsesmessig stress. Den ervervede kunnskapen lar deg forberede deg på dette øyeblikket, overleve det og gå videre til en retrettplan, hvoretter du kan gjøre noe. Så ja, når du begynner å innse hvordan det hele fungerer, er det en enorm livsendrende begivenhet. 
Selv fant jeg ikke de rette ordene, men jeg husket handlingsrekkefølgen. Poenget er at denne reaksjonen er like mye fysisk som den er verbal, og du trenger plass. Slik plass, i Zen-forstand. Det er nettopp dette som må forklares, og så umiddelbart gå til side - rent fysisk gå unna. Når jeg forblir stille verbalt, kan jeg bearbeide situasjonen følelsesmessig. Når adrenalinet når hjernen din, setter deg over i kamp- eller flymodus, kan du ikke lenger si noe, nei - nå er du en idiot, en piskende ingeniør, ute av stand til en anstendig respons eller til og med stoppe angrepet, og angriperen er fri. å angripe igjen og igjen. Du må først bli deg selv igjen, gjenvinne kontrollen, komme deg ut av "fight or flight"-modusen.

Og til dette trenger vi verbalt rom. Bare ledig plass. Hvis du sier noe i det hele tatt, kan du si akkurat det, og så gå og virkelig finne "plass" for deg selv: gå en tur i parken, lås deg inne i dusjen - det spiller ingen rolle. Det viktigste er å koble fra den situasjonen midlertidig. Så snart du slår av i minst noen sekunder, kommer kontrollen tilbake, du begynner å tenke nøkternt. "Ok, jeg er ikke en slags idiot, jeg gjør ikke dumme ting, jeg er en ganske nyttig person." Når du har klart å overbevise deg selv, er det på tide å gå videre til neste trinn: å forstå hva som skjedde. Du ble angrepet, angrepet kom fra der du ikke forventet det, det var et uærlig, sjofel bakholdsangrep. Dette er dårlig. Det neste trinnet er å forstå hvorfor angriperen trengte dette. Egentlig hvorfor? Kanskje fordi han selv er rasende? Hvorfor er han sint? For eksempel fordi han har skrudd til seg selv og ikke kan ta ansvar? Dette er måten å håndtere hele situasjonen nøye. Men dette krever handlingsrom, verbalt rom. Det aller første trinnet er å bryte den verbale kontakten. Unngå diskusjon med ord. Avbryt det, gå bort så raskt som mulig. Hvis det er en telefonsamtale, er det bare å legge på – dette er en ferdighet jeg lærte av å kommunisere med min ekskone. Hvis samtalen ikke går noe bra, er det bare å si «farvel» og legge på. Fra den andre siden av telefonen: "blah blah blah", svarer du: "yeah, bye!" og legg på. Du avslutter bare samtalen. Fem minutter senere, når evnen til å tenke fornuftig kommer tilbake til deg, har du kjølt deg ned litt, det blir mulig å tenke på alt, hva som skjedde og hva som vil skje videre. Og begynn å formulere et gjennomtenkt svar, i stedet for bare å reagere av følelser. For meg var gjennombruddet i selvbevissthet nettopp det faktum at jeg ved følelsesmessig stress ikke kan snakke. Å komme ut av denne tilstanden, tenke og planlegge hvordan du skal reagere og kompensere for problemer - dette er de riktige trinnene i tilfellet når du ikke kan snakke. Den enkleste måten er å stikke av fra situasjonen der følelsesmessig stress manifesterer seg og rett og slett slutte å delta i dette stresset. Etter det blir du i stand til å tenke, når du kan tenke, blir du i stand til å snakke, og så videre.

Forresten, i retten prøver den motsatte advokaten å gjøre dette mot deg - nå er det klart hvorfor. Fordi han har evnen til å undertrykke deg til en slik tilstand at du ikke engang kan uttale navnet ditt, for eksempel. I en veldig reell forstand vil du ikke kunne snakke. Hvis dette skjer med deg, og hvis du vet at du vil finne deg selv på et sted hvor verbale kamper raser, på et sted som en domstol, så kan du komme med advokaten din. Advokaten vil stå opp for deg og stoppe det verbale angrepet, og vil gjøre det på en helt lovlig måte, og det tapte Zen-rommet vil komme tilbake til deg. For eksempel måtte jeg ringe familien min et par ganger, dommeren var ganske vennlig om dette, men den motsatte advokaten skrek og ropte til meg, jeg klarte ikke engang å få inn et ord. I disse tilfellene fungerer det best for meg å bruke en mekler. Formidleren stopper alt dette presset som strømmer på deg i en kontinuerlig strøm, du finner det nødvendige Zen-rommet, og med det kommer evnen til å snakke tilbake. Dette er et helt kunnskapsfelt der det er mye å studere, mye å oppdage i deg selv, og alt dette blir til strategiske beslutninger på høyt nivå som er forskjellige for forskjellige mennesker. Noen mennesker har ikke problemene beskrevet ovenfor; vanligvis har ikke folk som er profesjonelle selgere dem. Alle disse menneskene som lever av ord – kjente sangere, poeter, religiøse ledere og politikere, de har alltid noe å si. De har ikke slike problemer, men jeg har det.

Andrew: Det var... uventet. Flott, vi har allerede snakket mye og det er på tide å avslutte dette intervjuet. Vi vil definitivt møtes på konferansen og vil kunne fortsette denne dialogen. Vi sees på Hydra!

Du kan fortsette samtalen med Cliff på Hydra 2019-konferansen, som avholdes 11.-12. juli 2019 i St. Petersburg. Han kommer med en rapport "Azul Hardware Transactional Memory-opplevelsen". Billetter kan kjøpes på den offisielle hjemmesiden.

Kilde: www.habr.com

Legg til en kommentar