Kliffs Kliks — CTO uzņēmumā Cratus (IoT sensori procesu uzlabošanai), dibinātājs un līdzdibinātājs vairākiem start-up uzņēmumiem (ieskaitot Rocket Realtime School, Neurensic un H2O.ai) ar vairākiem veiksmīgiem iziešanas darījumiem. Kliffs uzrakstīja savu pirmo kompilatoru 15 gadu vecumā (Pascal priekš TRS Z-80)! Viņš ir vislabāk pazīstams ar darbu pie C2 valodas Java (the Sea of Nodes IR). Šis kompilators parādīja pasaulei, ka JIT var ražot kvalitatīvu kodu, kas kļuva par vienu no faktoriem, kāpēc Java ir viena no galvenajām mūsdienu programmatūras platformām. Pēc tam Kliffs palīdzēja uzņēmumam Azul Systems uzbūvēt 864 kodolu galveno datoru ar programmatūru tīrā Java, kas atbalstīja GC pauzes 500 gigabaitu heapā 10 milisekundēs. Patiesībā Kliffs ir strādājis visos JVM aspektos.
Šis habraposts ir liels intervija ar Kliffu. Mēs runāsim par šādām tēmām:
- Pārslēgšanās uz zemo līmeņu optimizācijām
- Kā veikt lielu refaktorizāciju
- Izmaksu modelis
- Mācības par zemo līmeņu optimizācijām
- Praktiski piemēri veiktspējas uzlabošanai
- Kāpēc radīt savu programmēšanas valodu
- Veiktspējas inženiera karjera
- Tehniskie izaicinājumi
- Nedaudz par reģistru piešķiršanu un daudzprocesoru skaitļošanu
- Lielākais izaicinājums dzīvē
Interviju vada:
- Andrej Satārin no Amazon Web Services. Savā karjerā ir paspējis strādāt dažādos projektos: testējis izplatītu NewSQL datubāzi Yandex, mākoņdetectēšanas sistēmu Kaspersky laboratorijā, daudzu spēlētāju spēli Mail.ru un valūtas cenu aprēķina servisu Deutsche Bank. Interesējas par plašu aizmugurē esošo un izplatīto sistēmu testēšanu.
- Vladimirs Sitņikovs no Netcracker. Desmit gadus strādā pie veiktspējas un mērogotspējas NetCracker OS — programmatūra, ko lieto sakaru operatori tīklu pārvaldības un tīkla aprīkojuma automatizācijas procesu automatizēšanai. Interesējas par Java un Oracle Database veiktspēju. Vairāk nekā desmit veiktspējas uzlabojumu autors oficiālajam PostgreSQL JDBC draiverim.
Pārslēgšanās uz zemo līmeņu optimizācijām
Andrej: Jūs esat pazīstams cilvēks JIT kompilācijas, Java un veiktspējas jomā kopumā, vai ne?
Kliffs: Tieši tā!
Andrej: Sāksim ar vispārīgiem jautājumiem par veiktspējas uzlabošanu. Ko jūs domājat par izvēli starp augsta līmeņa un zemo līmeņu optimizācijām, piemēram, darbu pie CPU līmeņa?
Kliffs: Šeit viss ir vienkārši. Ātrākais kods ir tas, kuru nekad nesāk. Tāpēc vienmēr jāsāk no augsta līmeņa un jāstrādā pie algoritmiem. Labāka O-notācija uzvarēs sliktāku O-notāciju, izņemot gadījumus, ja iejaucas pietiekami lieli koeficienti. Zema līmeņa lietas tiek apskatītas pašās beigās. Parasti, ja esat pietiekami labi optimizējis visu pārējo steku un joprojām ir kaut kas interesants, tad tas arī ir zems līmenis. Bet kā sākt no augsta līmeņa? Kā zināt, ka augstā līmeņa darbā ir paveikts pietiekami daudz? Nu... nav nekādu gatavu recepšu. Jāizpēta problēma, jānoskaidro, ko grasies darīt (lai neveiktu nevajadzīgus nākamos soļus), un tad var sākt izmantot profileru, kas var pateikt kaut ko noderīgu. Kādā brīdī jūs paši saprotat, ka esat atbrīvoties no nevajadzīgām lietām un pienākusi laiks pievērst uzmanību zema līmeņa optimizācijai. Tas noteikti ir īpaša veida māksla. Daudzi cilvēki dara nevajadzīgas lietas, bet kustas tik ātri, ka par sniegumu viņiem nav laika domāt. Bet tā ir, līdz brīdim, kad jautājums kļūst kritisks. Parasti 99% laika nevienam nav interese, ko es daru, līdz kritiskās ceļā neparādās kāda svarīga lieta, kas kādam interesē. Un tieši tad visi sāk par tevi prasīt, „kāpēc tas no paša sākuma nedarbojās ideāli”. Kopumā vienmēr ir kas jāuzlabo sniegumā. Bet 99% laika tev nav pavedienu! Tu vienkārši mēģini panākt, lai kaut kas strādātu, un šī procesa laikā saproti, kas ir svarīgi. Nekad nav iespējams iepriekš zināt, ka tieši šis fragments ir jāizveido ideāls, tāpēc būtībā jācenšas būt ideālam visā. Bet tas nav iespējams un tā tu nedari. Vienmēr ir daudz lietu, kas jāremontē – un tas ir pilnīgi normāli.
Kā veikt lielu refaktorizāciju
Andrej: Kā jūs strādājat pie snieguma? Tas ir universāls jautājums. Piemēram, vai jums ir nācies strādāt pie problēmām, kas rodas no daudzu jau esošo funkcionalitātes savietojuma?
Kliffs: Es cenšos to izvairīties. Ja es zinu, ka veiktspēja kļūs par problēmu, es par to domāju vēl pirms kodēšanas uzsākšanas, it īpaši par datu struktūrām. Bet bieži vien tu to atklāj tikai daudz vēlāk. Un tad ir jāiet uz ekstrēmiem pasākumiem un jādara tas, ko es saucu par 'pārprogrammēšanu un pārvaldīšanu': jāņem pietiekami liels gabals. Kāda daļa koda tāpat jāpārprogrammē sakarā ar veiktspējas problēmām vai kādu citu iemeslu. Kāds jebkurš koda pārprogrammēšanas iemesls var būt – gandrīz vienmēr ir labāk pārprogrammēt lielāku gabalu nekā mazāku. Tajā brīdī visi sāk drebēt no bailēm: 'Ak Dievs, nevar taču izmainīt tik daudz koda!'. Bet, patiesībā, šāds pieejas veids gandrīz vienmēr darbojas daudz labāk. Jāķeras pie lielas problēmas uzreiz, jāizveido liels aplis ap to un jāsaka: viss, kas ir iekšā aplī, es to pārprogrammēšu. Robeža taču ir daudz mazāka par to saturu iekšā, kuru nepieciešams nomainīt. Un, ja šāds robežas noteikums ļaus perfekti paveikt iekšējās darbu – tev ir brīvas rokas, dari ko vēlies. Tiklīdz tu saproti problēmu, pārprogrammēšanas process norit daudz vieglāk, tāpēc ņem lielu gabalu!
Tajā pašā laikā, kad tu pārprogrammē lielu gabalu un apzini, ka veiktspēja kļūs par problēmu, vari uzreiz sākt par to uztraukties. Parasti tas pārvēršas vienkāršās lietās, piemēram, 'nepārvietojiet datus, pārvaldiet datus pēc iespējas vienkāršāk, padariet tos mazākus'. Lielās pārprogrammēšanās gadījumos ir standartveida veidi, kā uzlabot veiktspēju. Un tie gandrīz vienmēr ir saistīti ar datiem.
Izmaksu modelis
Andrej: Vienā no podkāstiem jūs runājāt par cenu modeļiem veiktspējas kontekstā. Vai varētu paskaidrot, ko jūs ar to domājāt?
Kliffs: Protams. Es piedzimstīju laikmetā, kad procesoru veiktspēja bija ārkārtīgi svarīga. Un šī ēra atkal atgriežas – liktenis neizbēgami ironizē. Es sāku dzīvot astoņbitu mašīnu laikmetā, mans pirmais dators darbojās ar 256 baitiem. Tiešām, ar baitiem. Viss bija ļoti neliels. Bija jāskaita instrukcijas, un tiklīdz sākām virzīties augstāk programmēšanas valodu stekā, valodas uzņēma aizvien vairāk un vairāk. Bija Assembler, tad Basic, tad C, un C uzņēma darbu ar daudzām detaļām, piemēram, reģistru sadali un instrukciju plānošanu. Bet tur viss bija diezgan skaidrs, un, ja es izveidoju rādītāju uz mainīgā instanci, es saņemšu load, un šai instrukcijai izmaksas ir zināmas. Dzelži sniedz zināmu skaitu mašīnu ciklu, tāpēc dažādu uzdevumu izpildes ātrumu var aprēķināt, vienkārši summējot visas instrukcijas, kuras tu plāno palaist. Katru compare/test/branch/call/load/store varējām saskaitīt un teikt: lūk, te ir izpildes laiks. Nodarbējoties ar veiktspējas uzlabošanu, tu noteikti pievērsīsi uzmanību tam, kādi skaitļi atbilst mazām karstām ciklu reakcijām.
Bet tiklīdz tu pāriet uz Java, Python un tamlīdzīgām lietām, tu ļoti ātri attālinies no zemo līmeņu dzelžiem. Kāda ir getters izsaukuma izmaksas Java? Ja JIT HotSpotā viss ir pareizi , tas būs load, bet, ja viņš to nav izdarījis – tas būs funkcijas izsaukums. Tā kā izsaukums atrodas karstajā ciklā, tas atcels visas pārējās optimizācijas šajā ciklā. Tāpēc reālā cena būs daudz lielāka. Un tu uzreiz zaudē spēju skatīties uz koda fragmentu un saprast, kādēļ mums to vērts veikt attiecībā uz procesora takts frekvenci, izmantoto atmiņu un kešatmiņu. Viss tas kļūst interesants tikai tad, ja tiešām dziļi iegrims veiktspējā.
Tagad mēs esam situācijā, kad procesoru ātrumi jau vairāk nekā desmit gadu laikā gandrīz nav auguši. Vecie laiki atgriežas! Jūs vairs nevarat paļauties uz labu vienas pavediena veiktspēju. Bet, ja notiks paralēlās aprēķina uzsākšana – tas ir ārkārtīgi sarežģīti, visi skatās uz jums kā uz Džeimsu Bondu. Desmitkārtīgas paātrināšanas parasti notiek tajās vietās, kur kāds ir kaut ko izlaidis. Paralēlismam ir nepieciešams daudz darba. Lai sasniegtu šo desmitkārtīgo paātrinājumu, ir jāizprot izmaksu modelis. Kas un cik maksā. Un, lai to izprastu, jāsaprot, kā valoda tiek uzlikta uz zemāk esošā dzelzs.
Mārtins Tompsons savam emuāram atrada lielisku vārdu ! Ir jāizprot, ko plāno darīt dzelzs, kā tieši tas to darīs un kāpēc tas vispār dara to, ko dara. Izmantojot to, ir diezgan vienkārši sākt saskaitīt instrukcijas un noskaidrot, kurp iet izpildes laiks. Ja tev nav attiecīgās sagatavotības, tu vienkārši meklē melnu kaķi tumšā istabā. Es pastāvīgi redzu cilvēkus, kas optimizē veiktspēju, kuriem nav ne mazākās nojausmas, ko lāga viņi vispār darā. Viņi ļoti cieš un nekur īpaši nevirzās uz priekšu. Un, kad es paņemu to pašu koda gabalu, ielikdams tur pāris sīku haku, un iegūstu pieckārtīgu vai desmitkārtīgu paātrinājumu, viņi saka: nu, tas nav godīgi, mēs arī tā zinājām, ka tu esi labāks. Pārsteidzoši. Par ko es runāju… izmaksu modelis – tas ir par to, kādu kodu tu raksti un cik ātri tas vidēji darbojas vispārējā attēlā.
Andrej: Un kā tādu apjomu noturēt prātā? To sasniedz ar lielu pieredzi, vai ne? Kur iegūt tādu pieredzi?
Kliffs: Nu, savu pieredzi es ieguvu ne vistuvākajā ceļā. Es programmēju Assemblerī vēl tajos laikos, kad varēja izprast katru atsevišķu instrukciju. Tas izklausās muļķīgi, bet no tā laika man prātā, atmiņā, uz visiem laikiem ir palicis Z80 instrukciju komplekts. Es neatceros cilvēku vārdus jau minūti pēc sarunas, bet atceros kodu, kas rakstīts pirms 40 gadiem. Interesanti, tas izskatās kā „».
Mācības par zemo līmeņu optimizācijām
Andrej: Vai ir kāds vienkāršāks veids, kā iesaistīties?
Kliffs: Jā un nē. Dzelzs, ko mēs visi izmantojam, šajā laikā būtiski nemainījās. Visi izmanto x86, izņemot viedtālruņus uz Arm. Ja tu nenodarbojies ar kādu hardcore iebūvēto sistēmu, tev viss ir tieši tāds pats. Labi, tālāk. Instrukcijas arī gadsimtus nav mainījušās. Jāiet un jāraksta kaut kas par Assembler. Nedaudz, bet pietiekami, lai sāktu saprast. Jūs smaidāt, bet es pilnīgi nopietni to saku. Jāizprot valodas un dzelža attiecības. Pēc tam jāiet, jāuzraksta nedaudz un jāizveido neliels rotaļu kompilers nelielai rotaļu valodai. 'Rotaļu' nozīmē, ka jāizveido to saprātīgā laikā. Tam var būt supervienkārša struktūra, bet tam jāģenerē instrukcijas. Instrukciju ģenerēšanas akts ļaus saprast izmaksu modeli tiltam starp augstā līmeņa kodu, ko visi raksta, un mašīnkodu, kas izpildās uz dzelža. Šī attiecība paliks prātos brīdī, kad rakstīsiet kompileri. Pat visvienkāršākajam kompilerim. Pēc tam var sākt skatīties uz Java un saprast, ka tās semantiskā kloķēšana ir daudz dziļāka, un tās virsū uzcelt tiltus ir daudz grūtāk. Java ir daudz grūtāk saprast, vai mūsu tilts ir labs vai slikts, kas to var sabojāt un kas nē. Bet tev ir nepieciešama kāda sākumpunkts, kad tu skaties uz kodu un saproti: 'Aha, šim getteram katru reizi jāsaturās iekšā'. Un tālāk izrādās, ka dažreiz tā arī notiek, izņemot situācijas, kad metode kļūst pārāk liela, un JIT sāks saturēt visu kopā. Šādu vietu veiktspēju var paredzēt nekavējoties. Parasti getterī darbojas labi, bet pēc tam tu skaties uz lieliem karstiem cikliem un saproti, ka tur ir kādi funkcionālie izsaukumi, kas nav skaidrs, ko dara. Šeit ir problēma ar plašu getteru lietošanu, iemesls, kāpēc tie neiekļaujas - nav skaidrs, vai tas ir getters. Ja tev ir supermaza kodu bāze, to var vienkārši iegaumēt un pēc tam teikt: šis ir getters, bet šis ir setters. Lielā kodu bāzē katrai funkcionālijai ir sava stāsts, kas nevienam, būtībā, nav zināms. Profileris saka, ka mēs zaudējuši 24% laika kaut kādā ciklā, un lai saprastu, ko dara šis cikls, jāskatās uz katru funkciju iekšā. Nevar to saprast, nepētījot funkciju, un tas nopietni palēnina izpratnes procesu. Tāpēc es neizmantoju getterus un setterus, es esmu sasniedzis jaunu līmeni!
Kā iegūt izmaksu modeli? Protams, var palasīt kaut ko... Bet es domāju, labākais veids ir rīkoties. Izveidot mazu kompilatoru, un tas būs labākais veids, kā apzināt izmaksu modeli un iekļaut to savā prātā. Mazs kompilators, kas derētu mikroviļņu krāsns programmēšanai – tas ir uzdevums iesācējiem. Nu, es domāju, ja tev jau ir programmēšanas prasmes, tad tās pietiks. Visas tās lietas, piemēram, izparsināt rindu, kas būs kāds algebriskais izteikums, izvilkt no tās matemātisko operāciju instrukcijas pareizajā secībā, iegūt pareizās vērtības no reģistriem – to visu var izdarīt viegli. Un kamēr tu to darīsi, tas ieies prātā. Domāju, visi zina, ko dara kompilators. Un tas dos izpratni par izmaksu modeli.
Praktiski piemēri veiktspējas uzlabošanai
Andrej: Uz ko vēl jāpievērš uzmanība, strādājot pie veiktspējas?
Kliffs: Datu struktūras. Starp citu, jā, jau sen neesmu vadījis šos pasākumus… . Tas bija interesanti, bet prasīja tik daudz spēku, bet man taču ir arī dzīve! Labi. Tātad, vienā no lielajām un interesantajām nodarbībām, "Kur pazūd jūsu veiktspēja", es devu studentiem piemēru: divarpus gigabaiti finteh datu tika nolasīti no CSV faila, un tālāk bija jāskaita pārdoto produktu skaits. Parasti tirgus dati. UDP paketes, kas pārvērstas teksta formātā, sākot ar 70. gadiem. Čikāgas Preču birža – visādas lietas kā eļļa, kukurūza, sojas pupas un tamlīdzīgi. Bija jāskaita šie produkti, darījumu skaits, vidējais naudas un preču apgrozījums utt. Tā ir diezgan vienkārša tirdzniecības matemātika: atrast produkta kodu (tas ir 1-2 simboli hash tabulā), iegūt summu, pievienot to vienam no darījumu kopumiem, pievienot apjomu, pievienot izmaksas un pāris citas lietas. Ļoti vienkārša matemātika. Rotaļīgā realizācija bija ļoti vienkārša: viss atrodas failā, es lasu failu un pārvietojos pa to, sadalot atsevišķas ierakstu Java rindās, meklēju tajās nepieciešamās lietas un sumēju saskaņā ar iepriekš aprakstīto matemātiku. Un tas darbojas ar kādu nelielu ātrumu.
Ar šādu pieeju viss ir acīmredzams, kas notiek, un paralēlie aprēķini šeit nepalīdz, vai ne? Izrādās, piecu reizes lielāku veiktspēju var sasniegt, vienkārši izvēloties pareizās datu struktūras. Un tas pārsteidz pat pieredzējušus programmētājus! Manā konkrētajā gadījumā uzmanība bija pievērsta tam, ka nevajadzētu veikt atmiņas rezervēšanu karstajā ciklā. Nu, tas nav viss, bet kopumā – nevajadzētu veikt rezervācijas „reizi X”, kad X ir pietiekami liels. Kad X ir divi ar pusi gigabaiti, nevajadzētu rezervēt neko „reizi par burtu”, vai „reizi par rindiņu”, vai „reizi par lauku”, neko tamlīdzīgu. Tieši tajā tiek tērēts laiks. Kā tas vispār darbojas? Iedomājieties, ka veicu izsaukumu String.split() vai BufferedReader.readLine(). Readline izveido virkni no byte kopas, kas nāk pa tīklu, vienu reizi katrai rindiņai, katrai no simtiem miljoniem rindiņu. Es paņemu šo rindu, analizēju to un izmetu. Kāpēc izmetu – nu, es to jau esmu apstrādājis, viss. Tātad katram byte, kas nolasīts no šiem 2.7G, tiks ierakstīti divi simboli virknes, t.i., jau 5.4G, un tie man turpmāk nav nepieciešami, tāpēc tie tiek izmestti. Ja paskatāmies uz atmiņas caurlaidspēju, mēs slodze 2.7G, kas iet cauri atmiņai un atmiņas bus, procesoram, un tālāk divreiz vairāk tiek nosūtīti uz virkni, kas atrodas atmiņā, un viss tas tiek pārstrādāts, veidojot katru jauno virkni. Bet man taču ir jāizlasa tā, dzelzs to nolasa, pat ja pēc tam viss tiks pārstrādāts. Un man tas jāieraksta, jo es izveidoju virkni, un keši ir pārpildīti – kešs nevar uzņemt 2.7G. Tātad katram nolasītajam byte es nolasu vēl divus papildu byte un ierakstu divus papildu byte, un kopumā tiem ir attiecība 4:1 – šādā attiecībā mēs nevajadzīgi tērējam atmiņas caurlaidspēju. Un tālāk izrādās, ka, ja es veicu String.split() – tad es to noteikti nedaru pēdējo reizi, tur iekšā var būt vēl 6-7 lauki. Tāpēc klasiskā CSV nolasīšanas un pēc tam virknēšanas kods noved pie atmiņas caurlaidspējas zuduma aptuveni 14:1 attiecībā pret to, ko jums faktiski vēlētos. Ja izmetam šīs rezervācijas, tad varam sasniegt piecu reizes ātrumu.
Un tas nav ļoti sarežģīti. Ja jūs paskatīsieties uz kodu no pareizā leņķa, viss kļūst diezgan vienkārši, tiklīdz saprotat problēmas būtību. Nav vērts vispār pārtraukt atmiņas sadali: problēma ir tajā, ka jūs kaut ko sadalāt un tas tūlīt mirst, un pa ceļam iznīcina svarīgu resursu, kas šajā gadījumā ir atmiņas caurlaidība. Un viss tas beidzas ar veiktspējas kritumu. x86 parasti ir nepieciešams aktīvi izmantot procesora taktis, bet šeit jūs iztērējāt visu atmiņu daudz agrāk. Risinājums ir samazināt sadalījumu skaitu.
Vēl viena problēmas daļa ir tā, ka, ja jūs palaidīsit profileru brīdī, kad beidzās atmiņas josla, tieši tajā brīdī, kad tas notiek, jūs parasti gaidāt kešatmiņas atgriešanos, jo tā ir pilna ar miskasti, ko jūs tikko radījāt visiem šiem rindām. Tāpēc katra load vai store operācija kļūst lēna, jo tās izraisa kešatmiņas trūkumus - visa kešatmiņa kļūst lēna, gaidot, kad no tās izvestīs miskasti. Tāpēc profilers parādīs tikai siltus nejaušus trokšņus, kas izsisti pa visu ciklu - nebūs nekādas atsevišķas karstas instrukcijas vai vietas kodā. Tikai troksnis. Un, ja jūs paskatīsieties uz GC cikliem, tie visi būs Young Generation un supers ātri - mikroskēnas vai milisekundes maksimums. Jo visa šī atmiņa mirst nekavējoties. Jūs sadalāt miljardiem gigabaitu, un tas tos nogriež, un nogriež un vēlreiz nogriež. Viss tas notiek ļoti ātri. Rezultātā ir lēti GC cikli, silts troksnis visā ciklā, bet mēs vēlamies iegūt 5-reizes paātrinājumu. Šajā brīdī kaut kas jāsakrājas galvā un jāskaidrojās: "kāpēc tā?!". Atmiņas joslas pārsniegšana neparādās klasiskajā debuggērī, ir jāpalaidīs aparatūras veiktspējas skaitītāju debuggers un jāpaskatās uz to tieši. Un, ja ne tieši, to var aizdomāties no šiem trim simptomiem. Trešais simptoms - tas ir, kad jūs skatāties, ko sadalāt, prasāt profileram, un viņš atbild: "Jūs izdarījāt miljardu rindu, bet GC strādāja par brīvu". Tiklīdz tas notiek, jūs saprotat, ka esat radījis pārāk daudz objektu un iztērējis visu atmiņas joslu. Ir veids, kā to izprast, bet tas nav acīmredzams.
Datu struktūras problēma: tukša struktūra, kas slēpjas aiz visa notiekošā, ir pārāk liela, tā aizņem 2.7G diskā, tādēļ šīs lietas kopēšana ir ļoti nevēlama – vēlētos to ielādēt tieši no tīkla bajtu bufera reģistros, lai nevajadzētu piecas reizes rakstīt un lasīt atpakaļ un uz priekšu. Diemžēl Java pēc noklusējuma nesniedz tādu bibliotēku savā JDK komplektā. Bet tas taču ir trivāli, vai ne? Patiesībā tas ir 5-10 koda rindiņas, kas tiks izmantotas, lai īstenotu savu buferizēto rindiņu ielādētāju, kas atkārto virkņu klases uzvedību, būtībā kalpojot kā apvalks ap zemāk esošo bajtu buferi. Rezultātā izrādās, ka tu strādā gandrīz it kā ar virknēm, bet patiesībā tur pārvietojas rādītāji uz buferi, un nevis neapstrādāti bajti tiek kopēti, un tādējādi tiek atkārtoti izmantoti tie paši buferi atkal un atkal, un operētājsistēma labprāt uzņemas lietas, kam tā ir paredzēta, piemēram, slēptu dubulto buferizēšanu šiem bajtu buferiem, un tu vairs neiznīcini bezgalīgu nevajadzīgu datu plūsmu. Starp citu, jūs taču saprotat, ka strādājot ar GC garantēts, ka katra atmiņas piešķiršana nebūs redzama procesoram pēc pēdējā GC cikla? Tādēļ, tas viss nekā nevar būt kešā, un tālāk notiek 100%-īgi garantēts nepareizais trieciens. Strādājot ar rādītāju, x86 piekļūšana reģistram no atmiņas aizņem 1-2 takts, un tiklīdz tas notiek, tu maksā, maksā, maksā, jo visa atmiņa ir – un tieši tas ir atmiņas piešķiršanas izmaksas. Patiesās izmaksas.
Citiem vārdiem sakot, datu struktūras ir tās, ko visgrūtāk mainīt. Un tiklīdz jūs saprotat, ka esat izvēlējies nepareizu datu struktūru, kas vēlāk sabojās veiktspēju, parasti jāpavada būtiska darba apjoms, bet, ja to neizdarīsiet, tālāk būs vēl sliktāk. Pirmkārt, ir jādomā par datu struktūrām, tas ir svarīgi. Galvenās izmaksas šeit ir saistītas ar lielām datu struktūrām, kuras sāk izmantot stilā "es kopēju datu struktūru X datu struktūrā Y, jo Y man labāk patīk pēc formas". Bet kopēšanas operācija (kura šķiet lēta) patiesībā patērē atmiņas joslu, un tieši šeit slēpjas viss zaudētais izpildes laiks. Ja man ir milzīga JSON rinda un es vēlos to pārvērst par strukturētu DOM koku no POJO vai kaut kā tamlīdzīga, šīs rindas parsēšanas operācija un POJO veidošana, un pēc tam jauna piekļuve POJO vēlākies radīs nevajadzīgas izmaksas – tas nav lēti. Ieskaitot gadījumus, ja jūs darbosies ar POJO daudz biežāk nekā ar rindu. Pirmajā brīdī varētu izmēģināt izsist rindu un izvilkt tikai nepieciešamo, nepārvēršot to par nekādiem POJO. Ja viss tas notiek ceļā, kurā nepieciešama maksimāla veiktspēja, nekādus POJO – ir vajaģs tieši ņemties ar rindu.
Kāpēc radīt savu programmēšanas valodu
Andrej: Jūs teicāt, ka, lai apzinātos izmaksu modeli, jāpieraksta sava mazā valoda...
Kliffs: Nevis valoda, bet kompilators. Valoda un kompilators ir dažādas lietas. Galvenā atšķirība – jūsu prātā.
Andrej: Starp citu, cik zinu, jūs eksperimentējat ar savu valodu izveidi. Kāpēc?
Kliffs: Tāpēc, ka varu! Es esmu pussezonā, tāpēc tas ir mans hobijs. Visu mūžu es esmu īstenojis svešas valodas. Un esmu arī daudz strādājis pie koda stila. Un vēl tāpēc, ka redzu problēmas citās valodās. Redzu, ka ir labāki veidi, kā veikt ierastas lietas. Un es tos izmantotu. Man ir apnikusi redzēt problēmas sevī, Java, Python, jebkurā citā valodā. Es šobrīd rakstu uz React Native, JavaScript un Elm kā hobiju, kas nav par pensiju, bet par aktīvu darbu. Un arī uz Python rakstu un, visticamāk, turpināšu strādāt pie mašīnmācīšanās Java aizmugurēm. Ir daudz populāru valodu, un katrai ir interesantas iezīmes. Katrs ir labs kaut kādā veidā, un var izmēģināt apvienot visas šīs funkcijas. Tāpēc es nodarbojos ar interesantu lietu izpēti, valodas uzvedību, cenšoties izdomāt saprātīgu semantiku. Un pagaidām man tas padodas! Šobrīd es cīnos ar atmiņas semantiku, jo gribu, lai tā būtu kā C un Java, un iegūt spēcīgu atmiņas modeli un atmiņas semantiku ielādēm un veikalā. Tajā pašā laikā iegūt automātisku tipu izslēgšanu kā Haskell. Lūk, es cenšos sajaukt Haskell-līdzīgu tipu izslēgšanu ar atmiņu, kas darbojas kā C un Java. Ar to es esmu nodarbojies pēdējās 2-3 mēnešus, piemēram.
Andrej: Ja jūs veidojat valodu, kas ņem labākās aspekts no citām valodām, vai esat domājis par to, ka kāds varētu izdarīt pretējo: ņemt jūsu idejas un izmantot tās pie sevis?
Kliffs: Tieši tā rodas jaunas valodas! Kāpēc Java ir līdzīga C? Tāpēc, ka C bija laba sintakse, ko visi saprata, un Java iedvesmojās no šīs sintakses, pievienojot tam veida drošību, masīvu robežu pārbaudes, GC, un vēl viņi uzlaboja dažas lietas no C. Viņi pievienoja savas. Bet viņi tika spēcīgi iedvesmoti, vai ne? Visi stāv uz milžu pleciem, kas bija pirms tevis – tieši tā notiek progress.
Andrej: Kā es saprotu, jūsu valoda būs droša attiecībā uz atmiņas izmantošanu. Vai jūs domājāt īstenot kaut ko līdzīgu borrow checker no Rust? Vai jūs to skatījāties, kā jums patika?
Kliffs: Nu, es rakstu C jau veselu mūžību, ar visiem šiem malloc un free, un manuāli pārvaldu dzīves ciklu. Zināt, 90-95% manuāli pārvaldītā cikla ir ar vienādu struktūru. Un tas ir ļoti, ļoti sāpīgi to darīt manuāli. Es vēlētos, lai kompilators vienkārši pateiktu, kas tur notiek un ko tu esi sasniedzis ar savām darbībām. Dažām lietām borrow checker to dara automātiski. Vēl tam ir jāspēj automātiski izvest informāciju, visam jābūt saprotamam un pat nevajag mani slodzīt ar to sapratni. Tam būtu jāveic vismaz lokāla aizbēgšanas analīze, un tikai tad, ja tas neizdodas, būtu jāpievieno tipa anotācijas, kas apraksta dzīves ciklu – un šāda shēma ir daudz sarežģītāka nekā borrow checker vai jebkurš cits esošais atmiņas pārbaudītājs. Izvēle starp „viss ir labi” un „es neko nesaprotu” – nē, tam jābūt kaut kam labākam.
Tātad, kā cilvēks, kurš ir uzrakstījis daudz C koda, uzskatu, ka automātiskās dzīves cikla pārvaldības atbalsts ir vissvarīgākā lieta. Un mani arī kaitina, cik daudz atmiņas izmanto Java, un galvenā sūdzība ir par GC. Pie atmiņas piešķiršanas Java gadījumā tev atmiņa, kas bija lokāla pēdējā GC ciklā, netiks atgriezta. Valodās ar precīzāku atmiņas pārvaldību tas nav tā. Ja izsaucat malloc, tu tūlītējo saņem atmiņu, kas parasti tikko tika izmantota. Parasti tu veic kādas pagaidu darbības ar atmiņu un tūlīt to atgriez atpakaļ. Un tā uzreiz atgriežas malloc baseinā, un nākamajā malloc ciklā to atkal izvelk atpakaļ. Tāpēc reālais atmiņas patēriņš samazinās līdz dzīvā objektu kopumam konkrētā brīdī plus kļūmēm. Un, ja tev nav tekšķu nepiedienīgos veidos, lielākā daļa atmiņas nosēdīsies kešos un procesorā, un tas darbosies ātri. Bet tas prasa daudz roku darba ar atmiņas pārvaldību, izmantojot malloc un free, kas jāsauc pareizajā secībā, pareizā vietā. Rust spēj ar to tikt galā pareizi, un daudzos gadījumos pat nodrošina augstāku veiktspēju, jo atmiņas patēriņš tiek samazināts tikai līdz pašreizējām aprēķiniem – atšķirībā no gaidīšanas nākamajam GC ciklam, kas atbrīvos atmiņu. Beigās mēs ieguvām ļoti interesantu veidu, kā uzlabot veiktspēju. Un diezgan jaudīgu – tā teikt, es nodarbojos ar šādām lietām datu apstrādē finanšu tehnoloģiju jomā, un tas ļāva iegūt paātrinājumu apmēram piecas reizes. Tas ir diezgan nozīmīgs paātrinājums, it īpaši pasaulē, kur procesori neveicas ātrāk, un mēs joprojām gaidām uzlabojumus.
Veiktspējas inženiera karjera
Andrej: Vēlētos arī pajautāt par karjeru kopumā. Jūs kļuvāt slavens, strādājot pie JIT HotSpot, pēc tam pārcēlāties uz Azul – un tā ir arī JVM kompānija. Bet jūs jau vairāk nodarbojāties ar aparatūru, nevis programmatūru. Un pēc tam pēkšņi pārgājāt uz Big Data un Machine Learning, pēc tam uz krāpšanas atklāšanu. Kā tas notika? Tas ir ļoti atšķirīgi izstrādes jomas.
Kliffs: Esmu jau diezgan ilgi nodarbojies ar programmēšanu un esmu pārzīmējis uz ļoti dažādām aktivitātēm. Un, kad cilvēki saka: «O, tu taču esi tas, kurš izstrādāja JIT Java!», tas vienmēr ir smieklīgi. Bet pirms tam es strādāju pie PostScript klona – valodas, kuru Apple kādreiz izmantoja saviem lāzerprinteriem. Un vēl pirms tam es veicu Forth valodas ieviešanu. Domāju, kopējā tēma man ir rīku izstrāde. Visa dzīve esmu veidojis rīkus, ar kuriem citi cilvēki raksta savas foršās programmas. Bet es esmu strādājis arī pie operētājsistēmām, vadītājiem, kodola līmeņa atkļūdošanas rīkiem, operētājsistēmu izstrādes valodām, kas sākās triviāli, bet laika gaitā kļuva arvien sarežģītākas. Bet galvenā tēma tomēr ir rīku izstrāde. Liela daļa manas dzīves ir pagājusi starp Azul un Sun, un tā bija par Java. Bet, kad es sāku nodarboties ar Big Data un Mašīnmācīšanos, es atkal uzlikšu savu svinīgo cepuri un teicu: «Ak, tagad mums ir netriviāla problēma, un šeit notiek vesela sērija interesantu lietu un cilvēku, kuri kaut ko dara». Tas ir lielisks ceļš attīstībai, pa kuru ir vērts iet.
Jā, es ļoti mīlu izkliedētās aprēķināšanas. Mana pirmā darba pieredze bija studentu gados, programmējot C valodā kādā reklāmas projektā. Tas bija izkliedēts aprēķins uz Zilog Z80 mikroshēmām, kuras vāc datus analogam optiskajam teksta atpazīšanai, ko nodrošināja īsts analoģais analizators. Tas bija aizraujošs un pavisam neparasts temats. Tomēr bija problēmas, kāda daļa netika pareizi atpazīta, tāpēc bija nepieciešams izvilkt attēlu un parādīt to cilvēkam, kurš jau bija lasījis acīm un paziņot, kas tur ir rakstīts, un tāpēc bija darbi ar datiem, un šiem darbiem bija sava valoda. Bija bāka, kas visu to apstrādāja – paralēli strādājošās Z80 mikroshēmas ar palaistiem vt100 termināliem – pa vienam uz cilvēku, un bija paralēlo programmēšanas modelis uz Z80. Noteikta kopīga atmiņas daļa, ko dalīja visas Z80 šajā „zvaigznes” konfigurācijā; dalījās arī bāka, un puse RAM tika dalīta iekšējā tīklā, un otra puse bija privāta vai devās uz kaut ko citu. Cieši sarežģīta paralēla izkliedēta sistēma ar dalītu... daļēji dalītu atmiņu. Kad tas bija... Vairs neatceros, kaut kur astoņdesmito gadu vidū. Pietiekami sen.
Jā, uzskatīsim, ka 30 gadi ir pietiekami ilgi. Uzdevumi, kas saistīti ar izkliedētām aprēķināšanām, ir pastāvējuši pietiekami ilgi, cilvēki jau sen cīnās ar -klasteriem. Šādi klasteri izskatās kā… Piemēram: ir Ethernet, un tavs ātrais x86 ir pievienots šim Ethernet, un tagad tu vēlies iegūt viltoto kopīgo atmiņu, jo neviens todien nevarēja nodarboties ar sadalīto aprēķinu kodēšanu, tas bija pārāk sarežģīti, un tādēļ bija viltota kopīgā atmiņa ar atmiņas lapu aizsardzību uz x86, un ja tu rakstīji šajā lapā, tad mēs paziņojām citiem procesoriem, ka, ja viņi piekļūst tai pašai kopīgajai atmiņai, tā jāielādē no tevis, un tādējādi radās kaut kas līdzīgs kešu saskaņošanas protokolam un programmatūrai tam. Interesanta koncepcija. Patiesā problēma, protams, bija citur. Viss tas darbojās, bet tu ātri saskārās ar veiktspējas problēmām, jo neviens nesaprata veiktspējas modeli pietiekami labi – kādi ir piekļuves modeļi uz atmiņu, kā panākt, lai mezgli nemitīgi nesazinātos savā starpā, un tā tālāk.
H2O man ir radījusi šādu ideju: paši izstrādātāji ir atbildīgi par to, lai noteiktu, kur ir slēpšanās paralēlisms un kur tā nav. Es izdomāju tādu kodēšanas modeli, ka ir viegli un vienkārši rakstīt augstas veiktspējas kodu. Taču uzrakstīt lēni strādājošu kodu ir grūti, tas izskatīsies slikti. Ir jāpieliek lielas pūles, lai uzrakstītu lēnu kodu; būs jāpielieto nestandarta metodes. Lēns kods ir redzams jau pirmajā mirklī. Tādēļ parasti tiek rakstīts kods, kas strādā ātri, bet jums jātiek galā ar to, kas jādara kopīgas atmiņas gadījumā. Tas viss ir saistīts ar lieliem masīviem, un uzvedība tajos ir līdzīga nevolatīviem lieliem masīviem paralēlajā Java. Iedomājieties, ka divi pavedieni raksta paralēlajā masīvā, viens no tiem uzvar, bet otrs, attiecīgi, zaudē, un jūs nezināt, kurš no viņiem ir kurš. Ja tie nav volatīvi, secība var būt jebkāda – un tas tiešām darbojas labi. Cilvēkiem ir svarīgi operāciju secība, viņi pareizi izvieto volatīvos un pareizajās vietās sagaida veiktspējas problēmas, kas saistītas ar atmiņu. Savādāk viņi vienkārši rakstītu kodu kā ciklus no 1 līdz N, kur N ir daži triljoni, cerot, ka visi sarežģītie gadījumi automātiski kļūs paralēli – un tur tas nedarbojas. Bet H2O nav ne Java, ne Scala, to var uzskatīt par „Java mīnuss mīnuss”, ja gribas. Tas ir ļoti saprotams programmēšanas stils, un tas ir līdzīgs vienkārša koda rakstīšanai C vai Java ar cikliem un masīviem. Bet tajā pašā laikā atmiņu var apstrādāt terabaitiem. Es vēl joprojām izmantoju H2O. Laiku pa laikam to izmantoju dažādos projektos – un tas joprojām ir ātrākais risinājums, kas desmitiem reižu apsteidz konkurentus. Ja jūs veicat Big Data ar kolonnveida datiem, ir ļoti grūti pārspēt H2O.
Tehniskie izaicinājumi
Andrej: Kāds ir bijis vislielākais izaicinājums jūsu karjerā?
Kliffs: Vai mēs apspriežam tehnisko vai ne tehnisko jautājumu? Es teiktu, ka lielākie izaicinājumi ir tehniskie.
Runājot par tehniskajiem izaicinājumiem. Es tos vienkārši pārvarēju. Pat nezinu, kurš no tiem bija vislielākais, taču bija vairāki diezgan interesanti, kas prasīja diezgan daudz laika un mentālās cīņas. Kad es devos uz Sun, biju pārliecināts, ka izveidošu ātru kompilatoru, bet daudzie seniori, pieminot to, teica, ka man nekas nesanāks. Bet es izšķīros doties šo ceļu, uzrakstīju kompilatoru līdz reģistru piešķiršanas līmenim, un tas bija diezgan ātrs. Tas bija tikpat ātrs kā mūsdienu C1, bet tajā laikā reģistru piešķiršana bija daudz lēnāka, un, raugoties atpakaļ, tā bija liela datu struktūras problēma. Man vajadzēja to, lai uzrakstītu grafisko reģistru piešķiršanu, un es nesapratu dilemmu starp koda izteiksmību un ātrumu, kas pastāvēja tajā laikā un bija ļoti svarīga. Izrādījās, ka datu struktūra parasti pārsniedz kešatmiņas izmēru x86 procesoros tajā laikā, un tāpēc, ja es sākotnēji pieņēmēju, ka reģistru piešķiršana prasīs 5-10 procentus no visa jītēšanas laika, tad realitātē tas izrādījās 50 procenti.
Laiks gāja, kompilators kļuva arvien skaidrāks un produktīvāks, pārstāja ģenerēt šausminošu kodu lielākajā daļā gadījumu, un veiktspēja arvien vairāk sāka līdzināties tam, ko sniedz C kompilators. Ja, protams, tu neraksti kaut kādu muļķību, ko pat C nevar paātrināt. Ja tu raksti kodu kā C, tu arī saņem rezultātu, kas ir produktīvs kā C lielākajā daļā gadījumu. Un jo tālāk, jo biežāk kods izrādījās asimptotiski atbilstošs C līmenim, reģistru piešķiršana sāka izskatīties kā kaut kas pabeigts... neatkarīgi no tā, vai tavs kods darbojas ātri vai lēni. Es turpināju strādāt pie reģistru piešķiršanas, lai tā nodrošinātu labākas piešķiršanas. Tā kļuva lēnāka un lēnāka, bet sniedza arvien labākus rezultātus tajās situācijās, kur vairāk nevienam neizdevās. Es varēju ienirt reģistru piešķiršanā, pavadīt tur mēnesi darbā, un pēkšņi viss kods sāka darboties par 5% ātrāk. Tas notika vairākkārt, un reģistru piešķiršana kļuva par kaut ko līdzīgu mākslas darba statusam – visi to mīlēja vai ienida, un akadēmiskās pasaules pārstāvji uzdeva jautājumus par to, «kāpēc viss tiek darīts tieši tā». , un kāda ir atšķirība. Atbilde ir vienāda: grafu krāsošanas bāzes alokators plus ļoti rūpīga saskarne ar buferkodu ir uzvaras instruments, labākā kombinācija, ko neviens nevar sakaut. Un tas ir diezgan neuztverams moments. Viss pārējais, ko dara kompilators – ir diezgan pazīstamas lietas, lai gan tās arī ir attīstītas līdz mākslas līmenim. Es vienmēr veicu lietas, kas vajadzēja pārvērst kompilatoru par mākslas darbu. Bet nekas no tā nebija nekas ārkārtējs – izņemot reģistru alokatoru. Triks ir tajā, ka ir jābūt rūpīgam zem slodzes, un, ja tas notiek (es varu izskaidrot detalizētāk, ja tas ir interesanti), tas nozīmē, ka var izvietot agresīvāk, neriskējot pārsniegt snieguma grafika salaušanas punktu. Tajos laikos bija daudz pilna mēroga kompilatoru, kas bija pielāgoti ar visādiem uzlabojumiem un zvaniņiem, kuru sastāvā bija reģistru alokatori, bet neviens tā vairs nespēja.
Problēma ir tāda, ka, ja tu pievieno metode, kas tiek inline'ota, palielinot un palielinot inline zonas, izmantoto vērtību skaits momentāni pārsniedz reģistru skaitu, un ir jānotiek spillam. Kritiskais līmenis parasti rodas, kad alokators padodas, un viens labs kandidāts uz spilla maksā vairāk nekā cits, tu spilli kaut kādas pilnīgi trakas lietas. Inline'ēšanas vērtība ir tāda, ka tu zaudē daļu overhead, overhead uz izsaukumu un saglabāšanu, tu vari redzēt vērtības iekšā un vari tās tālāk optimizēt. Inline'ēšanas cena ir tāda, ka rodas liels dzīvās vērtību skaits, un, ja tavs reģistru alokators izspiež vairāk, nekā nepieciešams, tu momentāni zaudē. Tādēļ lielākajai daļai alokatoru ir problēma: kad inline'ēšana pārsniedz kādu robežu, sāk spilties viss, un veiktspēju var norakstīt podā. Tie, kas realizē kompilatoru, pievieno kaut kādas heuristikas: piemēram, lai apturētu inline'ēšanu, sākot no kāda pietiekami lielā izmēra, jo alokācijas visu sabojās. Tā rodas veiktspējas grafika salauzums – tu inline'ē, inline'ē, veiktspēja pamazām pieaug – un tad bum! – tā krīt uz leju strauji, jo tu esi inline'ējis pārāk daudz. Tā viss arī strādāja līdz Java parādīšanai. Java prasa daudz vairāk inline'ēšanas, tāpēc man bija jāpadara mans alokators daudz agresīvāks, lai tas izlīdzinātos, nevis kristu, un, ja tu esi pārāk daudz inline'ējis – tas sāk spilties, bet tad tāpat pienāk brīdis "nav vairāk spilla". Tas ir interesants novērojums un tas man ienāca prātā tieši no nekurienes, neviennozīmīgs, bet labi atmaksājies. Es pievērsos agresīvai inline'ēšanai un tas noveda mani tādās vietās, kur Java un C veiktspēja ir blakus. Tiešām tuvu – es varu rakstīt Java kodu, kas būs ievērojami ātrāks par C kodu un tamlīdzīgi, bet vidēji, lielajā attēlā, tie ir aptuveni salīdzināmi. Domāju, daļa no šī nopelna ir reģistru alokators, kas ļauj man inline'ot maksimāli bezgaumīgi. Es vienkārši inline'ēju visu, ko redzu. Jautājums ir, vai alokators darbojas labi, vai iznāk saprātīgs kods. Tas bija liels izaicinājums: saprast visu to un likt tam strādāt.
Nedaudz par reģistru piešķiršanu un daudzprocesoru skaitļošanu
Vladimirs: Problēmas, piemēram, reģistru alokācija, šķiet kā kāda mūžīga tēma. Interesanti, vai bija tā, ka kāda ideja izskatījās solīga, bet tad praktiski izgāzās?
Kliffs: Protams! Reģistru alokācija ir joma, kurā, lai atrisinātu NP-grūtību uzdevumu, tu mēģini atrast kādas heuristikas. Un tu nekad nespēsi panākt ideālu risinājumu, vai ne? Tas vienkārši ir neiespējami. Skatieties, Ahead of Time kompilācija – tā arī darbojās slikti. Šeit runa ir par kādiem vidējiem gadījumiem. Par tipisku veiktspēju, tāpēc var doties un izmērīt kaut ko, ko tu uzskati par labu tipisku veiktspēju – galu galā tu taču strādā pie tās uzlabošanas! Reģistru alokācija ir tēma, kas pilnībā veltīta veiktspējai. Tiklīdz tev ir pirmais prototips, kas strādā un nokrāso, kas nepieciešams, sākas darbs pie veiktspējas. Nepieciešams iemācīties labi mērīt. Kāpēc tas ir svarīgi? Ja ir skaidri dati, var skatīties uz dažādām teritorijām un redzēt: ah, tas šeit palīdzēja, bet tur viss sabojājās! Parādās dažas labas idejas, tu pievieno jaunu heuristiku un pēkšņi viss vidēji sāk darboties nedaudz labāk. Vai arī nesāk. Man bija daudz gadījumu, kad mēs cīnījāmies par piecām procentēm veiktspējas, kas atšķīra mūsu izstrādi no iepriekšējā alokatora. Un katrā reizē tas izskatās tā: kaut kur vinnēja, kaut kur zaudēja. Ja tev ir labi veiktspējas analīzes rīki, var atrast zaudējušās idejas un saprast, kāpēc tās zaudē. Varbūt ir vērts atstāt visu pa vecam, bet varbūt nopietni pievērsties smalkai regulēšanai, vai doties un salabot kaut ko citu. Tas ir vesels komplekss! Es izdarīju šo foršo haku, bet man nepieciešams arī šis, un šis, un šis – un to kopējā kombinācija nodrošina kādus uzlabojumus. Bet vienas idejas var neizdoties. Tā ir NP-grūtību uzdevumu veiktspējas darba daba.
Vladimirs: Veidojas sajūta, ka tādas lietas kā krāsošana alokatoros – ir jau atrisināta problēma. Nu, jums tā šķiet atrisināta, spriežot pēc tā, ko jūs stāstāt, vai tad vispār vajag…
Kliffs: Tā nav atrisināta kā tāda. Tev tas jāpārvērš par „atrisinātu”. Ir smagas problēmas, un tās ir jārisina. Kad tas ir izdarīts, pienāk laiks strādāt pie veiktspējas. Uz šo darbu jāattiecas atbilstoši - veikt testus, vākt metriku, izskaidrot situācijas, kad, atgriežoties pie iepriekšējās versijas, tavs vecais hack atkal sāka darboties (vai otrādi, pārstāja). Un nevajag atkāpties, kamēr kaut ko nepanāk. Kā jau teicu, ja foršas idejas, kas neizdevās, bet ideju reģistra alokācijas jomā ir aptuveni bezgalīgi. Var, piemēram, lasīt zinātniskos izdevumus. Lai gan šobrīd šī joma virzās daudz lēnāk un ir kļuvusi skaidrāka nekā savās jaunības dienās. Tomēr šajā jomā strādā vesela bezgalība cilvēku, un visas viņu idejas ir vērts izmēģināt, visas gaida savu brīdi. Un tu nevari pateikt, cik labi tās ir, ja nemēģini. Cik labi tās integrējas ar visu pārējo tavā alokatorā, jo alokators veic daudzas lietas, un dažas idejas tavā konkrētajā alokatorā var nedarboties, bet citā alokatorā – bez problēmām. Galvenais veids, kā uzvarēt alokatoram, ir izvilkt lēnos elementus ārpus galvenā ceļa un piespiedu kārtā sadalīt pa lēno ceļu malām. Tāpēc, ja tu gribi palaist GC, iet pa lēno ceļu, deoptimizēt, izmest izņēmumu, viss šādā garā - tu zini, ka šīs lietas ir salīdzinoši retas. Un tās tiešām ir reti, esmu pārbaudījis. Tu veic papildu darbu, un pateicoties tam pazūd daudz ierobežojumu uz šiem lēnajiem ceļiem, bet tas nav ļoti svarīgi, jo tie ir lēni un pa tiem reti pārvietojas. Piemēram, nulles rādītājs - tas nekad nenotiek, vai ne? Ir jābūt vairākām ceļa variantēm dažādām lietām, bet tām nevajadzētu sajaukties galvenajā.
Vladimirs: Ko tu domā par daudzkodolu, kad kodolu ir tūkstošiem? Vai tā ir noderīga lieta?
Kliffs: GPU panākumi parāda, ka tā ir diezgan noderīga!
Vladimirs: Tie ir diezgan specializēti. Bet ko par vispārīgās lietošanas procesoriem?
Kliffs: Nu, tā bija Azul biznesa modelis. Atbilde nāca vēl tajā ēras laikā, kad cilvēki ļoti mīlēja prognozējamu veiktspēju. Tajā laikā bija grūti rakstīt paralēlo kodu. H2O kodēšanas modelis labi pielāgojas, bet tas nav vispārējas lietošanas modelis. Drīzāk tas ir nedaudz vispārīgāks nekā, izmantojot GPU. Mēs runājam par šāda veida izstrādes grūtībām vai par tās izmantošanas sarežģītību? Piemēram, interesants mācību stunda, ko man deva Azul, diezgan neskaidrs: mazi keši - tas ir normāli.
Lielākais izaicinājums dzīvē
Vladimirs: Kā ir ar netehniskiem izaicinājumiem?
Kliffs: Lielākais izaicinājums bija nenotikties… laipni un labi pret cilvēkiem. Un kā sekas, es pastāvīgi nonācu ļoti konfliktos situācijās. Tajās, kurās es zināju, ka viss norit nepareizi, bet nezināju, kā virzīties uz priekšu šo problēmu risināšanā un nespēju ar tām tikt galā. Daudzas ilgas problēmas, kas ilga desmitiem gadu, radās tieši šādā veidā. Tas, ka Java ir kompilatori C1 un C2 – ir tieša šī sekas. Tas, ka Java desmit gadu garumā nebija daudzlīmeņu kompilācijas – arī ir tieša sekas. Acīmredzami, ka mums šāda sistēma bija nepieciešama, bet nav skaidrs, kāpēc tās nebija. Man bija problēmas ar vienu inženieri… vai inženieru grupu. Pirms daudziem gadiem, kad es sāku strādāt Sun, es biju… Labi, ne tikai toreiz, man vispār vienmēr ir savs viedoklis. Un es uzskatīju, ka var vienkārši paņemt šo savu patiesību un izteikt to tieši. Turklāt, es biju šokējoši pareizs lielāko daļu laika. Un, ja tev šāds pieeja nepatīk… jo īpaši, ja tu acīmredzami kļūdies un dari muļķības… Vispār, nedaudzi cilvēki varēja paciest šādu komunikācijas formu. Tomēr daži varēja, piemēram, es. Es visu savu dzīvi esmu būvējis uz meritokrātiskiem principiem. Ja tu man parādīsi kaut ko nepareizu, es tūlīt pagriezīšos un teikšu: tu teici muļķības. To darot, protams, atvainojos un visu tādu, atzīšu nopelnu, ja tādi vispār pastāv, un veicu citas pareizas darbības. No otras puses, es esmu šokējoši pareizs šokējoši lielā procentā kopējā laika. Un tas nevis ir ļoti labi strādā attiecībās ar cilvēkiem. Es nemaz nemēģinu būt laipns, bet nostādīju jautājumu uz paplātes. „Šis nekad nestrādās, jo viens, divi un trīs.” Un viņi tādi: „Ak!” Bija arī citi sekas, kuras, iespējams, labāk būtu izlaižu: piemēram, tādi, kas noveda pie šķiršanās ar sievu un desmit gadu depresijas pēc tam.
Izaicinājums ir cīņa ar cilvēkiem, ar viņu uztveri par to, ko tu vari vai nevari darīt, kas ir svarīgi un kas nav. Ir bijuši daudzi izaicinājumi par kodēšanas stilu. Es joprojām rakstu daudz kodu, un tajos laikos man pat nācās palēnināt tempu, jo es veicu pārāk daudz paralēlu uzdevumu un neizdarīju tos labi, nevis koncentrējos uz vienu. Atgriežoties pagātnē, es uzrakstīju pusi no Java JIT komandas koda, C2 komandas. Nākamais ātrākais kodētājs rakstīja uz pusi lēnāk, nākamais – vēl uz pusi lēnāk, un tas bija eksponenciāls kritums. Septītais cilvēks šajā rindā bija ļoti, ļoti lēns – tā vienmēr ir! Es strādāju ar daudz kodu. Es skatījos, kurš ko raksta, bez izņēmumiem, es skatījos viņu kodu, veicu pārskatus par katru no viņiem un joprojām turpināju rakstīt vairāk nekā kāds no viņiem. Ar cilvēkiem šāda pieeja ne vienmēr darbojās labi. Daži to nepatika. Un kad viņi ar to nespēja tikt galā, sākās visāda veida sūdzības. Piemēram, reiz man tika teikts pārtraukt rakstīt kodu, jo es rakstu pārāk daudz kodu, un tas apdraud komandu, un viss tas man izklausījās kā joks: čalis, ja visa pārējā komanda izzudīs un es turpināšu rakstīt kodu, tu zaudēsi tikai pusi komandas. No otras puses, ja es turpināšu rakstīt kodu un tu zaudēsi pusi komandas – tas izklausās pēc ļoti sliktas pārvaldības. Es nekad par to daudz nedomāju, nekad par to nerunāju, bet tas tomēr bija kaut kur manā prātā. Apziņā riņķoja doma: 'Jūs taču jokojat?'. Līdz ar to, vislielākā problēma biju es un manas attiecības ar cilvēkiem. Tagad es saprotu sevi daudz labāk, es ilgi biju komandas vadītājs programmētājiem, un tagad es taisni saku cilvēkiem: zini, es esmu tāds, kāds es esmu, un jums būs jāarī ar mani – vai ir labi, ja es šeit pastāvu? Un kad viņi ar to sāka tikt galā, viss sāka darboties. Es nevienam neesmu ne slikts, ne labs, man nav nekādu sliktu nodomu vai egoistisku tieksmju, tas vienkārši ir mans būtība, un ar to kaut kā jādzīvo.
AndrejPavisam nesen visi sāka runāt par pašapziņu introvertiem un vispār par mīkstajām prasmēm. Ko par to var teikt?
Kliffs: Jā, tas bija izpratne un mācība, ko es guvu no šķiršanās ar sievu. Ko es guvu no šķiršanās – tā ir izpratne par sevi. Tā es sāku saprast citus cilvēkus. Saprast, kā darbojas šī mijiedarbība. Tas izsauca atklājumus viens pēc otra. Parādījās apziņa, kas es esmu, un kas esmu. Ko es daru: vai es esmu noraizējies par uzdevumu, vai izvairos no konflikta, vai kaut kas cits – un šāds pašapziņas līmenis tiešām palīdz turēt sevi rokās. Pēc tam viss iet daudz vieglāk. Viens punkts, ko es esmu atklājis ne tikai sev, bet arī citiem programmētājiem – nespēja verbilizēt domas, kad esi emocionālā stresa stāvoklī. Piemēram, tu sēdi un kodē, esi plūsmā, un tad pie tevis pienāk un sāk kliegt histērijā, ka kaut kas ir salūzis, un tagad tiks pielietoti ekstrēmi pasākumi. Un tu nespēj neko pateikt, jo esi emocionālā stresa stāvoklī. Iegūtā zināšanās ļauj sagatavoties šim mirklim, izdzīvot to un pāriet uz atkāpšanās plānu, pēc kura var darīt kaut ko. Tātad, jā, kad tu sāk saprast, kā viss tas darbojas – tas ir milzīgs notikums, kas maina dzīvi.
Es pats neizdevās atrast pareizos vārdus, bet atcerējos darbību secību. Jēga ir tāda, ka šī reakcija ir tikpat fiziska, cik verbāla, un tev ir nepieciešama telpa. Tādu telpu, zenā. Tieši to ir nepieciešams izskaidrot, un tad uzreiz atkāpties – tīri fiziski atkāpties. Kad es klusēju ar vārdiem, es varu apstrādāt situāciju emocionālajā daļā. Pēc tam, kad adrenalīns nonāk smadzenēs, tu pārej uz
Un tādēļ ir nepieciešama verbālā telpa. Vienkārši brīva telpa. Ja vispār kaut ko teikt, tad var tieši to apgalvot, un pēc tam doties un patiešām atrast sev "telpu": doties pastaigā pa parku, ieslēgties dušā – nav nozīmes. Galvenais ir uz laiku atslēgties no tās situācijas. Tikko tu pat uz dažām sekundēm atslēdzies, atgriežas kontrole, tu sāk domāt skaidri. "Labi, es neesmu idiots, es nedaru muļķīgas lietas, es esmu diezgan noderīgs cilvēks." Tiklīdz tu spēj pārliecināt pats sevi, ir laiks pāriet uz nākamo posmu: saprast, kas noticis. Tevi uzbruka, uzbrukums nāca no negaidītas vietas, tā bija negodīga izsistība. Tas ir slikti. Nākamais solis – saprast, kāpēc uzbrucējam tas bija nepieciešams. Patiesībā, kāpēc? Varbūt tāpēc, ka viņš pats ir dusmīgs? Kāpēc viņš ir dusmīgs? Piemēram, tāpēc, ka viņš pats ir kļūdījies un nevar uzņemties atbildību? Šādā veidā ir jāpievēršas visai situācijai. Bet tam ir nepieciešama manevrēšanas telpa, verbālā telpa. Pirmais solis – pārtraukt verbālo kontaktu. Aiziet no vārdu apspriešanas. Atcelt to, aiziet prom, cik ātri vien iespējams. Ja tas ir telefona saruna – vienkārši noliec klausuli – šī ir prasme, kuru es guvu no sarunām ar bijušo sievu. Ja saruna nenoved pie nekas laba, vienkārši saki "uz redzēšanos" un noliec klausuli. No otras puses: "bla-bla-bla", tu atbildi: "aha, uz redzēšanos!" un noliec klausuli. Vienkārši pārtrauc sarunu. Pēc piecām minūtēm, kad tev atgriežas spēja skaidri domāt, tu esi nedaudz atdzisis, kļūst iespējams visu pārdomāt, kas vispār notika un kas notiks tālāk. Un sākt formulēt pārdomātu atbildi, nevis vienkārši reaģēt uz emocijām. Manā paša apziņas attīstībā tieši tas, ka emocionālā stresa gadījumā es nevaru runāt, ir bijis atklājums. Iziet no šīs stāvokļa, apdomāt un plānot, kā atbildēt un kompensēt problēmas – šie ir pareizi soļi gadījumā, ja nevar runāt. Vienkāršākais veids – aizbēgt no situācijas, kurā izpaužas emocionālais stress un vienkārši pārtraukt piedalīties šajā stresā. Pēc tam tu atgūsti spēju domāt, kad tu vari domāt, parādās iespēja runāt, un tā tālāk.
Starp citu, pretējās puses advokāts tiesā mēģina darīt to ar tevi – tagad jau ir skaidrs, kāpēc. Jo viņam ir iespēja tevi apspiest līdz tādai pakāpei, ka tu pat nevari izrunāt savu vārdu, piemēram. Tiešā nozīmē, tu nespēji runāt. Ja tas notiek ar tevi un ja tu zini, ka nonāksi vietā, kur plosās verbālas cīņas, piemēram, tiesā, tu vari atnākt ar savu juristu. Jurists aizstāvēs tevi un pārtrauks verbālo uzbrukumu, un to izdarīs pavisam likumīgi, un tev atgriezīsies zaudētais zen telpas sajūta. Piemēram, man dažreiz bija nepieciešams piezvanīt ģimenei, tiesnesis pret to attiecās diezgan draudzīgi, bet pretējās puses advokāts kliedza un kliedza uz mani, es pat vārdu nevarēju ievietot. Šādās situācijās man vislabāk strādā starpnieka izmantošana. Starpnieks pārtrauc visu šo spiedienu, kas tev nepārtraukti plūst virsū, tu atrod nepieciešamo zen telpas sajūtu, kopā ar to atgriežas spēja runāt. Tas ir vesela zināšanu joma, kurā jāapgūst daudz, daudz jāatklāj sevī, un viss tas pārvēršas augsta līmeņa stratēģiskos risinājumos, kas ir dažādi atkarībā no cilvēka. Dažiem cilvēkiem nav augstāk minēto problēmu, parasti tās nav cilvēkiem, kas profesionāli nodarbojas ar pārdošanu. Visi šie cilvēki, kas pelna iztiku ar vārdiem – pazīstami dziedātāji, dzejnieki, reliģiskie darbinieki un politiķi, viņiem vienmēr ir ko teikt. Viņiem nav šādu problēmu, bet man tās ir.
Andrej: Tas bija… negaidīti. Lieliski, mēs jau esam diezgan daudz runājuši un ir laiks noslēgt šo interviju. Mēs noteikti tiksimies konferencē un varēsim turpināt šo dialogu. Tiksimies Hydra!
Turpināt sarunu ar Klifu varēs konferencē Hydra 2019, kas notiks 11.-12. jūlijā 2019. gadā Sanktpēterburgā. Viņš ieradīsies ar ziņojumu . Biļetes var iegādāties .
Avots: habr.com
