Cliff Click — A Cratus (IoT érzékelők a folyamatok javításához) vezérigazgatója, több startup, köztük a Rocket Realtime School, Neurensic és H2O.ai alapítója és társ-alapítója, számos sikeres kilépéssel. Cliff 15 évesen írta meg az első fordítóját (Pascal a TRS Z-80 számára)! Legismertebb a Java C2 (a Sea of Nodes IR) munkájáról. Ez a fordító megmutatta a világnak, hogy a JIT képes kiváló minőségű kódot előállítani, amely a Java egyik fő modern programozási platformmá válásának egyik tényezője lett. Később Cliff segített az Azul Systems-nek felépíteni egy 864 magos mainframe-et tiszta Java szoftverrel, amely 500 gigabájtos halommal 10 milliszekundumon belül támogatta a GC szüneteket. Valójában Cliff minden JVM aspektusával foglalkozott.
Ez a huba-post egy nagy interjú Cliff-el. A következő témákról fogunk beszélni:
- Áttérés alacsony szintű optimalizációkra
- Hogyan végezzünk nagy refaktorálást
- Költségmodell
- Alacsony szintű optimalizációk oktatása
- Gyakorlati példák a teljesítmény javítására
- Miért hozzunk létre saját programozási nyelvet
- Karrier a teljesítménymérnökségben
- Technikai kihívások
- Egy kis információ a regiszterek allokációjáról és a többmagosságról
- Életem legnagyobb kihívása
Az interjút vezeti:
- Andrej Satarin az Amazon Web Services-től. Pályafutása során teljesen különböző projekteken dolgozott: tesztelte a Yandex új SQL elosztott adatbázisát, a Kaspersky Labor felhőalapú detektáló rendszerét, egy többjátékos játékot a Mail.ru-n, és egy devizaárazási szolgáltatást a Deutsche Banknál. Érdekli a nagyméretű backend- és elosztott rendszerek tesztelése.
- Vladimir Sitnikov a Netcracker-től. Tíz éve a NetCracker OS teljesítményén és skálázhatóságán dolgozik — a távközlési szolgáltatók által a hálózati folyamatok és berendezések automatizálásához használt szoftver. Érdekli a Java és az Oracle Database teljesítményi kérdései. Több mint egy tucat teljesítményjavítást írt a hivatalos PostgreSQL JDBC illesztőprogramban.
Áttérés alacsony szintű optimalizációkra
Andrej: Ön ismert személy a JIT-kompilálás, a Java és a teljesítménnyel kapcsolatos munkák világában, igaz?
Cliff: Így van!
Andrej: Kezdjünk a teljesítmény optimalizálásának általános kérdéseivel. Mi a véleménye a magas és alacsony szintű optimalizálások közötti választásról, mint például a CPU szintjén végzett munka?
Cliff: Itt minden egyszerű. A leggyorsabb kód az, ami soha nem fut le. Ezért mindig magas szinten kell kezdeni, a algoritmusokon dolgozni. Egy jobb O-notáció megveri a gyengébbet, hacsak nem lépnek be elég nagy konstansok. Az alacsony szintű dolgok jönnek a legutoljára. Általában, ha már elég jól optimalizáltad a teljes stacket, és még mindig maradt valami érdekes – na, az az alacsony szint. De hogyan kezdjük el a magas szinten? Hogyan tudhatjuk, hogy elég munka készült el magas szinten? Nos… nem lehet tudni. Nincsenek kész receptjeink. Meg kell értened a problémát, eldönteni, mit akarsz csinálni (hogy elkerüld a felesleges lépéseket), és csak akkor jöhet elő a profilerszer, ami hasznos információt tud adni. Egy idő után te is megérted, hogy megszabadultál a felesleges dolgoktól, és itt az ideje a finomhangolásnak az alacsony szinten. Ez bizonyos értelemben egy különleges művészeti ág. Rengeteg ember csinál felesleges dolgokat, de olyan gyorsan halad, hogy nincs ideje a teljesítményre ügyelni. De ez addig tart, amíg a kérdés a színpadra nem kerül. Általában az idő 99%-ában senkit sem érdekel, hogy mit csinálok, egészen addig a pillanatig, amikor egy kritikus útvonalon megjelenik valami fontos, amit valakinek fontos. Itt hirtelen mindenki elkezd téged faggatni arról, hogy „miért nem működött tökéletesen az elejétől fogva”. Összességében mindig van mit javítani a teljesítményen. De az idő 99%-ában nincsenek kapaszkodóid! Csak próbálod csinálni, hogy valami működjön, és közben megérted, hogy mi a fontos. Soha nem tudhatod előre, hogy ez a részlet tökéletesnek kell lennie, így lényegében mindenben tökéletesnek kell lenned. De ez lehetetlen, és nem is csinálod így. Mindig van egy csomó dolog, amit meg kell javítani – és ez teljesen normális.
Hogyan végezzünk nagy refaktorálást
Andrej: Hogyan dolgozol a teljesítményen? Ez egy átfogó probléma. Például dolgoztál már olyan problémákon, amelyek a már meglévő funkcionalitások nagy számból adódnak?
Cliff: Próbálom elkerülni ezt. Ha tudom, hogy a teljesítmény problémává válik, akkor mérlegelni kezdem, mielőtt elkezdek kódolni, különösen az adatszerkezeteknél. De gyakran ezekre a problémákra csak sokkal később derül fény. És akkor kénytelen vagyok radikális lépésekre és arra, amit én „írd át és uralkodj”-nak nevezek: meg kell ragadnod egy elég nagy részt. A kód egy részét úgyis át kell írni a teljesítményproblémák miatt, vagy valami más okból. Bármilyen okból is legyen szükség a kód átírására, szinte mindig jobb egy nagyobb részt átírni, mint egy kisebbet. Ekkor mindenki megijed: „Ó istenem, nem szabad ilyen sok kódot érinteni!”. De a valóságban ez a megközelítés szinte mindig sokkal jobban működik. Azonnal neki kell állni a nagy problémának, körüljárni azt nagy körvonalakkal, és mondani: mindent, ami a körön belül van, átírok. A határ sokkal kisebb, mint a bennünket érintő tartalom, amit ki kell cserélni. És ha egy ilyen határvonal kijelölése lehetővé teszi a belső munka tökéletesítését – szabad kezed van, csinálj, amit akarsz. Amint megértetted a problémát, a folyamat sokkal egyszerűbbé válik, ezért harapj le egy nagy falatot!
Ugyanakkor, amikor nagy részt írsz át, és megérted, hogy a teljesítmény problémává válik, azonnal elkezdhetsz aggódni érte. Ez általában egyszerű dolgokká alakul, például „ne másold az adatokat, kezeld az adatokat lehető legegyszerűbben, tedd őket kisebbé”. Nagy átírások esetén normál módszerek vannak a teljesítmény javítására. És ezek szinte mindig az adatok körül forognak.
Költségmodell
Andrej: Egy podcastban a költségmodellekről beszéltél a teljesítmény kontextusában. El tudnád magyarázni, mit értettél ez alatt?
Cliff: Természetesen. Egy olyan korszakban születtem, amikor a processzor teljesítménye rendkívül fontos volt. És ez a kor újra visszatér – a sors nem mentes az iróniától. Nyolcbites gépeken kezdtem el élni, az első számítógépem 256 bájttal dolgozott. Igen, bájtokkal. Minden nagyon kicsi volt. Számolni kellett az utasításokat, és amint elkezdtük felfelé haladni a programozási nyelvek ranglétráján, a nyelvek egyre többet és többet vettek magukra. Volt Assembler, aztán Basic, majd C, és a C vette a kezébe a sok apró részlet kezelését, mint a regiszterek elosztása és az utasítások kiválasztása. De ott minden elég világos volt, és ha mutatót készítettem egy változó példányra, akkor kaptam egy load-ot, és ennek az utasításnak a költsége ismert volt. A hardver egy ismert mennyiségű gépi ciklust ad ki, így a különböző dolgok végrehajtási sebességét egyszerűen úgy lehet kiszámolni, hogy összeadjuk azokat az utasításokat, amelyeket végre akartunk hajtani. Minden compare/test/branch/call/load/store összeadható volt, és megmondhattad: íme a végrehajtási idő. A teljesítményjavítás során mindenképpen észre fogod venni, hogy a számok megfelelnek a forró körtnek.
De ahogy átváltasz a Java-ra, Python-ra és hasonló dolgokra, nagyon gyorsan eltávolodsz a low-level hardvertől. Mi a költsége a getter hívásának Java-ban? Ha a JIT a HotSpotban mindent jól , akkor ez egy load, de ha nem tette – akkor ez egy funkcióhívás lesz. Mivel a hívás a forró ciklusban van, mindent megsemmisít a ciklusban végrehajtott egyéb optimalizációkkal. Ezért a valós költség sokkal magasabb lesz. És azonnal elveszíted a képességed, hogy a kód egy részére nézve megértsd, hogy miért érdemes végrehajtani a processzor órajelsűrűség, a használt memória és a cache fogalmát tekintve. Mindez csak akkor válik érdekessé, ha tényleg alaposan belenéztél a teljesítménybe.
Jelenleg abban a helyzetben vagyunk, hogy a processzorok sebessége szinte egy évtizede már alig növekszik. Visszatérnek a régi idők! Már nem számíthatsz jó egy szálú teljesítményre. De ha véletlenül párhuzamos számításokat szeretnél végezni – az őrült bonyolult, mindenki úgy néz rád, mint James Bondra. A tízszeres gyorsulások általában ott fordulnak elő, ahol valaki valamit elmulasztott. A párhuzamosítás sok munkát igényel. Ahhoz, hogy megszerezd azt a bizonyos tízszeres gyorsulást, meg kell értened a költségmodellt. Mi és mennyibe kerül. Ehhez pedig meg kell érteni, hogyan illeszkedik a nyelv az alatta lévő hardverhez.
Martin Thompson nagyszerű szót talált a blogjához ! Meg kell érteni, hogy a hardver mit szeretne tenni, hogyan fogja ezt végrehajtani, és hogy egyáltalán miért csinálja azt, amit csinál. Ezt kihasználva viszonylag egyszerű elkezdeni számlálni az utasításokat és kideríteni, hová tűnik az idő. Ha viszont nincs megfelelő háttérképzésed, akkor csak egy fekete macskát keresel egy sötét szobában. Folyamatosan látom azokat az embereket, akik a teljesítményoptimalizálással foglalkoznak, de fogalmuk sincs, hogy mit csinálnak. Nagyon szenvednek, és nem haladnak sehová. És amikor én ugyanazt a kódot veszem elő, bedobok néhány apró hack-et, és ötszörös vagy tízszeres gyorsulást érek el, akkor ők azt mondják, hogy ez nem fair, tudtuk, hogy te jobb vagy. Meglepő. Miről is beszéltem… a költségmodell arról szól, hogy milyen kódot írsz, és hogy az átlagosan mennyire gyorsan fut a nagyobb képben.
Andrej: És hogyan lehet így ennyi mindent a fejben tartani? Ez sok tapasztalat révén elérhető, vagy? Hol lehet ilyen tapasztalatot szerezni?
Cliff: Nos, a tapasztalatom nem a legegyszerűbb úton jött. Assembly nyelven programoztam még akkor, amikor minden egyes utasítást meg lehetett érteni. Ez hülyén hangzik, de azóta a fejemben örökre megmaradt a Z80 utasításai. Az emberek neveit nem emlékszem pár perccel a beszélgetés után, de emlékszem a 40 éve írt kódra. Vicces, hogy ez úgy néz ki, mint a "».
Alacsony szintű optimalizációk oktatása
Andrej": Van valami egyszerűbb mód arra, hogy belevágj?
Cliff: Igen és nem. Az az eszköz, amit mindannyian használunk, nem sokat változott azóta. Mindenki x86-ot használ, az Arm-on futó okostelefonokat kivéve. Ha nem valami hardcore beágyazott rendszerekkel foglalkozol, akkor minden ugyanaz. Rendben, tovább. Az utasítások sem változtak évszázadok óta. El kell menned, és írnod kell valamit Assembly nyelven. Kicsit, de eleget ahhoz, hogy elkezdj érteni. Mosolyogsz, de én teljesen komolyan mondom. Meg kell értened a nyelv és a hardver közötti összefüggést. Ezt követően el kell menned, írnod kell kicsit, és készítened kell egy apró, játékos fordítót egy kis játéknyelvhez. A „játékos” azt jelenti, hogy ésszerű időn belül kell elkészítened. Lehet nagyon egyszerű, de generálnia kell utasításokat. Az utasításgenerálás aktusa segít megérteni a költségmodellt a magas szintű kód és a gépi kód között, amely a hardveren fut. Ez az összefüggés észrevehetővé válik az agyadban, amint megírod a fordítót. Még a legegyszerűbb fordító esetében is. Ezt követően elkezdhetsz a Javára nézni, és láthatod, hogy a közte lévő szemantikai szakadék sokkal mélyebb, és sokkal bonyolultabb hidakat kell építened fölé. A Javában sokkal nehezebb megérteni, hogy az általunk épített híd jó vagy rossz lett, mitől fog összeomlani és mitől nem. De szükséged van valamire, amivel elindulsz, amikor nézed a kódot, és megérted: „aha, ennek a getternek minden alkalommal inline-nak kellene lennie.” Ezután kiderül, hogy néha valóban így van, kivéve, amikor a metódus túl nagy lesz, és a JIT mindenfélét kezd inlinelni. Az ilyen helyek teljesítménye azonnal előrejelezhető. Általában a gettereik jól működnek, de aztán a nagy forró ciklusoknál észreveszed, hogy ott különböző függvényhívások úszkálnak, amiknek nem tudni, mit csinálnak. Ez a probléma a geterek elterjedt használatával, az oka, amiért nem inline-olják őket – nem világos, hogy getter-e. Ha nagyon kicsi a kódalapod, azt egyszerűen meg lehet jegyezni, és aztán tudod mondani: ez egy getter, az meg egy setter. Nagy kódalapnál minden egyes függvény a saját történetével rendelkezik, amely senki számára nem igazán ismert. A profilozó azt mondja, hogy 24%-ot veszítettünk el az időnkből egy ciklusban, és ahhoz, hogy megértsük, mit csinál ez a ciklus, meg kell néznünk minden függvényt belül. Ezt nem lehet megérteni anélkül, hogy a függvényt tanulmányoznánk, és ez komolyan lelassítja a megértési folyamatot. Ezért nem használok getereket és setereket, új szintre léptem!
Honnan szerezhetünk költségmodellt? Nos, olvashatunk valamit, persze... De úgy gondolom, a legjobb mód a cselekvés. Készíteni egy kis fordítót, és ez lesz a legjobb módja, hogy megértsük a költségmodellt és elhelyezzük a saját fejünkben. Egy kis fordító, ami alkalmas lenne a mikrosütő programozására - ez egy kezdőknek való feladat. Nos, arra értem, hogy ha már van programozási tudásod, akkor elégséges lesz. Ezek a dolgok, mint például az, hogy parse-oljuk a sztringet, ami bármilyen algebrai kifejezés lesz, kiemelni belőle a matematikai műveletek utasításait a megfelelő sorrendben, a megfelelő értékeket venni a regiszterekből - mindez nagyon gyorsan megoldható. És miközben ezt csinálod, beivódik az agyadba. Azt hiszem, mindenki tudja, hogy mit csinál egy fordító. És ez majd megadja a költségmodell megértését.
Gyakorlati példák a teljesítmény javítására
Andrej: Milyen egyéb dolgokra érdemes figyelni a teljesítmény javítása során?
Cliff: Adatszerkezetek. Egyébként igen, már régóta nem tartottam ilyen órákat… . Ez szórakoztató volt, de ennyi energiát igényelt, és nekem még életem is van! Rendben. Szóval, az egyik nagy és érdekes órán, „Hová tűnik a teljesítményed”, példát mutattam a diákoknak: két és fél gigabájt fintech adatot olvastunk CSV fájlból, és utána meg kellett számolni a eladott termékek számát. Szokásos tick piaci adatok. UDP csomagok, amelyek szöveges formátumra lettek átírva, 70-es évektől kezdődően. Chicago Mercantile Exchange – mindenféle dolog, mint olaj, kukorica, szójabab és hasonlók. Meg kellett számolni ezeket a termékeket, az ügyletek számát, a pénz és javak átlagos mozgását stb. Ez meglehetősen egyszerű kereskedelmi matematika: megkeresni a termékkódot (ez 1-2 karakter a hash táblázatban), megkapni az összeget, hozzáadni azt az ügyletek egyik halmazához, hozzáadni a volumenét, hozzáadni a költségét és néhány más dolgot. Nagyon egyszerű matematika. A játékos implementáció nagyon egyenes volt: minden a fájlban volt, olvastam a fájlt és haladtam rajta, a különálló rekordokat Java sztringekké osztva, megkerestem bennük a szükséges dolgokat és összeadtam a fent leírt matematika szerint. És ez bizonyos kis sebességgel működött.
Ezzel a megközelítéssel nyilvánvaló, hogy mi történik, és a párhuzamos számítások itt nem segítenek, igaz? Kiderül, hogy az teljesítmény ötszörös növelését csupán a megfelelő adatszerkezetek kiválasztásával el lehet érni. Ez még a tapasztalt programozókat is meglepi! A konkrét esetemben az a lényeg, hogy nem érdemes memóriát foglalni a forró ciklusban. Nos, ez nem az egyetlen igazság, de alapvetően nem érdemes „X-enként” foglalni, amikor X elég nagy. Amikor X két és fél gigabájt, nem érdemes semmit „betűnként”, vagy „soronként”, vagy „mezőnként” foglalni, semmi ilyesmi. Ez viszi el az időt. Hogyan működik ez egyáltalán? Képzeld el, hogy meghívom a String.split() vagy BufferedReader.readLine(). Readline egy sort készít a hálózaton érkező bájtok halmazából, egyszer minden egyes sorhoz, több százmillió sorhoz. Ezt a sort megkapom, elemezgetem, majd eldobom. Miért dobom el? Nos, már feldolgoztam, ennyi. Tehát, minden egyes bájtnál, amit ezekből a 2.7G-ból olvasok, két karaktert fogok írni a sorba, azaz már 5.4G-t, és arra nekem továbbra sincs szükségem, ezért eldobom. Ha megnézed a memória sávszélességét, akkor betöltjük a 2.7G-t, ami átmegy a memórián és a memória buszon a processzorba, és ezután kétszer annyi megy a memóriában lévő sorba, és mindez elkészül minden új sor létrehozásakor. De nekem még olvasnom kell, a hardver olvassa, még ha később mindent újra feldolgoznak is. Írnom kell, mert létrehoztam egy sort, és a cache-ek megteltek – a cache nem tud 2.7G-t elhelyezni. Tehát, minden olvasott bájt után még két további bájtot olvasok és kettőt írok, így a viszonyuk 4:1 – ebben a viszonyban pazarlóan használjuk a memória sávszélességét. Aztán kiderül, hogy ha csinálom String.split() – akkor ezt messze nem az utolsó alkalommal teszem, belül még 6-7 mező is lehet. Ezért a klasszikus CSV olvasási kód, amely után a sorokat elemzi, a memória sávszélesség elvesztéséhez vezet 14:1 arányban ahhoz képest, amit valójában szeretnél. Ha ezeket a foglalásokat eltávolítod, ötszörös gyorsítást érhetsz el.
És ez nem is olyan nehéz. Ha a kódra a megfelelő szögből nézel, minden meglehetősen egyszerűvé válik, amint megérted a probléma lényegét. Soha ne hagyd abba a memória allokálását: a probléma csupán annyi, hogy valamit allokálsz, és az azonnal elhalálozik, miközben fontos erőforrást éget el, amely ebben az esetben a memória sávszélessége. Mindez teljesítménycsökkenéshez vezet. Az x86-os rendszereken általában aktívan kell égetni a processzorciklusokat, de itt már jóval korábban elégetted az összes memóriát. A megoldás – csökkenteni kell az allokálások számát.
A probléma másik része, hogy ha a profiler-t akkor indítod el, amikor a memória sávszélessége elfogyott, éppen abban a pillanatban, amikor ez történik, általában a cache visszatérésére vársz, mert tele van a szeméttel, amit éppen generáltál, mindezekkel a sorokkal. Ezért minden load vagy store művelet lassúvá válik, mivel cache miss-hez vezetnek – az egész cache lassú lett, miközben arra vár, hogy a szemét eltűnjön. A profiler tehát csak meleg, véletlenszerű zajt mutat, amely az egész ciklus során elmosódik – nem lesz külön forró utasítás vagy hely a kódban. Csak zaj. És ha megnézed a GC ciklusokat, azok mind a Young Generation-ben lesznek, és szupergyorsak – mikro- vagy milliszekundum maximum. Mivel ez a memória azonnal hal meg. Te milliárd gigabájtot allokálsz, és ő vágja, és vágja, és újra vágja. Mindez nagyon gyorsan történik. Így olcsó GC ciklusaink vannak, meleg zaj az egész ciklusban, de mi ötvenszeres teljesítménynövelést akarunk. Ebben a pillanatban valaminek záródnia kell a fejedben, és megszólalnia: „miért van ez így?!”. A memória sávszélesség túlcsordulása nem jelenik meg a klasszikus debuggerben, a hardveres teljesítménymérője debuggert kell indítanod, hogy ezt önállóan és közvetlenül láthasd. Közvetetten ezt a három tünetből lehet gyanítani. A harmadik tünet az, amikor nézed, mit allokálsz, megkérdezed a profilt, és ő válaszol: „Te milliárd sort készítettél, de a GC ingyen dolgozott.” Amint ez megtörténik, rájössz, hogy túl sok objektumot generáltál, és elégettél minden memória sávszélességet. Van mód arra, hogy ezzel foglalkozz, de nem nyilvánvaló.
A probléma az adatszerkezetben rejlik: a mögöttes alapstruktúra túl nagy, 2.7G a lemezen, ezért nagyon nem kívánatos ezt a dolgot lemásolni - szeretnénk közvetlenül a hálózati bájtbuferből betölteni a regiszterekbe, hogy ne olvassunk-írjunk öt alkalommal oda-vissza. Sajnos a Java alapértelmezés szerint nem biztosít ilyen könyvtárat a JDK-ban. De igazából ez triviális, nem? Lényegében 5-10 sor kód szükséges a saját pufferezett sorbetöltőnk megvalósításához, amely a karakterlánc osztály viselkedését utánozza, miközben a mögöttes bájtbufert burkolja. Ennek eredményeként úgy tűnik, hogy szinte karakterláncokkal dolgozol, de valójában a mutatók mozognak a bufferben, és a nyers bájtok nem másolódnak sehova, így ugyanazokat a buffereket használod újra és újra, miközben az operációs rendszer boldogan átvállalja azokat a feladatokat, amelyekre készült, így például a láthatatlan duplapufferezést ezekkel a bájtbuferekkel, és te már nem őrlöd a szükségtelen adatfolyamot. Üdvözöljük! Tudják, hogy a GC használatakor garantált, hogy a memória minden egyes kiosztása nem látható a processzor számára az utolsó GC ciklus után? Ezért mindez nem lehet a gyorsítótárban, és ezután egy 100%-os hibát okoz. Az irányító használata során az x86-on a regiszter memóriaolvasása 1-2 ciklusba telik, és amint ez megtörténik, neked fizetned kell, fizetned kell, fizetned kell, mert az összes memória a – és ez az a valódi költség, ami a memória kiosztásából származik. Valódi költség.
Más szóval, az adatszerkezetek azok, amik a legnehezebben módosíthatók. Amint belátja, hogy rossz adatszerkezetet választott, ami később rontani fogja a teljesítményt, általában jelentős munkát kell elvégeznie, de ha ezt nem teszi meg, a helyzet csak rosszabb lesz. Mindenekelőtt az adatszerkezetekre kell gondolni, ez fontos. A fő költség itt a nehéz adatszerkezetekre hárul, amelyeket az „X adatszerkezetet átmásoltam a Y adatszerkezetbe, mert Y nekem jobban tetszik” stílusban kezdenek használni. De a másolási művelet (amely olcsónak tűnik) valójában memóriát igényel, és itt van eltemetve az elveszett futási idő. Ha van egy hatalmas JSON szövegem, és azt szeretném átalakítani egy strukturált DOM-fává POJO-ból vagy valami ilyesmiből, az e szöveg feldolgozási művelete és a POJO felépítése, majd később újabb hívás a POJO-ra extraköltséggé válik – ami nem olcsó. Kivéve, ha sokkal gyakrabban fut ezek között a POJO-k között, mint a szöveg között. Éppen ezért, próbálhatja dekódolni a szöveget, és csak a szükséges dolgokat kiemelni anélkül, hogy bármilyen POJO-ba átalakítaná. Ha mindez a maximális teljesítményt igénylő úton történik, semmiféle POJO-ra nincs szükség – közvetlenül a szövegben kell matatni.
Miért hozzunk létre saját programozási nyelvet
Andrej: Azt mondta, hogy az ármodell megértéséhez, szükség van egy kis saját nyelvet írni...
Cliff: Nem nyelvet, hanem fordítót. A nyelv és a fordító két külön dolog. A legfontosabb különbség – a fejünkben van.
Andrej: Egyébként, amennyire tudom, Ön kísérletezik saját nyelvek létrehozásával. Miért?
Cliff: Mert azért csinálom! Félig nyugdíjas vagyok, így ez a hobbim. Életem során mindig mások nyelveit valósítottam meg. Ráadásul sokat dolgoztam a kódolási stíluson is. Ezen kívül látom a problémákat más nyelvekben. Látom, hogy vannak jobb módok a megszokott dolgok elvégzésére. Én is igénybe venném őket. Egyszerűen elegem van abból, hogy problémákat látok magamban, a Java-ban, a Python-ban, bármely más nyelvben. Jelenleg React Native, JavaScript és Elm nyelven írok, mint hobbi, ami nem a nyugdíjról szól, hanem az aktív munkáról. Python-on is írok, és valószínűleg továbbra is a gépi tanulásra fogok összpontosítani a Java backendekhez. Számos népszerű nyelv létezik, és mindegyiknek megvannak az érdekes sajátosságai. Mindegyik valamiben jó, és meg lehet próbálni ezeket az érdekességeket egyesíteni. Szóval, érdekes dolgok tanulmányozásával foglalkozom, a nyelv viselkedésével, próbálok ésszerű szemantikát kitalálni. És eddig jól megy! Jelenleg a memória szemantikájával küzdök, mert olyat szeretnék, mint a C és Java, és szeretnék egy erős memória modellt és memória szemantikát a betöltésekhez és tárolásokhoz. Közben szeretném, ha automatikusan következne a típuskiadás, mint a Haskell-nél. Na ez az, amit az utolsó 2-3 hónapban próbálok összekombinálni, például.
Andrej: Ha egy olyan nyelvet építesz, ami a többi nyelv legjobb aspektusait veszi át, gondoltál már arra, hogy valaki visszafelé is megteszi: átveszi az ötleteidet, és felhasználja az övében?
Cliff: Így jelennek meg az új nyelvek! Miért hasonlít a Java a C-re? Mert a C-nek volt egy jó szintaxisa, amit mindenki értett, és a Java inspirálódott ebből a szintaxisból, hozzáadva a típusbiztonságot, a tömbhatárok ellenőrzését, a GC-t, valamint javítva néhány dolgot a C-ből. Alapvetően saját dolgokat is hozzáadtak. De erősen inspirálódtak, nem igaz? Mindenki óriások vállán áll, akik előttük voltak – pontosan így történik a fejlődés.
Andrej: Ahogy értem, a nyelved biztonságosan fog bánni a memóriával. Gondolkodtál arról, hogy megvalósíts valami hasonlót a Rust-ból származó borrow checkerhez? Nézted már? Mi a véleményed róla?
Cliff: Nos, már egy ideje C nyelven írok, mindenféle malloc-al és free-dal, és manuálisan kezelem az élettartamot. Tudjátok, az összes manuálisan kezelt élettartam 90-95%-a ugyanazzal a struktúrával rendelkezik. És ez nagyon, nagyon fájdalmas, hogy ezt manuálisan csináljam. Jó lenne, ha a fordító csak megmondaná, hogy mi történik, és mit értél el a tetteiddel. Egyes dolgoknál a borrow checker ezt automatikusan megteszi. Emellett automatikusan ki kellene írnia az információt, mindent értenie kellene, és még csak nem is terhelnie engem azzal, hogy ezt az érthetőséget kifejtse. Legalább helyi escape analízist kellene végeznie, és csak akkor, ha ezzel nem boldogul, lenne szükség típus annotációk hozzáadására, amelyek az élettartamot írják le - és egy ilyen séma sokkal bonyolultabb, mint a borrow checker, vagy bármilyen létező memóriaellenőr. A választás a 'minden rendben' és a 'semmit sem értettem' között - nem, kellene valami jobb.
Tehát, mint valaki, aki sok kódot írt C-ben, úgy érzem, hogy az automatikus élettartam-kezelés támogatása a legfontosabb dolog. Ráadásul már unom, mennyire nagy memóriát használ a Java, és a fő panasz a GC-re vonatkozik. Amikor memóriát foglalsz a Java-ban, nem kapod vissza azt a memóriát, amely a legutolsó GC ciklusban lokális volt. Olyan nyelveknél, ahol a memória kezelése pontosabb, ez nem így van. Ha a malloc-ot hívod, azonnal megkapod a memóriát, amit általában csak most használtak. Általában valamilyen ideiglenes dolgot csinálsz a memóriával, majd azonnal visszaadod. Az pedig azonnal visszakerül a malloc poolba, és a következő malloc ciklus ismét előhúzza. Ezért a tényleges memóriahasználat csökken a konkrét időpontban lévő élő objektumok számának, plusz a memória szivárgások miatt. És ha semmi nem szivárog teljesen tisztességtelen módon, a memória nagy része a gyorsítótárakban és a processzorban ül, és ez gyorsan működik. De sok kézi memória kezelés igényel a malloc és free használatával, a megfelelő sorrendben, megfelelő helyen. A Rust helyesen képes kezelni ezt, és sok esetben még nagyobb teljesítményt is nyújt, mivel a memóriafogyasztás csak az aktuális számításokra szűkül – szemben a következő GC ciklus várakozásával, ami felszabadítja a memóriát. Ennek eredményeként nagyon érdekes módot kaptunk a teljesítmény javítására. Ráadásul elég erős – tekintve, hogy ilyen dolgokat csináltam a pénzügyi technológiák adatfeldolgozásánál, és ez körülbelül ötszörös gyorsulást eredményezett. Ez elég nagy gyorsulás, különösen egy olyan világban, ahol a processzorok nem válnak gyorsabbá, és még mindig várjuk a fejlesztéseket.
Karrier a teljesítménymérnökségben
Andrej: Szeretnék még kérdezni a karrieredről összességében. Híressé váltál a JIT HotSpot munkád miatt, majd átkerültél az Azulhoz – ami szintén JVM-cég. De már inkább hardverrel foglalkoztál, mint szoftverrel. Aztán hirtelen átváltottál a Big Data-ra és a gépi tanulásra, majd a csalásészlelésre. Hogy történt ez? Ezek nagyon különböző fejlesztési területek.
Cliff: Már egy ideje programozással foglalkozom, és sokféle tevékenységben részt vettem. Amikor az emberek azt mondják: „ó, te vagy az, aki a JIT-et készítette a Java-hoz!”, mindig vicces. Előtte egy PostScript klónnal foglalkoztam – azzal a nyelvvel, amelyet az Apple valaha a lézernyomtatóihoz használt. Mielőtt pedig a Forth nyelv megvalósításával foglalkoztam. A központi téma számomra mindig az eszközök fejlesztése volt. Az életem során mindig olyan eszközöket készítettem, amelyek segítségével mások nagyszerű programokat írhatnak. De foglalkoztam operációs rendszerek, illesztőprogramok, kernel szintű debugger-ek, operációs rendszerek fejlesztésére szánt nyelvek kidolgozásával, amelyeket kezdetben egyszerűnek indultak, de idővel egyre bonyolultabbá váltak. Az alapvető téma azonban mégis az eszközök fejlesztése. Nagy része az életemnek az Azul és a Sun között telt el, és ez Java-ról szólt. De amikor a Big Data és a Machine Learning területére terelődtem, újra felvettem a „formális kalapomat”, és azt mondtam: „Ó, most van egy nem triviális problémánk, és itt tényleg rengeteg érdekes dolog és ember van, aki valamit csinál.” Ez egy nagyszerű fejlesztési út, amelyet érdemes végigjárni.
Igen, nagyon szeretem a diszkrét számítástechnikát. Az első munkám egyetemi éveim alatt volt C nyelven, egy reklámprojekten. Ez diszkrét számításokat végzett Zilog Z80 chipeken, amelyek adatokat gyűjtöttek analóg optikai szövegfelismeréshez, amit egy valódi analóg elemző végzett. Ez egy izgalmas és teljesen szokatlan téma volt. De problémák merültek fel, egyes részeket nem ismertek fel jól, ezért a képet ki kellett venni és megmutatni egy olyan személynek, aki már látta a szöveget, és közölte, hogy mi áll benne, ezért ott adatokkal foglalkozó feladatok voltak, és ezeknek a feladatoknak saját nyelvük volt. Volt egy backend, ami mindezt feldolgozta – párhuzamosan működő Z80-asok, mindegyikhez egy vt100 terminál, és volt egy párhuzamos programozási modell Z80-on. Egy közös memória, amit az összes Z80 megosztott egy csillag típusú konfiguráción belül; a backplane és a RAM fele megosztott volt a hálózatban, a másik fele pedig privát vagy más célokra ment. Értelmesen bonyolult párhuzamos elosztott rendszer megosztott... félig megosztott memóriával. Mikor is volt ez... Már nem is emlékszem, valamikor a 80-as évek közepén. Elég régen.
Igen, tekintsük úgy, hogy 30 év elég régen van. A diszkrét számítástechnikával kapcsolatos feladatok már régóta léteznek, az emberek régóta küzdenek ezzel. - klaszterekkel. Az ilyen klaszterek úgy néznek ki, mint… Például: van Ethernet, és a gyors x86 géped csatlakozik ehhez az Ethernethez, és most szeretnél egy hamis megosztott memóriát kapni, mert akkor senki sem tudott foglalkozni a párhuzamos számítással, ez túlságosan bonyolult volt, ezért készült egy hamis megosztott memória x86-on, amely memóriavédelemmel rendelkezett, és ha írtál ebbe az oldalba, akkor azt mondtuk a többi processzornak, hogy ha hozzáférnek ugyanahhoz a megosztott memóriához, akkor azt tőled kellett betölteni, így jött létre valami a gyorsítótárkoherencia támogatási protokolljából és a szoftverből. Érdekes koncepció. A valódi probléma nyilvánvalóan más volt. Mindez működött, de gyorsan teljesítményproblémák merültek fel, mert senki sem értette a teljesítmény modelljét elég jól – mik a memória-hozzáférési minták, hogyan lehet úgy beállítani, hogy a csomópontok ne pingeljék egymást végtelenül, és így tovább.
Az H2O-ban azt találta ki, hogy maguk a fejlesztők felelősek azért, hogy meghatározzák, hol rejlik a párhuzamosság és hol nem. Olyan kódolási modellt találtam ki, ami lehetővé tette, hogy könnyen és egyszerűen írjunk nagy teljesítményű kódot. Ellenben nehéz lassú kódot írni, az rosszul fog mutatni. Komoly erőfeszítést igényel, hogy lassú kódot írjunk, szokatlan módszereket kell alkalmaznunk. A lassítókód azonnal látható. Ennek következményeként általában gyorsan futó kódot írunk, de közben meg kell értenünk, hogy mit kell tenni megosztott memória esetén. Mindez nagy tömbökhöz kötődik, és a viselkedésük hasonlít a nem volatilis nagy tömbökhöz a párhuzamos Java-ban. Képzelje el, hogy két szál párhuzamos tömbbe ír, az egyik nyer, a másik elveszít, és nem tudja, ki melyik. Ha nem volatilisak, akkor a sorrend bármi lehet – és ez valóban jól működik. Az emberek valóban törődnek a műveletek sorrendjével, helyesen helyezik el a volatilis kulcsszót, és a megfelelő helyeken előre várják a memóriával kapcsolatos teljesítményproblémákat. Ellenkező esetben csak kódot írnának 1-től N-ig, ahol N valamilyen trillió, abban a reményben, hogy az összes bonyolult eset automatikusan párhuzamossá válik – és itt ez nem működik. De az H2O nem Java, és nem is Scala, ha úgy tetszik, nevezzük „Java minus minus”-nak. Ez egy nagyon érthető programozási stílus, és hasonlít a C vagy a Java egyszerű kódjának írására, ciklusokkal és tömbökkel. De emellett terabájtokkal lehet dolgozni a memóriában. Még mindig használom az H2O-t. Időről időre különböző projektekben alkalmazom – és ez még mindig a leggyorsabb dolog, több tízszeresen megelőzve a versenytársakat. Ha Big Data-val dolgozik oszlopos adatokkal, nagyon nehéz felülmúlni az H2O-t.
Technikai kihívások
Andrej: Mi volt a legnagyobb kihívás a karrierje során?
Cliff: A technikai vagy nem technikai részét beszéljük a kérdésnek? Azt mondanám, a legnagyobb kihívások nem technikaiak.
Ami a technikai kihívásokat illeti. Egyszerűen legyőztem őket. Nem is tudom, mi volt a legnagyobb, de volt néhány elég érdekes, amire sok időt és mentális küzdelmet fordítottam. Amikor a Sunhoz mentem, biztos voltam benne, hogy gyors fordítót készítek, de sok idősebb kolléga azt mondta, hogy soha nem fog sikerülni. De elindultam ezen az úton, megírtam a fordítót a regiszterallokátorig, és elég gyors lett. Olyan gyors volt, mint a modern C1, de akkor a regiszterallokátor sokkal lassabb volt, és visszatekintve – ez nagy adatszerkezet problémája volt. Szükségem volt rá, hogy megírjam a grafikus regiszterallokátort, és nem értettem a kód kifejezőereje és a sebessége közötti dilemmát, ami abban az időben létezett és nagyon fontos volt. Kiderült, hogy az adatszerkezet általában meghaladja a cache méretét az akkori x86-okon, és ezért, ha eredetileg arra számítottam, hogy a regiszterallokátor 5-10 százalékát fogja eltölteni a JIT-elés idejének, a valóságban ez 50 százalékra nőtt.
Az idő telt, a fordító egyre világosabbá és teljesítményesebbé vált, egyre ritkábban generált borzalmas kódot, és a teljesítmény egyre inkább hasonlított arra, amit a C fordító nyújt. Ha persze nem egy olyan vacakot írsz, amit még a C sem gyorsít. Ha C-szerűen írsz kódot, akkor több esetben is olyan teljesítményt kapsz, mint a C. És minél messzebb jutottam, annál gyakrabban adott kódot, amely aszimptotikusan megegyezett a C szintjével, a regiszterallokátor kezdetben valami befejezett dolognak tűnt... függetlenül attól, hogy a kódod gyorsan vagy lassan fut. Folytattam az allokátor fejlesztését, hogy jobb allokációkat végezzen. Egyre lassabb lett, de egyre jobb teljesítményt nyújtott azokban az esetekben, amikor senki más nem tudta. Be tudtam merülni a regiszterallokátorba, hónapokat ástam el benne, és hirtelen az egész kód 5%-kal gyorsabban kezdett futni. Ez újra és újra megtörtént, és a regiszterallokátor valami művészeti alkotássá vált – mindenki imádta vagy utálta, és az akadémikusok kérdéseket tettek fel, hogy "miért úgy csináltál mindent". , és mi a különbség. A válasz ugyanaz: a gráf színezésén alapuló alloátor és egy nagyon pontos pufferkód-kezelés együtt a győzelem eszköze, a legjobb kombináció, amit senki sem tud legyőzni. És ez meglehetősen nem nyilvánvaló dolog. Minden más, amit a fordító csinál – ezek meglehetősen jól ismert dolgok, bár művészi szintre emelték őket. Mindig olyan dolgokat csináltam, amik a fordítót műalkotássá kellett volna varázsolniuk. De semmi ebből nem volt rendkívüli – kivéve a regiszterek alloátorát. A lényeg, hogy óvatosan terhelés alatt, és ha ez megtörténik (részletesebben elmagyarázhatom, ha érdekes), az azt jelenti, hogy agresszívebben lehet inline-olni, anélkül, hogy kockáztatnánk a teljesítménygörbe áthágását. Akkoriban rengeteg teljes körű fordító létezett, tele trükkökkel és csilingelőkkel, amelyekben voltak regiszter alloátorok, de senki más nem tudta ezt így megvalósítani.
A probléma az, hogy ha inline módszereket adsz hozzá, és ezzel folyamatosan növeled az inline területet, a felhasznált értékek készlete azonnal meghaladja a regiszterek számát, és spillage-hez vezet. A kritikus szint gyakran akkor érkezik el, amikor az allokátor feladja, és egy jó spillage-jelölt felér egy másikkal, tehát valóban furcsa dolgokat spillelsz el. Az inline értékének lényege, hogy elveszítesz egy részt a hívás és tárolás költségéből, szemben láthatod az értékeket, és tovább optimalizálhatod azokat. Az inline költsége abban rejlik, hogy nagy mennyiségű élő érték keletkezik, és ha az allokátorod regiszterből többet spillel, mint amennyire szükség van, azonnal vesztesz. Éppen ezért a legtöbb allokátor hátrányban van: amikor az inline egy bizonyos határt átlép, mindent spill el, és a teljesítményed elmegy a vécébe. Azok, akik a fordítót implementálják, bizonyos heurisztikákat adnak hozzá: például megállítják az inline-t, ha egy elegendően nagy méretet elér, mivel az allokációk mindent elrontanak. Így keletkezik a teljesítménygörbe törése - inline-olsz, inline-olsz, a teljesítmény lassan nő - aztán puff! - hirtelen esik egy nagyot, mert túl sokat inline-oltál. Így működött minden, egészen a Java megjelenéséig. A Java sokkal több inlininget igényel, ezért sokkal agresszívebb allokátorra volt szükségem, hogy az egyensúlyban maradjon, és ne essék vissza, és ha túl sokat inline-oltál - elkezd spillage-t csinálni, de végül mégis elérkezik az a pillanat, amikor „nincs több spillage”. Ez egy érdekes megfigyelés, és szinte a semmiből érkezett, nem nyilvánvaló, de jól megtérült. Aggresszív inline-ozásba kezdtem, és ez olyan helyekre vezetett, ahol a Java és a C teljesítménye párhuzamosan fut. Valóban közel állnak egymáshoz – tudok olyan Java kódot írni, amely jelentősen gyorsabb, mint a C kód, és hasonlók, de átlagosan, a nagy kép szempontjából, körülbelül hasonlóak. Azt hiszem, ennek a részének köszönhető az allokátor, amely lehetővé teszi számomra, hogy a lehető legegyszerűbben inline-oljak. Egyszerűen mindent inline-olok, amit látok. A kérdés itt az, hogy jól működik-e az allokátor, és az eredmény egy értelmesen működő kódot ad-e. Ez volt a nagy kihívás: mindezt megérteni, és működésre bírni.
Egy kis információ a regiszterek allokációjáról és a többmagosságról
Vlagyimir: Az olyan problémák, mint a regiszterek allokálása, valóságosan végtelen témának tűnnek. Kíváncsi vagyok, volt-e olyan ötlet, ami ígéretesnek tűnt, de a gyakorlatban megbukott?
Cliff: Természetesen! A regiszterek allokálása az a terület, ahol az NP-teljes feladat megoldása érdekében próbálsz valamilyen heuristikát találni. Soha nem fogsz tudni tökéletes megoldást elérni, nem igaz? Ez egyszerűen lehetetlen. Nézd, az Ahead of Time fordítás is rosszul működik. Itt átlagos esetekről van szó. A tipikus teljesítményről, így elmehetsz és mérheted valami olyasmit, amit jónak tartasz a tipikus teljesítmény szempontjából – végül is, te dolgozol a fejlesztésén! A regiszterek allokálása teljesen a teljesítményről szól. Amint megvan az első prototípus, ami működik és a szükséges dolgokat megcsinálja, jön a teljesítménnyel való munka. Meg kell tanulnod jól mérni. Miért fontos ez? Ha vannak világos adatok, megnézheted a különböző részeket és láthatod: aha, itt segített, de ott minden elromlott! Mindenféle jó ötlet felmerül, hozzáadsz egy új heuristikát és hirtelen minden átlagosan kicsit jobban működik. Vagy nem működik. Rengeteg esetem volt, amikor az öt százaléknyi teljesítményért küzdöttünk, ami különböztette meg a fejlesztésünket az előző allokátortól. És minden alkalommal úgy tűnik, hogy itt-ott elnyertük a csatát. Ha jó teljesítmény-elemző eszközeid vannak, meg lehet találni a gyengélkedő ötleteket és megérteni, miért nem működnek. Lehet, hogy érdemes mindent úgy hagyni, ahogy van, vagy talán komolyabban bele kell merülni a finomhangolásba, vagy elmenni és megjavítani valami mást. Ez egy egész csomag! Készítettem ezt a szuper hackot, de szükség van erre, arra és arra is – és ezek összegzett kombinációja hoz bizonyos javulásokat. Az egyszemélyes megoldások kudarcra is ítélhetők. Ilyen a teljesítmény NP-teljes feladatán való munkálkodás természete.
Vlagyimir: Úgy tűnik, hogy az olyan dolgok, mint az allokátorokban a festés – már megoldott feladatok. Nos, az Önök számára megoldott, a beszámolója alapján, így érdemes egyáltalán...
Cliff: Ez nem megoldott, ahogyan van. Neked kell «megoldottá» tenned. Vannak nehéz feladatok, és azokat meg kell oldani. Amikor ez megtörtént, elérkezik az idő a teljesítmény javítására. Ehhez a munkához megfelelően kell viszonyulni – benchmarkokat kell végezni, metrikákat gyűjteni, és elmagyarázni azokat a helyzeteket, amikor a korábbi verzióhoz való visszatéréskor a régi hacked újra működni kezdett (vagy éppen ellenkezőleg, megállt). És nem szabad visszariadni, amíg valamit el nem érsz. Ahogy már mondtam, ha izgalmas ötletek vannak, amelyek nem működtek, de a regiszterek allokálásának területén a lehetőségek szinte végtelenek. Például olvashatsz tudományos publikációkat. Bár mostanában ez a terület sokkal lassabban fejlődik, és világosabbá vált, mint ifjúsága idején. Mindazonáltal ezen a területen végtelen számú ember dolgozik, és minden ötletet érdemes kipróbálni, mind várja a maga idejét. És nem mondhatod, hogy mennyire jók, ha nem próbálod ki őket. Mennyire integrálódnak mindennel a te allokátorodban, hiszen az allokátor sok mindent csinál, és egyes ötletek a te konkrét allokátorodban nem működnek, míg egy másik allokátorban simán. Az allokátor fő győzelmi módja, hogy a lassú dolgokat kikerüli az alapúton, és kényszerítve osztja szét a lassú utak határain. Tehát, ha el akarod indítani a GC-t, lassú úton akarsz menni, deoptimalizálni, kivételt dobni, ilyesmi – tudod, hogy ezek a dolgok viszonylag ritkák. És valóban ritkák, ellenőriztem. További munkát végzel, és így sok korlátozás eltűnik ezeken a lassú utakon, de ez nem különösebben fontos, mert lassúak, és ritkán járnak rajtuk. Például a null pointer – az sosem fordul elő, igaz? Több utat kell kialakítani különböző dolgokhoz, de nem szabad, hogy zavarják az alapút.
Vlagyimir: Mit gondolsz a többmagos feldolgozásról, amikor egyszerre ezer mag van? Ez hasznos dolog?
Cliff: A GPU sikeressége azt mutatja, hogy eléggé hasznos!
Vlagyimir: Ezek eléggé specializáltak. Mi a helyzet a általános célú processzorokkal?
Cliff: Nos, ez volt az Azul üzleti modellje. A válasz még abban az időben érkezett, amikor az emberek nagyon szerették a kiszámítható teljesítményt. Akkoriban elég nehéz volt párhuzamos kódot írni. A H2O kódolási modell jól skálázható, de nem egy általános célú modell. Csak egy kicsit általánosabb, mint a GPU használata esetén. Arról beszélünk, hogy a hasonló dolog fejlesztése mennyire bonyolult, vagy hogy a használata mennyire nehézkes? Például, egy érdekes leckét tanított nekem az Azul, ami nem nyilvánvaló: a kis gyorsítótárak rendben vannak.
Életem legnagyobb kihívása
Vlagyimir: Mi a helyzet a nem technikai kihívásokkal?
Cliff: A legnagyobb kihívás az volt, hogy ne legyek… kedves és jó az emberekkel. Ennek következtében állandóan rendkívül konfliktusos helyzetekbe kerültem. Olyan helyzetekbe, ahol tudtam, hogy minden rosszul megy, de nem tudtam, hogyan lépjek tovább a problémák megoldásában, és nem tudtam megbirkózni velük. Számos évtizedes problémám emiatt alakult ki. Az, hogy a Javában léteznek C1 és C2 fordítók, közvetlen következménye ennek. Az is, hogy a Javában tíz éven keresztül nem volt többlépcsős fordítás – szintén közvetlen következménye. Nyilvánvaló, hogy szükségünk volt egy ilyen rendszerre, de nem egyértelmű, hogy miért nem volt. Problémáim voltak egy mérnökkel… vagy mérnökök csoportjával. Rég volt, amikor elkezdtem dolgozni a Sun-nál, akkor… Nos, nemcsak akkor, mindig van véleményem mindenről. És igaznak tartottam, hogy egyszerűen csak elő lehet venni ezt az igazságot, és nyíltan elmondani. Főleg, mivel sokszor megdöbbentően igazam volt. Ha neked nem tetszik ez a megközelítés… különösen, ha nyilvánvalóan tévedsz és hülyeséget csinálsz… Szóval, csak kevesen tudtak tolerálni egy ilyen formát. Bár voltak, például én. Az egész életemet meritokratikus alapelvek szerint építettem fel. Ha megmutatsz nekem valami hibásat, azonnal megfordulok, és azt mondom: hülyeséget mondtál. Közben persze elnézést kérek, és ilyesmi, elismerem az érdemeket, ha azok léteznek, és egyéb helyes lépéseket teszek. Másfelől, megdöbbentően gyakran van igazam. És ez nem működik túl jól az emberekkel való kapcsolatokban. Nem is próbálok kedves lenni, hanem a kérdést egyértelműen felvetem. „Ez sosem fog működni, mert 1, 2 és 3.” Erre ők: „Ó!” Volt más következménye is, amiket talán jobb lenne kihagyni: például a feleségemmel való váláshoz és az azt követő tíz évi depresszióhoz vezetett.
A kihívás az emberekkel, azokkal a percepciókkal való küzdelem, hogy mit tehetsz vagy mit nem, hogy mi fontos és mi nem. Sok kihívás volt a kódolási stílusról. Még mindig sok kódot írok, és azokban az időkben lassítanom kellett, mert túl sok párhuzamos feladatom volt, és rosszul végeztem el őket, ahelyett, hogy egyre fókuszáltam volna. Visszatekintve, a Java JIT csapat felét írtam meg, a C2 csapatot. A következő leggyorsabb kódoló fele annyira lassan írt, a következő pedig még fele annyira lassan, és ez eksponenciálisan csökkent. A hetedik ember ebben a sorban nagyon, nagyon lassú volt – ez mindig így van! Sok kódot láttam. Néztem, ki mit ír, kivétel nélkül, bámultam a kódjukba, mindenkit átnéztem, és még mindig többet írtam, mint bármelyikük. Az emberekkel ez a megközelítés nem nagyon működik. Néhányan nem szeretik ezt. És amikor nem tudják kezelni, elkezdődnek a különféle panaszok. Például egyszer azt mondták nekem, hogy hagyjak fel a kódírással, mert túl sok kódot írok, és ez veszélyezteti a csapatot, és ez számomra viccnek hangzott: haver, ha a csapat többi tagja eltűnik, és én továbbra is kódot írok, akkor csak a csapat felét fogod elveszíteni. Másrészről, ha továbbra is kódot írok, és te elveszíted a csapat felét – ez nagyon rossz menedzsmentnek hangzik. Ezen sosem gondolkodtam el, sosem beszéltem róla, de valahol mégis a fejemben volt. A tudatom hátsó zugában az a gondolat keringett: „Ti vicceltek velem?”. Így tehát a legnagyobb probléma én voltam és az emberekkel való kapcsolatom. Most már sokkal jobban megértem magam, sokáig csapatvezető voltam programozók között, és most már egyenesen elmondom az embereknek: tudjátok, ilyen vagyok, és meg kell birkóznotok velem – semmi baj, ha itt állok? És amikor ezzel megbirkóztak, minden működni kezdett. Valójában nem vagyok sem rossz, sem jó, nincsenek rossz szándékaim vagy egoista vágyaim, ez egyszerűen csak az én lényem, és valahogy ezzel élni kell.
Andrej: Nemrégiben mindenki a befelé figyelésről beszélt, az introvertáltak számára, és egyáltalán a soft skill-ekről. Mit lehet erről mondani?
CliffIgen, ez egy megértés és tanulság volt, amelyet a feleségemmel való válásból vontam le. Amit a válásból leszűrtem – az az önmagam megértése. Így kezdtem el más embereket is megérteni. Megérteni, hogyan működik ez az interakció. Ez egymás után új felfedezésekhez vezetett. Ráébredtem, ki is vagyok és mit is képviselek. Amit csinálok: vagy az feladaton aggódom, vagy elkerülöm a konfliktust, vagy valami más – és ez a szintű önismeret valóban segít kordában tartani magam. Ezt követően minden sokkal egyszerűbbé válik. Egy dolog, amit nemcsak magamnál, hanem más programozóknál is felfedeztem – a képtelenség kifejezni a gondolatokat, amikor érzelmi stresszben vagyunk. Például, ott ülsz és kódolsz, áramlásban vagy, és egyszer csak rohan hozzád valaki és hisztérikusan kiabál, hogy valami elromlott, és hogy most drasztikus intézkedéseket fognak alkalmazni. És te nem tudsz egy szót sem mondani, mert érzelmi stresszben vagy. Azon tudás, amit szereztem, lehetővé teszi, hogy felkészüljek erre a pillanatra, átvészeljem azt, és áttérjek a visszavonulási tervre, amely után már lehet valamit tenni. Tehát igen, amikor elkezded tudni, hogyan működik ez az egész – az egy hatalmas életváltoztató esemény.
Én magam nem tudtam jól megfogalmazni a szavakat, de emlékeztem a cselekvések sorrendjére. A lényeg az, hogy ez a reakció annyira fizikai, mint amennyire verbális, és szükséged van egy térre. Egy olyan térre, mint a zen filozófiában. Ezt kellene megmagyarázni, majd azonnal félre kell állni – tisztán fizikailag félre állni. Amikor csendes vagyok szavakban, képes vagyok feldolgozni a helyzetet érzelmileg. Ahogy a adrenalin eljut az agyhoz, átvált a „ütni vagy futni” üzemmódba, már nem tudsz semmit mondani, nem – most már hülye vagy, ütésre teremtett mérnök, aki képtelen méltó választ adni vagy legalább megállítani a támadást, és a támadó szabadon tud támadni újra és újra. Először újra önmagaddá kell válni, visszanyerni az irányítást, kijönni a „ütni vagy futni” üzemmódból.
És ehhez szükség van egy verbális térre. Csak szabad térre. Ha egyáltalán beszélni akarunk, akkor ezt kimondhatjuk, majd elmehetünk és valójában találhatunk «teret»: sétálhatunk a parkban, bezárkózhatunk a zuhany alatt – nem számít. A legfőbb, hogy átmenetileg lekapcsolódjunk arról a helyzetről. Amint legalább néhány másodpercre lekapcsolódsz, visszatér a kontroll, és kezdesz tisztán gondolkodni. „Jól van, nem vagyok valami bolond, nem csinálok hülyeségeket, elég hasznos ember vagyok.” Amint sikerült meggyőznöd magadat, itt az idő, hogy lépj a következő szintre: megérteni, mi történt. Támadás érte, az a támadás jött, amire nem számítottál, ez egy tiszátalan, aljas csapda volt. Ez rossz. A következő lépés – megérteni, miért volt szüksége a támadónak erre. Tényleg, miért? Talán mert ő maga dühös? Miért dühös? Például mert ő maga elrontotta és nem tudja vállalni a felelősséget? Ekképpen kell óvatosan feldolgozni a helyzetet. De ehhez mozgástérre van szükség, verbális térre. Az első lépés – megszakítani a verbális kapcsolatot. Elkerülni a szóbeli vitát. Lemondani róla, elmenni, amilyen gyorsan csak lehet. Ha ez egy telefonbeszélgetés – csak csapd le a telefont – ez a készség, amit a volt feleségemmel való beszélgetésekből tanultam. Ha a beszélgetés nem vezet semmi jóra, csak mondd, hogy „viszlát” és tedd le a telefont. A vonal másik végén: „bla-bla-bla”, te pedig válaszolsz: „aha, szia!” és leteszed a telefont. Csak megszakítod a beszélgetést. Öt perccel később, amikor visszatér a józan gondolkodás képessége, egy kicsit megnyugszol, lehetőséged nyílik átgondolni, mi is történt és mi lesz a következő lépés. És elkezdesz megfogalmazni egy átgondolt választ, nem csupán reagálsz az érzelmeidre. Számomra a legfontosabb áttörés az önismeretben az volt, hogy érzelmi stressz esetén nem tudok beszélni. Kilépni ebből az állapotból, átgondolni és megtervezni, hogyan válaszoljak és kompenzáljam a problémákat – ezek a helyes lépések, amikor nem tudok beszélni. A legegyszerűbb mód – elmenekülni a helyzettől, ahol megjelenik az érzelmi stressz, és egyszerűen megszüntetni a részvételt ebben a stresszben. Ezt követően képessé válsz gondolkodni, amikor tudsz gondolkodni, megjelenik a beszélgetés lehetősége, és így tovább.
A bíróságon az ellenfél ügyvédje próbálja ezt veled csinálni - most már világos, miért. Mert képes arra, hogy annyira elnyomjon, hogy még a nevedet sem tudod kiejteni például. A legszorosabb értelemben nem tudsz beszélni. Ha ez veled történik, és tudod, hogy olyan helyre kerülsz, ahol szóbeli csaták folytatódnak, mint a bíróság, érdemes eljönni a saját ügyvéddel. Az ügyvéd megvédi téged, és megakadályozza a szóbeli támadást, és mindezt teljesen törvényes úton teszi, visszaadva az elveszett zenét. Például nekem pár alkalommal szükségem volt családdal beszélni, a bíró eléggé barátságosan viszonyult, de az ellenfél ügyvédje kiabált és kiabált velem, alig tudtam beleavatkozni. Ilyen esetekben számomra a legjobban a közvetítő használata működik. A közvetítő megszünteti ezt a folyamatos nyomást, amely rád nehezedik, így újra megtalálod a szükséges zenédet, és ezzel együtt visszatér a beszédképességed. Ez egy egész tudományág, amelyben sok mindent meg kell tanulni, sok mindent fel kell fedezni magadban, és mindez magas szintű stratégiai döntésekké alakul át, amelyek különböznek különböző emberek esetében. Vannak, akiknek nincsenek a fenti problémái, ez általában nem jellemző azokra, akik szakmai szinten foglalkoznak értékesítéssel. Mindazok az emberek, akik a szavakkal keresnek pénzt - ismert énekesek, költők, vallási vezetők és politikusok, mindig tudnak mit mondani. Nincs ilyen problémájuk, nekem viszont van.
Andrej: Ez váratlan volt. Remek, már elég sokat beszéltünk, és ideje befejezni ezt az interjút. Feltétlenül találkozunk a konferencián, és folytathatjuk a párbeszédet. Találkozzunk a Hydrán!
A kommunikáció folytatása Cliff-fel a Hydra 2019 konferencián lehetséges lesz, amely 2019. július 11-12-én kerül megrendezésre Szentpéterváron. Ő előadással érkezik, . A jegyek megvásárolhatók .
Forrás: habr.com
