Cliff Click — CTO i Cratus (sensorët IoT për përmirësimin e proceseve), themelues dhe bashkëthemelues i disa startupeve (përfshirë Rocket Realtime School, Neurensic dhe H2O.ai) me disa dalje të suksesshme. Cliff shkroi kompilatorin e tij të parë në moshën 15 vjeçare (Pascal për TRS Z-80)! Ai është më i njohur për punën mbi C2 në Java (the Sea of Nodes IR). Ky kompilator i tregoi botës se JIT mund të prodhojë kod të kualitetit, që ishte një nga faktorët që çoi në zhvillimin e Java si një nga platforma kryesore të programimit moderne. Më pas, Cliff ndihmoi Azul Systems të ndërtojë një mainframe me 864 bërthama me softuer në Java të pastër, që mbështeste pauzat GC mbi një heap 500-giga në rreth 10 milisekonda. Në përgjithësi, Cliff ka punuar në të gjitha aspektet e JVM.
Ky post është një intervistë e gjerë me Cliff. Do të flasim për temat në vijim:
- Kalimi te optimizimet e nivelit të ulët
- Si të bëni një ristrukturim të madh
- Modeli i kostos
- Mësimi i optimizimeve të nivelit të ulët
- Shembuj praktikë për përmirësimin e performancës
- Pse të krijoni një gjuhë programimi tuajën
- Karriera e inxhinierit të performancës
- Sfidat teknike
- Pak për alokimin e regjistrave dhe shumëbërthamerinë
- Sfidat më të mëdha në jetën tuaj
Interviste me:
- Andrei Satarin nga Amazon Web Services. Në karrierën e tij, ai ka punuar në projekte të ndryshme: ka testuar një bazë të dhënash të shpërndarë NewSQL në Yandex, një sistem të zbulimit në re në Laboratorin Kaspersky, një lojë shumëlojtëshe në Mail.ru dhe një shërbim llogaritës të çmimeve valutore në Deutsche Bank. Ka interesa në testimin e sistemeve të mëdha backend dhe të shpërndara.
- Vladimir Sitnikov nga Netcracker. Ka dhjetë vjet që punon në performancën dhe shkallëzimin e NetCracker OS — software që përdoret nga operatorët e komunikimit për automatizimin e proceseve të menaxhimit të rrjetit dhe pajisjeve të rrjetit. Ka interesa mbi performancën e Java dhe Oracle Database. Autori i më shumë se dhjetë përmirësimeve të performancës në drejtuesin zyrtar të PostgreSQL JDBC.
Kalimi te optimizimet e nivelit të ulët
Andrei: Ju jeni një person i njohur në botën e JIT-kompilimit, në Java dhe punën mbi performancën në përgjithësi, apo jo?
: Kështu është!: Le të fillojmë me pyetje të përgjithshme mbi punën në performancë. Çfarë mendoni për zgjedhjen midis optimizimeve të nivelit të lartë dhe atyre të nivelit të ulët siç është puna në nivelin e CPU?
Andrei: Po, këtu është e thjeshtë. Kodi më i shpejtë është ai që asnjëherë nuk ekzekutohet. Prandaj, gjithmonë duhet të filloni nga një nivel i lartë, të punoni mbi algoritmet. Një O-notacion më i mirë do të tejkalojë një O-notacion më të keq, përveç nëse ndërhyrin ndonjëherë konstanta të mëdha. Çështjet e nivelit të ulët vijnë në fund. Zakonisht, nëse keni optimizuar mjaftueshëm pjesën tjetër të stakut dhe ndonëse ka diçka interesante — ajo është niveli i ulët. Por si të filloni nga një nivel i lartë? Si ta dini që keni bërë mjaft punë në nivelin e lartë? Epo… nuk ka receta të gatshme. Duhet të kuptoni problemin, të vendosni se çfarë do të bëni (për të mos bërë hapa të padobishëm më pas), dhe atëherë mund të nxirrni profiler-in që mund të thotë diçka të dobishme. Në një moment, ju vetë kuptoni që keni hequr elementet e panevojshme dhe ka ardhur koha të merremi me optimizimin e nivelit të ulët. Kjo, padyshim, është një lloj arti të veçantë. Një mori njerëzish bën gjëra të panevojshme, por lëvizin aq shpejt saqë nuk kanë kohë të mendojnë për performancën. Por kjo ndodh deri në momentin kur ndonjë çështje dalin në pah. Zakonisht 99% e kohës askujt nuk i intereson se çfarë po bëj, deri në momentin kur një gjë e rëndësishme doli në rrugën kritike. Atëherë, të gjithë fillojnë të pyesin pëse gjithçka nuk ka funksionuar perfekt nga fillimi. Në përgjithësi, gjithmonë ka diçka për të përmirësuar në performancë. Por 99% e kohës nuk keni indikacione! Ju thjesht përpiqeni të bëni diçka të funksionojë dhe gjatë kësaj arrini të kuptoni se çfarë është e rëndësishme. Kurrë nuk mund të dini paraprakisht se cila pjesë duhet të jetë perfekte, prandaj zhvillimi i përsosur në çdo aspekt është e pamundur, dhe ashtu nuk e bëni. Gjithmonë ka shumë gjëra për të riparuar — dhe kjo është krejt normale.
: Kështu është!: Këtu gjithçka është e thjeshtë. Kodi më i shpejtë është ai, i cili kurrë nuk ekzekutohet. Prandaj, gjithmonë duhet të filloni nga një nivel i lartë, të punoni mbi algoritmet. Një nota O më e mirë do ta mposhtë një nota O më të keqe, përveç nëse ndërhyjnë disa konstanta mjaft të mëdha. Gjërat me nivel të ulët shkojnë në fund. Zakonisht, nëse e keni optimizuar gjithë stack-un e mbetur mjaft mirë dhe ka ende diçka interesante – ajo është niveli i ulët. Por si të filloni nga një nivel i lartë? Si të dini se keni kryer mjaft punë në nivel të lartë? Epo... ashtu. Nuk ka receta të gatshme. Duhet të kuptoni problemin, të vendosni se çfarë keni ndërmend të bëni (për të mos bërë hapa të panevojshëm më vonë) dhe pastaj mund të nisni profiler-in, i cili mund të thotë diçka të dobishme. Në një moment, ju vetë kuptoni se jeni liruar nga gjërat e panevojshme dhe ka ardhur koha për të punuar në optimizimin e nivelit të ulët. Kjo është, pa dyshim, një lloj artistike e veçantë. Një grumbull njerëzish bëjnë gjëra të panevojshme, por lëvizin aq shpejt sa nuk kanë kohë të merren me performancën. Por kjo është derisa çështja bëhet urgjente. Zakonisht, 99% të kohës askujt nuk i intereson se çfarë po bëj, deri në momentin kur diçka e rëndësishme del në rrugën kritike, në të cilën dikujt i intereson. Dhe atëherë të gjithë fillojnë të të ngacmojnë përse në fillim nuk ka punuar perfekt. Në përgjithësi, gjithmonë ka diçka për të përmirësuar në performancë. Por 99% të kohës nuk keni asnjë pigment! Thjesht po përpiqeni të bëni diçka të funksionojë dhe gjatë kësaj kuptoni se çfarë është e rëndësishme. Kurrë nuk mund të dini paraprakisht se ky copë është ai që duhet ta bëni perfektin, prandaj, në thelb, duhet të jeni perfekt në të gjitha. Dhe kjo është e pamundur dhe nuk e bëni ashtu. Gjithmonë ka shumë gjëra për të riparuar – dhe kjo është krejt normale.
Si të bëni një ristrukturim të madh
Andrei: Si punoni mbi performancën? A është kjo një problem i përgjithshëm. Për shembull, a ju ka ndodhur ndonjëherë të punoni mbi probleme që lindin nga ndërveprimi i shumë funksionaliteteve ekzistuese?
: Kështu është!: Po përpiqem ta evitoj këtë. Nëse e di që performanca do të jetë një problem, mendoj para se të filloj të kodoj, sidomos për strukturat e të dhënave. Por shpesh zbulojmë gjithë këtë shumë më vonë. Atëherë detyrohemi të marrim masa drastike dhe të bëjmë atë që e quaj «rishkruaj dhe sundo»: duhet të kapim një copë të mjaftueshme. Një pjesë e kodit do të duhet të rishkruhet për shkak të problemeve me performancën ose për ndonjë arsye tjetër. Çfarëdo arsyeje për rishkrimin e kodit që të ndodhë, është gjithmonë më mirë të rishkruash një copë më të madhe sesa një copë më të vogël. Në këtë moment, të gjithë fillojnë të dridhen nga frika: «o zot, nuk mund ta prekësh kaq shumë kod!» Por, në fakt, ky qasje funksionon shumë më mirë. Duhet menjëherë të merresh me një problem të madh, ta përshkruash rreth tij një rreth të madh dhe të thuash: gjithçka brenda rrethit, unë do ta rishkruaj. Kufiri është shumë më i vogël se ai përmbajtje brenda tij që duhet të zëvendësohet. Dhe nëse kjo përshkrim i kufijve e bën punën brenda ideale – ke duar të lira, bëj çfarë të duash. Sapo e kupton problemin, procesi i rishkrimit bëhet shumë më i lehtë, prandaj merre një copë të madhe!
Në të njëjtën kohë, kur bën rishkrimin me një copë të madhe dhe e kupton se performanca do të bëhet një problem, mund të fillosh menjëherë të shqetësohesh për të. Zakonisht, kjo kthehet në gjëra të thjeshta si «mos kopjo të dhënat, menaxho të dhënat sa më thjeshtë, bëji më të vogla». Në rishkrimet e mëdha, ka mënyra standarde për të përmirësuar performancën. Dhe ato pothuajse gjithmonë qendrojnë rreth të dhënave.
Modeli i kostos
Andrei: Në një nga podkasterët, flisnit për modelet e kostos në kontekstin e performancës. Mund të shpjegoni se çfarë nënkuptohej me këtë?
: Kështu është!: Sigurisht. Jam lindur në epokën kur performanca e procesorit ishte ekstremisht e rëndësishme. Dhe kjo epokë po kthehet përsëri – fati nuk është pa ironi. Fillova të jetoj në kohët e makinave me tetë bitë, kompjuteri im i parë punonte me 256 byte. Pikërisht byte. Gjithçka ishte shumë e vogël. Duhej të numëroja instrukcionet dhe sapo filluam të ngritemi në shkallën e gjuhëve të programimit, gjuhët merrnin përsipër gjithnjë e më shumë. Ishte Assembler, pastaj Basic, pastaj C, dhe C merrte përsipër punën me shumë detaje, si shpërndarjen e regjistrave dhe zgjidhjen e instrukcioneve. Por aty ishte relativisht e qartë dhe nëse krijoja një tregues për një instancë të ndryshores, do të merrja një ngarkesë, dhe kjo instrukcion kishte një kosto të njohur. Hardueri jep një numër të njohur të cikleve makinerike, kështu që shpejtësia e ekzekutimit të gjërave të ndryshme mund të llogaritet thjesht duke mbledhur të gjitha instrukcionet që ke për të ekzekutuar. Çdo krahasim/testim/degjim/thirrje/ngarkesë/ruajtje mund të ishte i mbledhur dhe të thuash: ja sa është koha e ekzekutimit. Ndërsa merresh me përmirësimin e performancës, sigurisht që do të vëreni se numrat përputhen me ciklet e vogla të nxehta.
Por sapo kalon në Java, Python dhe gjëra të ngjashme, shumë shpejt largohesh nga hardueri me nivel të ulët. Çfarë është kostoja e thirrjes së getter-it në Java? Nëse JIT në HotSpot e ka bërë gjithçka siç duhet , do të jetë ngarkesë, por, nëse ai nuk e bëri këtë – do të jetë një thirrje funksioni. Duke qenë se thirrja ndodhet në ciklin e nxehtë, do të anulojë të gjitha optimizimet e tjera në këtë cikël. Prandaj kostoja reale do të jetë shumë më e madhe. Dhe menjëherë humb aftësinë për të parë një copë kod dhe të kuptosh se sa ia vlen ta ekzekutojmë atë në terma të frekuencës së ciklit të procesorit, memories përdorur dhe caches. Të gjitha këto bëhen interesant vetëm nëse në të vërtetë thellohesh në performancë.
Tani jemi në një situatë ku shpejtësitë e procesorëve nuk janë rritur pothuajse për një dekadë. Kohët e vjetra po kthehen! Nuk mund të presësh më një performancë të mirë me një rrjedhë. Por nëse ndonjëherë merresh me llogaritjet paralel – kjo është jashtëzakonisht e komplikuar, të gjithë të shikojnë si në James Bond. Shtesat dhjetëfish këtu zakonisht ndodhin në ato vende ku dikush e ka humbur diçka. Paraleliteti kërkon shumë punë. Për të marrë atë shtesën dhjetëfish, duhet të kuptosh modelin e kostos. Çfarë dhe sa kushton. Dhe për këtë, duhet të kuptosh se si gjuha vendoset mbi pajisjen në nivel të ulët.
Martin Thompson zgjodhi një fjalë të shkëlqyer për blogun e tij ! Është e nevojshme të kuptohet se çfarë do të bëjë hardueri, si do ta bëjë atë dhe pse e bën atë që bën. Duke e përdorur këtë, është mjaft e thjeshtë të filloni të përllogaritni instrukcionet dhe të zbuloni se ku shkon koha e ekzekutimit. Nëse nuk keni përgatitjen e duhur, thjesht po kërkoni një mace të zezë në një dhomë të errët. Unë vazhdimisht shoh njerëz që optimizojnë performancën, të cilët nuk kanë asnjë ide se çfarë po bëjnë. Ata vuajnë shumë dhe nuk përparojnë shumë. Dhe kur unë marr të njëjtin copë kodi, i jap disa hile të vogla dhe arrij një rritje pesëfish ose dhjetëfish, ata thonë: po, nuk është e drejtë, ne e dinim se ishe më i mirë. Është befasuese. Për çfarë po flas… modeli i kostos – është për atë se çfarë kodi shkruani dhe sa shpejt punon në përgjithësi.
Andrei: Dhe si të mbash një volum të tillë në mendje? A arrihet kjo përmes një sasi të madhe përvoje, apo? Ku mund të fitohet një përvojë e tillë?
: Kështu është!: Epo, përvoja ime nuk erdhi në një rrugë të lehtë. Kam programuar në Assembler kur isha akoma në ato kohëra kur mund të kuptoje çdo njësi të veçantë. Kjo tingëllon e çmendur, por që atëherë në mendjen time, në kujtesë, ka mbetur një grup instrukcionesh Z80. Nuk i mbaj mend emrat e njerëzve vetëm pas një minute bisedë, por mbaj mend kodin e shkruar 40 vjet më parë. Qesharake, duket si sindromi i “».
Mësimi i optimizimeve të nivelit të ulët
Andrei: A ka ndonjë mënyrë më të thjeshtë për t'u futur në këtë?
: Kështu është!: И да и нет. Железо, которым мы все пользуемся, за это время не так уж изменилось. Все используют x86, за исключением смартфонов на Arm. Если ты не занимаешься каким-то хардкорным эмбеддедом, у тебя все то же самое. Хорошо, дальше. Инструкции тоже веками не менялись. Нужно пойти и написать что-нибудь на Ассемблере. Немного, но достаточно, чтобы начать понимать. Вы вот улыбаетесь, а я совершенно серьезно говорю. Нужно понять соответствие языка и железа. После этого нужно пойти, пописать немного и сделать небольшой игрушечный компилятор для небольшого игрушечного языка. «Игрушечный» означает, что нужно сделать его за разумное время. Он может быть суперпростым, но должен генерировать инструкции. Акт генерации инструкции позволит понять модель стоимости для моста между высокоуровневым кодом, на котором все пишут, и машинным кодом, который выполняется на железе. Это соответствие прожжется в мозгах в момент написания компилятора. Даже самого простенького компилятора. После этого можно начать смотреть на Java и то, что у нее семантическая пропасть куда глубже, и возводить поверх нее мосты куда сложнее. В Java гораздо сложнее понять, получился ли наш мост хорошим или плохим, что заставит его развалиться и что нет. Но тебе нужна какая-то отправная точка, когда ты смотришь на код и понимаешь: «ага, этот геттер должен инлайниться каждый раз». А дальше оказывается, что иногда так и происходит, за исключением ситуации, когда метод становится слишком большим, и JIT начинает инлайнить все подряд. Производительность таких мест можно предсказать мгновенно. Обычно геттеры работают хорошо, но потом ты смотришь на большие горячие циклы и понимаешь, что там плавают какие-то вызовы функций, которые непонятно что делают. В этом и есть проблема с повсеместным использованием геттеров, причина по которой они не инлайнятся – непонятно, геттер ли это. Если у тебя супермаленькая кодовая база, ее можно просто запомнить и потом сказать: вот это геттер, а вот это сеттер. В большой кодовой базе каждая функция проживает свою собственную историю, которая никому, в общем-то, не известна. Профайлер говорит, что мы потеряли 24% времени на каком-то цикле и чтобы понять, что делает этот цикл, нужно посмотреть на каждую функцию внутри. Невозможно понять это, не изучая функцию, и это серьезно замедляет процесс понимания. Поэтому я и не использую геттеры и сеттеры, я вышел на новый уровень!
Nga ta merrni modelin e çmimit? Epo, mund të lexoni diçka, sigurisht... Por mendoj se mënyra më e mirë është të veproni. Të krijoni një kompilator të vogël dhe kjo do të jetë mënyra më e mirë për të kuptuar modelin e çmimit dhe për ta vendosur atë në mendjen tuaj. Një kompilator i vogël që do të ishte i dobishëm për programimin e mikrovalëve - kjo është një detyrë për fillestarët. E kam fjalën se nëse tashmë keni aftësi programimi, do t'ju mjaftojnë ato. Të gjitha këto gjëra si të analizosh një varg, i cili do të jetë një shprehje algebrike, të nxjerrësh nga ajo udhëzimet për operacionet matematike në rendin e duhur, të marrësh vlerat e sakta nga regjistrat - të gjitha këto bëhen lehtësisht. Dhe derisa të bëni këtë, do të përshtatet në mendje. Mendoj se të gjithë e dinë çfarë bën një kompilator. Dhe kjo do të ofrojë kuptimin e modelit të çmimit.
Shembuj praktikë për përmirësimin e performancës
Andrei: Në çfarë tjetër duhet të keni parasysh kur punoni për performancën?
: Kështu është!: Strukturat e të dhënave. Po, ndoshta e kam lënë mënjanë këtë për një kohë të gjatë... . Kjo ishte argëtuese, por kërkonte shumë përpjekje, dhe unë kam edhe një jetë tjetër! Mirë, kështu që, në një nga leksionet më të mëdha dhe më interesante, "Kudë shkon performanca juaj", u jepja studentëve një shembull: dy e gjysmë gigabajt të dhënash fintech u lexuan nga një skedë CSV dhe pastaj duhej të llogaritej numri i produkteve të shitura. Të dhëna të zakonshme të tregut. Paketa UDP, të kthyer në format tekstual që nga vitet '70. Chicago Mercantile Exchange – gjëra të tilla si nafta, misri, fasule soje, dhe të ngjashme. Duhej të llogariteshin këto produkte, numri i transaksioneve, mesatare e volumit të lëvizjes së fondeve dhe mallrave, etj. Kjo është matematikë e thjeshtë tregtare: të gjesh kodin e produktit (kjo është 1-2 simbole në një tabelë hash), të marrësh shumën, ta shtosh në një nga grupet e transaksioneve, të shtosh volumin, të shtosh kostot, dhe disa gjëra të tjera. Një matematikë shumë e thjeshtë. Implementimi i lodrave ishte shumë i thjeshtë: gjithçka është në skedë, lexoj skedën dhe lëviz përmes saj, duke ndarë regjistrat individualë në rreshta Java, kërkoj në to gjërat e nevojshme dhe i llogaris sipas matematikës së përmendur më sipër. Dhe kjo funksionon me një shpejtësi të vogël.
Me një qasje të tillë, gjithçka është e qartë se çfarë ndodh, dhe llogaritjet paralel këtu nuk do të ndihmojnë, apo jo? Siç duket, një rritje pesëfish e performancës mund të arrihet vetëm duke zgjedhur struktura të duhura të të dhënave. Dhe kjo e befason madje edhe programuesit me përvojë! Në rastin tim konkret, theksi ishte se nuk duhet të bëni alokime memorie në një cikël të nxehtë. Epo, kjo nuk është e gjitha e vërtetë, por në përgjithësi - nuk duhet të alokoni "një herë në X", kur X është mjaft i madh. Kur X është dy e gjysmë gigabajt, nuk duhet të alokoni "një herë për një shkronjë", apo "një herë për një rresht", apo "një herë për një fushë", asgjë e ngjashme. Kjo është ajo që merr kohë. Si funksionon kjo? Imagjinoni se bëj thirrjen String.split() ose BufferedReader.readLine(). Readline bën një varg nga një grup bajtesh që vijnë nga rrjeti, një herë për çdo rresht, për çdo një nga qindra miliona rreshta. E analizoj këtë rresht, e heq, dhe përse e heq - e kam përpunuar tashmë, mjafton. Prandaj, për çdo bajt të lexuar nga këto 2.7G, regjistrohet dy simbole në një varg, që do të thotë se tashmë kemi 5.4G, dhe ato më nuk më duhen, prandaj i heq. Nëse shikojmë kapasitetin e memorjes, ngarkohemi 2.7G, që kalon përmes memories dhe autobusit të memories në procesor, dhe pastaj dy herë më shumë dërgohet në varg, që qëndron në memorie, dhe gjithçka është e fshirë me krijimin e çdo vargu të ri. Por unë duhet ta lexoj, hardueri e lexon, edhe nëse më pas do të jetë gjithçka e fshirë. Dhe duhet ta shkruaj, sepse kam krijuar një varg dhe cache-t janë mbushur - cache-i nuk mund të përmbajë 2.7G. Pra, për çdo bajt të lexuar, lexoj edhe dy bajt të tjerë dhe shkruaj dy bajt të tjerë, dhe në përfundim kemi një raport 4:1 - në këtë raport ne shpenzojmë pa kuptuar kapacitetin e memorjes. Dhe më pas del se nëse bëj String.split() – e bëj këtë jo herën e fundit, brenda mund të ketë edhe 6-7 fushat. Prandaj, kodi klasik i leximit të CSV me analizën e mëvonshme të rreshtave çon në humbje të kapacitetit të memorjes që është rreth 14:1 në raport me atë që ju në të vërtetë do të dëshironit të kishit. Nëse i heqim ato alokime, mund të arrijmë një përshpejtim pesëfish.
Dhe kjo nuk është diçka shumë e vështirë. Nëse e shikoni kodin nga këndvështrimi i duhur, gjithçka bëhet mjaft e thjeshtë, sapo të kuptoni thelbin e problemit. Nuk duhet të ndaloni kurrë së alokuarit të memories: problemi është se bëni një alokim dhe ajo vdes menjëherë, duke djegur një burim të rëndësishëm, që në këtë rast është brezi i memories. Çdo gjë përfundon në një rënie të performancës. Në x86 zakonisht duhet të digjni aktivisht ciklet e procesorit, ndërsa këtu keni djegur gjithë memories përpara. Zgjidhja është të reduktoni numrin e alokimeve.
Një pjesë tjetër e problemit është se, nëse aktivizoni profailer-in kur brezi i memories ka mbaruar, pikërisht në momentin që ndodh, zakonisht prisni rikthimin e caches, sepse ato janë plot me plehra që sapo keni gjeneruar me të gjitha ato vargje. Prandaj, çdo operacion load ose store bëhet i ngadaltë, sepse ata shkaktojnë humbje në cache – e gjithë cache është bërë e ngadaltë, duke pritur deri sa të largohet plehu. Prandaj, profiler-i vetëm do të tregojë një zhurmë të rastësishme të ngrohtë, e shpërndarë gjatë gjithë ciklit – nuk do të ketë asnjë udhëzim të veçantë të nxehtë apo vend në kod. Vetëm zhurmë. Dhe nëse shikoni ciklet GC, ato të gjitha do të jenë nga Young Generation dhe jashtëzakonisht të shpejta – mikrosekonda ose milisekonda maksimumi. Sepse gjithë kjo memories vdes menjëherë. Ju alokoni miliarda gigabajt dhe ai i pret, e i pret, e përsëri i pret. Të gjitha ndodhin shumë shpejt. Pjesa më e lirë janë ciklet GC, zhurma e ngrohtë gjatë të gjithë ciklit, por ne duam një përshpejtim 5-fish. Në atë moment, duhet të lidhet diçka në mendjen tuaj dhe të dëgjohet: "pse ndodh kështu?!". Përcaktimi i mbingarkesës së brezi të memories nuk shfaqet në debugger-in klasik, duhet të aktivizoni debugger-in e numerave të performancës dhe ta shihni këtë vetë dhe drejtpërdrejt. Dhe, jo drejtpërdrejt, mund ta dyshoni nga këto tre simptoma. Simptoma e tretë është kur shikoni çfarë po alokoni, pyesni profiluesin, dhe ai përgjigjet: "Keni krijuar një miliard vargjesh, por GC ka punuar falas". Sapo ndodh kjo, kuptoni se keni gjeneruar shumë objekte dhe keni djegur gjithë brezin e memories. Ka një mënyrë për t'u shkëputur nga kjo, por nuk është e qartë.
Problemi është në strukturën e të dhënave: struktura e thjeshtë që qëndron pas të gjithave, është shumë e madhe, është 2.7G në disk, prandaj të bësh një kopje të kësaj gjëje është shumë e paivlershme – dëshiron ta ngarkosh atë në mënyrë direkte nga bufferi rrjetës në regjistra, në mënyrë që të mos lexosh-shkruash në një varg pesë herë. Për fat të keq, Java nuk të ofron një bibliotekë të tillë si pjesë e JDK-së. Por kjo është triviale, apo jo? Në thelb, janë 5-10 rreshta kod që do të shkojnë në realizimin e një loader-i të bufferizuar për vargjet, i cili përsërit sjelljen e klasës string, duke qenë një mbushës rreth bufferit të bytes nënës. Si rezultat, del se po punoni pothuajse sikur të ishit me vargje, por në të vërtetë ka pointers që lëvizin në buffer, dhe bytes e papërpunuara nuk kopjohen askund, kështu që riafroheshin të njëjtat buffer, herë pas here, dhe sistemi operativ është i lumtur të marrë përsipër gjërat, për të cilat është dizajnuar, si bufferizimi i dyfishtë të bytes buffer-ve, dhe ju vetë nuk po përpunoni një rrjedhë të pafund të të dhënave të padobishme. Nga ana tjetër, e dini, kur punoni me GC garanton që çdo alokim memories nuk do të shihet nga procesori pas ciklit të fundit GC? Prandaj, e gjithë kjo nuk mund të jetë në cache, dhe më pas ndodh një humbje 100% e garantuar. Kur punoni me një pointer, në x86 të lexosh një regjistër nga memoria merr 1-2 cikle, dhe sapo ndodh, ju paguani, paguani, paguani, sepse e gjithë memoria në – dhe kjo është kostoja e alokimit të memories. Kostoja e vërtetë.
Në fjalë të tjera, strukturat e të dhënave janë ajo që është më e vështirë për t'u ndryshuar. Dhe sapo kuptoni që keni zgjedhur strukturën e gabuar të të dhënave, e cila më vonë do të dëmtojë performancën, zakonisht kërkon një punë të konsiderueshme për ta rregulluar atë. Por, nëse nuk e bëni këtë, gjërat do të përkeqësohen. Para së gjithash, duhet të mendoni për strukturat e të dhënave, kjo është e rëndësishme. Kostoja kryesore është në strukturat e dhënave të rëndësishme, të cilat fillojnë të përdoren në stilin 'e kam kopjuar strukturën e të dhënave X në strukturën e të dhënave Y, sepse Y më pëlqen më shumë'. Por operacioni i kopjimit (i cili duket i lirë) në të vërtetë shpenzon hapësirë të kujtesës dhe këtu është e gjithë kohës së humbur në ekzekutim. Nëse kam një varg gjigand me JSON dhe dua ta transformoj atë në një pemë DOM të strukturuar nga POJO ose diçka e tillë, operacioni i analizës së këtij vargu dhe ndërtimi i POJO, dhe pastaj një qasje e re në POJO më vonë do të kthehet në kosto shtesë – një gjë që nuk është e lirë. Përveç nëse do të lëvizni mes POJO-ve shumë më shpesh se sa në varg. Përafërsisht, në vend të kësaj mund të përpiqeni të dekriptimit vargun dhe të nxirrni vetëm ato që ju nevojiten, pa e ndërruar në asnjë POJO. Nëse gjithçka ndodh gjatë një procedurë që kërkon maksimumin e performancës, asnjë POJO – duhet të gërmoni drejtpërdrejt në varg.
Pse të krijoni një gjuhë programimi tuajën
Andrei: Ju thatë se për të kuptuar modelin e kostos, nevojitet të shkruani një gjuhë të vogël...
: Kështu është!: Jo një gjuhë, por një kompilator. Gjuha dhe kompilatori janë gjëra të ndryshme. Diferenca kryesore është në mendjen tuaj.
Andrei: Nga ana tjetër, sa di unë, ju eksperimentoni me krijimin e gjuhëve tuaja. Pse?
: Kështu është!: Sepse mundem! Jam gjysmë në pension, kështu që kjo është hobi im. E gjithë jetën kam realizuar gjuhët e të tjerëve. Gjithashtu kam punuar shumë mbi stilin e kodimit. Po ashtu, sepse shoh probleme në gjuhët e tjera. E shoh se ka mënyra më të mira për të bërë gjëra të zakonshme. Dhe do t'i shfrytëzoja ato. Jam mërzitur të shikoj probleme në veten time, në Java, në Python, në çdo gjuhë tjetër. Tani po shkruaj në React Native, JavaScript dhe Elm si një hobi që nuk është për pensionim, por për punë aktive. Po shkruaj edhe në Python dhe me shumë mundësi do të vazhdoj të punoj mbi mësimin e makinerive për Java-backend. Ka shumë gjuhë të njohura dhe të gjitha kanë veçori interesante. Çdo njëra është e mirë për diçka të saj dhe mund të provojmë të përbashkojmë të gjitha këto karakteristika. Kështu që unë merrem me studimin e gjërave interesante për mua, sjelljes së gjuhëve, po përpiqem të shpik një semantikë të arsyeshme. Deri tani po ia dal!
Andrei: Nëse ndiheni të krijoni një gjuhë që merr aspektet më të mira nga gjuhët e tjera, a keni menduar ndonjëherë se dikush do të marrë idetë tuaja dhe do t'i përdorë ato për vete?
: Kështu është!: Kështu lindin gjuhët e reja! Pse Java ngjan me C? Sepse C kishte një sintaksë të mirë që të gjithë e kuptonin dhe Java u frymëzua nga kjo sintaksë, duke i shtuar siguri në tipe, kontrollin e kufijve të matricave, GC, dhe gjithashtu ata përmirësuan disa gjëra nga C. Shtuan të tyre. Por ata u frymëzuan mjaft fort, apo jo? Të gjithë qëndrojnë mbi supet e gjigantëve që ishin para jush – kjo është mënyra si bëhet progresi.
Andrei: Siç e kuptoj, gjuha juaj do të jetë e sigurt në lidhje me përdorimin e kujtesës. A keni menduar të implementoni diçka si kontrolluesin e huazimit nga Rust? A e keni parë, si ju duket?
: Kështu është!: Mirë, kam shkruar në C për një kohë të gjatë, me të gjitha këto malloc dhe free, dhe menaxhoj manualisht kohëzgjatjen e jetës. E dini, 90-95% e jetës së menaxhuar manualisht ka struktura të ngjashme. Dhe është shumë, shumë e dhimbshme ta bësh këtë manualisht. Do të doja që kompilatori të mësonte se çfarë po ndodhte dhe se çfarë keni arritur përmes veprimeve tuaja. Për disa gjëra kontrolluesi i huazimit e bën këtë nga kutia. Gjithashtu, ai duhet të nxjerrë informacionin automatikisht, të kuptojë gjithçka dhe madje të mos më ngarkojë me detyrimin për ta paraqitur atë kuptim. Ai duhet të bëjë të paktën një analizë të shpërndarjes lokale, dhe vetëm nëse nuk ka arritur, atëherë duhet të shtohen anotacione të tipeve që do të përshkruajnë jetën – dhe një skemë e tillë është shumë më e komplikuar sesa kontrolluesi i huazimit ose ndonjë kontrollues tjetër ekzistues të kujtesës. Zgjedhja mes 'të gjitha është në rregull' dhe 'nuk kuptoj asgjë' - jo, duhet të ketë diçka më të mirë.
Prandaj, si një person që ka shkruar shumë kod në C, mendoj se mbështetje për menaxhimin automatik të kohëzgjatjes është gjëja më e rëndësishme. Gjithashtu, më ka bezdisur sa shumë Java përdor memorie dhe ankesa kryesore është te GC. Kur alokoni memorie në Java, nuk e merrni përsëri memorjen që ishte lokale në ciklin e fundit të GC. Në gjuhët me menaxhim më të saktë të memories, kjo nuk ndodh. Nëse thërrisni malloc, ju merrni menjëherë memorjen që zakonisht sapo është përdorur. Zakonisht bëni diçka të përkohshme me memorjen dhe e ktheni menjëherë. Dhe ajo kthehet menjëherë në pool-in e malloc, dhe cikli i ardhshëm i malloc përsëri e nxjerr atë jashtë. Prandaj, përdorimi real i memories reduktohet në një grup objektesh të gjalla në një moment të caktuar, plus rrjedhjet. Dhe nëse nuk keni rrjedhje krejt të turpshme, pjesa më e madhe e memories qëndron në cache dhe procesor, dhe kjo funksionon shpejt. Por kërkon shumë menaxhim manual të memories me malloc dhe free, duke i thërritur në rendin dhe vendin e duhur. Rust mund ta menaxhojë këtë vetë në mënyrë korrekte dhe në shumë raste të ofrojë edhe performancë më të madhe, pasi konsumimi i memories ngushtohet vetëm deri në llogaritjet aktuale – në krahasim me pritjen e ciklit të ardhshëm të GC, i cili do të lirojë memorjen. Në fund, arritëm një mënyrë shumë interesante për të përmirësuar performancën. Dhe mjaft të fuqishme – në kuptimin se kam punuar me këto gjëra në përpunimin e të dhënave për fintech, dhe kjo lejonte të arrihej një shpejtësi rreth pesëfish. Kjo është një rritje e madhe, sidomos në një botë ku procesorët nuk bëhen më të shpejtë, dhe ne ende presim përmirësime.
Karriera e inxhinierit të performancës
Andrei: Po ashtu, do të doja të pyesja për karrierën në tërësi. Ju u bëtë të njohur për punën tuaj në JIT në HotSpot, e më pas u transferuat në Azul – dhe kjo është gjithashtu një kompani JVM. Por tashmë ishit më shumë të angazhuar në harduer sesa në softuer. Pastaj papritur kaluat në Big Data dhe Machine Learning, e më pas në detection e mashtrimeve. Si ndodhi kjo? Këto janë fusha shumë të ndryshme të zhvillimit.
: Kështu është!: Kam punuar në programezim për një kohë të gjatë dhe kam pasur eksperienca shumë të ndryshme. Dhe kur njerëzit thonë: "o, ti je ai që bëri JIT për Java!", kjo gjithmonë është qesharake. Por para kësaj kam punuar mbi një klon të PostScript – gjuhës që Apple dikur e përdorte për printerët e saj laser. Dhe para kësaj kam realizuar gjuhën Forth. Mendoj se tema e përbashkët për mua është zhvillimi i mjeteve. Gjatë gjithë jetës kam bërë mjete që i ndihmojnë të tjerët të shkruajnë programet e tyre të shkëlqyera. Por kam punuar gjithashtu në zhvillimin e sistemeve operative, drejtuesve, debuggers në nivelin e bërthamës, gjuhëve për zhvillimin e OS, që filluan thjesht, por me kalimin e kohës u bënë gjithnjë e më komplekse. Por tema kryesore, përfundimisht, është zhvillimi i mjeteve. Një pjesë e madhe e jetës time kaloi mes Azul dhe Sun, dhe ajo ishte për Java. Por kur fillova të merrem me Big Data dhe Machine Learning, vura përsëri kapelen time të qenies formale dhe thashë: "Ah, tani kemi një problem jo trivial dhe këtu ndodhin shumë gjëra interesante dhe njerëz që bëjnë diçka". Ky është një rrugë e shkëlqyer për t'u zhvilluar, për të cilën vlen të ecësh.
Po, unë e adhuroj shumë përpunimin e shpërndarë. Puna ime e parë ishte në studentim në C, mbi një projekt reklamash. Ishte përpunim i shpërndarë në çipet Zilog Z80, që grumbullonin të dhëna për njohjen optike të teksteve analogjike, të prodhuar nga një analizues të vërtetë analogjik. Kjo ishte një temë e mrekullueshme e papraktikuar. Por kishte probleme, një pjesë nuk njiheshin saktë, prandaj duhej të nxirrja imazhin dhe t'ia tregoja një njeriu që kishte lexuar me sy dhe raportonte se çfarë thoshte, dhe për këtë arsye kishte punë me të dhëna, dhe ato punë kishin gjuhën e tyre. Kishte një backend që e përpunonte këtë – Z80 që punonin paralelisht me terminale vt100 – një për çdo këndvështrim, dhe kishte një model të programimit paralel në Z80. Një copë e përbashkët memorje që e ndanin të gjithë Z80 brenda një konfigurimi tip "yll"; ndanin gjithashtu backplane-in, dhe gjysma e RAM-it ndante brenda rrjetit, dhe gjysma tjetër ishte private ose shkonte për diçka tjetër. Një sistem paralel shpërndarës mjaft kompleks me memorje të përbashkët... pjesërisht të përbashkët. Kur ndodhi kjo... Nuk e mbaj mend, diku në mes të viteve '80. Mjaft krijimi.
Po, le të themi se 30 vjet është mjaft kohë. Problemet që lidhen me përpunimin e shpërndarë ekzistojnë mjaft kohë, njerëzit kanë luftuar me -klastereve. Këto klastere duken si… Për shembull: ka Ethernet dhe x86 tuaj i shpejtë është i lidhur me këtë Ethernet, dhe tani dëshiron të shkosh për një kujtesë të përbashkët false, sepse askush nuk mund të merrej me kodimin e llogaritjeve të shpërndara, kjo ishte shumë e komplikuar dhe kështu kishte kujtesë të përbashkët false me mbrojtjen e faqeve të memories në x86, dhe nëse shkruajte në këtë faqe, ne i thoshim procesorëve të tjerë se nëse ata kanë qasje në të njëjtën kujtesë të përbashkët, ajo duhet të ngarkohet prej teje, dhe kështu u krijua diçka si një protokoll mbështetjeje për koherencën e caches dhe softuer për këtë. Një koncept interesante. Problemi i vërtetë, natyrisht, ishte diçka tjetër. Të gjitha këto funksiononin, por shpejt fillove të kishe probleme me performancën, sepse askush nuk e kuptonte modelin e performancës në një nivel të mjaftueshëm të mirë – çfarë ishin modelet e aksesit në memorie, si t'i bësh që nodet të mos pingonin njëri-tjetrin pafundësisht, e kështu me radhë.
Në H2O kam menduar si ndjek: vetë zhvilluesit janë përgjegjës për të përcaktuar se ku është paralelizmi dhe ku nuk është. Kam shpikur një model kodimi, që të shkruash kod me performancë të lartë është bërë e lehtë dhe e thjeshtë. Por, të shkruash kod që punon ngadalë është e vështirë, do të ketë një pamje të keqe. Duhet të punosh me të vërtetë fort për të shkruar kod të ngadaltë, do të duhet të përdorësh metoda jo standarde. Kodi i ngadalshëm është i dukshëm që në shikimin e parë. Si pasojë, zakonisht shkruhet kod që punon shpejt, por ju duhet të merret me atë se çfarë të bëni në rastin e memories së ndarë. E tërë kjo është e lidhur me grupe të mëdha dhe sjellja atje është si kujtesat e mëdha jo-volatile në Java paralel. Në kuptimin, imagjinoni se dy rrjedha shkruajnë në një grup paralel, njëra fiton, ndërsa tjetra, për pasojë, humbet, dhe nuk e dini kush është kush. Nëse ato nuk janë volatile, atëherë rendi mund të jetë çfarëdo – dhe kjo funksionon me të vërtetë mirë. Njerëzit me të vërtetë kujdesen për rendin e operacioneve, ata vendosin saktë volatile dhe në vendet e duhura presin problemet me performancën e memories. Në rast se jo, ata thjesht do të shkruanin kod në formën e cikleve prej 1 deri në N, ku N është disa triliona, duke shpresuar se të gjitha rastet e komplikuara automatikisht do të bëhen paralel – dhe aty kjo nuk funksionon. Por në H2O kjo nuk është Java, dhe nuk është Scala, mund ta quani këtë 'Java minus minus', nëse dëshironi. Kjo është një stil programimi shumë i kuptueshëm dhe i ngjashëm me atë të shkruarit kod të thjeshtë në C ose Java me cikle dhe grupe. Por, me këtë, mund të trajtosh terabajtë memories. Unë akoma e përdor H2O. Herë pas here e përdor në projekte të ndryshme – dhe akoma është gjëja më e shpejtë, disa herë përpara konkurentëve. Nëse po bëni Big Data me të dhëna kolumne, është shume e vështirë të tejkalosh H2O.
Sfidat teknike
Andrei: Cili ka qenë sfida më e madhe në gjithë karrierën tuaj?
: Kështu është!: A po diskutojmë pjesën teknike apo jo teknikë të çështjes? Do të thoja, sfidat më të mëdha – nuk janë teknike.
Sa i përket sfidave teknike. Unë thjesht i kam mposhtur. Nuk e di as cila ka qenë më e madhja, por kishte disa mjaft interesante, të cilat morën shumë kohë, luftë mendore. Kur shkova në Sun, isha i sigurt se do të bëja një kompajler të shpejtë, ndërsa shumë seniorë në përgjigje thanë se nuk do të kisha asnjëherë sukses. Por unë ndoqa këtë rrugë, shkrova një kompajler deri në alokatorin e regjistrimeve, dhe mjaft të shpejtë. Ai ishte po aq i shpejtë sa kompajleri modern C1, por atëherë alokatori ishte shumë më i ngadaltë, dhe duke e parë gjatë pas, kishte një problem me strukturën e madhe të të dhënave. Më nevojitej për të shkruar një alokator grafik regjistrimesh dhe nuk e kuptoja dilemën midis shprehshmërisë së kodit dhe shpejtësisë, e cila ekzistonte në atë epokë dhe ishte shumë e rëndësishme. Doli se struktura e të dhënave zakonisht tejkalonte madhësinë e caches në x86-të e asaj kohe dhe, kështu, nëse unë fillimisht e supozova se alokatori i regjistrave do të punonte 5-10 përqind të gjithë kohës së JIT-it, në të vërtetë kjo u shndërrua në 50 përqind.
Koha kalonte, kompajleri bëhej gjithnjë e më i qartë dhe më i fuqishëm, ndalonte së gjeneruari kod të neveritshëm në më shumë raste, dhe performanca filloi t'i ngjajë gjithnjë e më shumë asaj që prodhonte kompajleri C. Nëse, sigurisht, nuk shkruan ndonjë gjë të keqe, të cilën as C nuk e përshpejton. Nëse shkruan kod si në C, merr gjithashtu performancë si në C në më shumë raste. Dhe sa më shumë kalonte koha, aq më shpesh kodet që rezultonin ishin asimptotikisht të ngjashme me nivelin e C, alokatori i regjistrave filloi të ngjante me diçka të përfunduar... pavarësisht nëse kodi yt funksiononte shpejt ose ngadalë. Unë vazhdoja të punoja mbi alokatorin, që të bëja alokime më të mira. Ai po bëhej gjithnjë e më i ngadaltë, por jepte performancë gjithnjë e më të mirë në ato raste kur askush tjetër nuk arrinte. Mund të futesha në alokatorin e regjistrave, të hidhja aty një muaj pune, dhe papritur tërë kodi fillonte të ekzekutohej 5% më shpejt. Kjo ndodhte çdo herë dhe alokatori i regjistrave u bë diçka si një vepër arti – të gjithëve u pëlqente ose e urrenin, dhe njerëzit nga akademia bënin pyetje në lidhje me "pse bëhet gjithçka kështu", pse jo , dhe çfarë është dallimi. Përgjigjja është gjithnjë e njëjtë: alokatori i bazuar në ngjyrosjen e grafit plus puna shumë e kujdesshme me kodin mbështetës është e barabartë me armën e fitores, kombinimi më i mirë që askush nuk mund ta mposht. Dhe kjo është një gjë mjaft e padukshme. E gjithë gjëja tjetër që bënte kompajleri është çështje e studiuar mjaft mirë, megjithëse janë të përmirësuara në nivel arti. Unë gjithmonë bëja gjëra që duhej ta kthenin kompajlerin në një vepër arti. Por asgjë nga kjo nuk ishte diçka e jashtëzakonshme – përveç alokatorit të regjistrave. Thelbi është se duhet të përpiqesh nën ngarkesë dhe, nëse ndodh kjo (mund të shpjegoj më shumë, nëse është e interesuar), kjo do të thotë që mund të bësh inline më agresivisht, pa rrezikun e kalimit përtej pikës së thyerjes së performancës. Në ato kohë kishte shumë kompajlerë me përmasë të plotë, të mbushur me gjëra e vegla, që kishin alokatorë regjistrash, por askush nuk arriti më asnjëherë.
Problemi është se, nëse shton metoda të cilat duhet të bëhen inline, duke e rritur dhe zgjeruar zonën e inline-it, grupi i vlerave të përdorura menjëherë tejkalon numrin e regjistrave, dhe duhet të skalon. Nivelin kritik zakonisht e arin kur alokatori dorëzohet, dhe një kandidat i mirë për skalim është më i vlefshëm se tjetri, dhe ti skalon gjëra fare të çudshme. Vlera e inline-it qëndron në faktin se humb një pjesë të overhead-it, overhead-it për thirrje dhe ruajtje, mund të shohësh vlerat brenda dhe mund t'i optimizosh më tej. Kostoja e inline-it është se krijohet një sasi e madhe vlerash aktive, dhe nëse alokatori yt i regjistrave skalon më shumë se sa duhet, do të humbësh menjëherë. Prandaj, shumica e alokatorëve kanë një problem: kur inline-i kalon një farë kufiri, gjithçka fillon të skalojë dhe performanca mund të humbasë. Ata që realizojnë kompajlerin shtojnë disa heuristika: për shembull, të ndalojnë inline-in duke filluar nga një madhësi mjaft të madhe, pasi alokimet do ta prishin gjithçka. Kështu ndodh një pikë e thyerje e kurbës së performancës - ti bësh inline, bësh inline, performanca rritet ngadalë - dhe pastaj bum! – bie me një rënie të shpejtë, sepse ke bërë inline shumë. Kështu kishte funksionuar deri në shfaqjen e Java-s. Java kërkon shumë më tepër inline, kështu që më duhej të bëja alokatorin tim më agresiv, që ai të rregullohej, e të mos binte, dhe nëse bën shumë inline – ai fillon të skalojë, por përsëri vjen momenti "nuk ka më skalim". Ky është një vëzhgim interesant dhe erdhi te unë në mënyrë krejtësisht të papritur, mjaft jo e dukshme, por që u shpërblye mirë. U angazhua me inline-in agresiv dhe kjo më çoi në vende ku performanca e Java-s dhe C-së shkon paralele. Ato janë vërtet të afërta – mund të shkruaj kod në Java që është dukshëm më i shpejtë se kodi në C dhe e ngjashme, por në mesatare, në pamjen e madhe të gjërave, ato janë afërsisht të krahasueshme. Më duket se një pjesë e kësaj merite është alokatori i regjistrave, i cili më lejon të bëj inline në mënyrën më idiote. Unë thjesht bëj inline çdo gjë që shoh. Çështja është nëse alokatori punon mirë, a del kodi i prodhuar që punon si duhet. Ky ishte një sfidë e madhe: të kuptoj të gjithë këtë dhe ta bëj të funksionojë.
Pak për alokimin e regjistrave dhe shumëbërthamerinë
Vladimir: Problemet si alokimi i regjistrave duken si një temë e pafund. A ka ndodhur ndonjëherë që një ide të duket premtuese, por më pas të dështojë në praktikë?
: Kështu është!: Sigurisht! Alokimi i regjistrave është një fushë ku përveç zgjidhjes së një problemi NP-të plot, përpiqesh të gjesh disa heuristic-e. Dhe nuk do të arrish kurrë një zgjidhje të përkryer, apo jo? Kjo është thjesht e pamundur. Shiko, kompilimi Ahead of Time gjithashtu ka rezultate të keqa. Biseda është për raste të zakonshme. Për performancën tipike, mund të shkosh dhe të japësh matje për diçka që e konsideron një performancë të mirë tipike – në fund të fundit, je duke punuar për ta përmirësuar! Alokimi i regjistrave është një temë e plotë kushtuar performancës. Mjafton të kesh një prototip të parë, që funksionon dhe kryen atë që duhet, dhe fillon puna për performancën. Duhet të mësosh të masësh mirë. Pse është kjo e rëndësishme? Nëse ka të dhëna të qarta, mund të shohësh në pjesë të ndryshme dhe të shohësh: ah, kjo ndihmoi këtu, por atje dështoi! Paraqiten disa ide të mira, shton një heuristic të re dhe papritmas gjithçka fillon të funksionojë pak më mirë në mesatare. Ose ndoshta jo. Kam pasur shumë raste, kur ne luftonim për pesë për qind performancë, që ndante zhvillimin tonë nga alokatori i mëparshëm. Dhe çdo herë, duket kështu: një vend fituam, diku tjetër humbëm. Nëse ke mjete të mira për analizën e performancës, mund të gjesh idetë që nuk i bënë mirë dhe të kuptosh pse. Ndoshta ia vlen të lësh gjithçka ashtu siç është, ndoshta vlen të angazhohesh më seriozisht për një tuning më të ndjeshme, ose të shkosh dhe të rregullosh diçka tjetër. Ky është një grup i tërë gjërash! Kam bërë këtë hack të shkëlqyer, por më duhet ky, ky dhe ky – dhe kombinimi i tyre jep përmirësime të caktuara. Dhe përpjekjet individuale mund të dështojnë. Kjo është natyra e punës me problemet NP-të plot.
Vladimir: Duket sikur gjëra si piktura në alokatorë janë një problem i zgjidhur. Po, për ju, duket se ju e keni zgjidhur, kështu që duhet ta bëni...?
: Kështu është!: Ajo nuk është e zgjidhur si e tillë. Ti duhet ta kthesh në 'të zgjidhur'. Ka probleme të vështira dhe ato duhen zgjidhur. Kur kjo është bërë, fillon puna për performancën. Duhet të qaseni ndaj kësaj pune siç duhet – të bëni banchmarke, të mbani metrika, të shpjegoni situatat kur duke u rikthyer në versionin e mëparshëm, hack-i yt i vjetër fillon përsëri të funksionojë (ose përndryshe, ndalon). Dhe të mos hiqni dorë derisa të arrini diçka. Siç kam thënë, idetë e shkëlqyera që nuk funksionuan, por në fushën e alokimit të regjistrave ka ide pothuajse të pafundme. Mund të lexosh, për shembull, publikime shkencore. Ndërkohë që kjo fushë është bërë më e ngadaltë dhe më e qartë se në ditët e saj të rinisë. Megjithatë, në këtë fushë punojnë një pafundësi njerëzish dhe të gjitha idetë e tyre meritojnë të provohen, të gjithë presin orën e tyre. Dhe nuk mund të thuash se sa të mira janë, nëse nuk i provon. Sa mirë integrohen me gjithçka tjetër në alokatorin tënd, sepse alokatori bën shumë gjëra, dhe disa ide nuk do të funksionojnë në alokatorin tënd konkret, ndryshe nga një tjetër. Mënyra kryesore për të fituar për alokatorin është të nxjerrësh gjërat e ngadalta jashtë rrugës kryesore dhe të detyrosh shpërndarjen në kufijtë e rrugëve të ngadalta. Prandaj, nëse do të nisësh GC, të shkosh në rrugën e ngadalte, të deoptimizosh, të hedhësh një përjashtim, gjithçka në atë stil – e di që këto gjëra janë relativisht të rralla. Dhe ata janë me të vërtetë të rrallë, e kam kontrolluar. Bën një punë shtesë dhe për këtë, shumë kufizime në ato rrugë të ngadalta zhduken, por kjo nuk është shumë e rëndësishme, sepse ato janë të ngadalta dhe rrallë përdoren. Për shembull, një pointer zero – ai kurrë nuk ndodh, apo jo? Duhet të kesh disa rrugë për gjëra të ndryshme, por ato nuk duhet të pengojnë në rrugën kryesore.
Vladimir: Çfarë mendoni për multicore-n, kur ka mijëra bërthamë? A është një gjë e dobishme?
: Kështu është!: Suqesi i GPU tregon se është mjaft e dobishme!
Vladimir: Ata janë të specializuar. Po për procesorët e përgjithshëm?
: Kështu është!: Po, kjo ishte modeli i biznesit Azul. Përgjigja erdhi në një epokë kur njerëzit e donin shumë performancën e parashikueshme. Në atë kohë ishte e vështirë të shkruhej kod paralel. Modeli i kodimit H2O përshtatet mirë, por nuk është një model universal. Nëse ka ndonjëherë më shumë universalisht se sa me përdorimin e GPU. Po flasim për vështirësinë e zhvillimit të një gjëje të tillë ose për vështirësinë e përdorimit të saj? Për shembull, një mësim interesant më dha Azul, mjaft i dukshëm: cache-t e vogla janë të pranueshme.
Sfidat më të mëdha në jetën tuaj
Vladimir: Çfarë ndodh me sfidat jo teknike?
: Kështu është!: Sfidat më të mëdha ishin që të mos isha… i mirë dhe i sjellshëm me njerëzit. Dhe si rezultat, unë vazhdimisht përfundova në situata të përplasjes. Situata, ku e dija që çdo gjë ishte duke shkuar keq, por nuk dija se si të përparoja në zgjidhjen e këtyre problemeve dhe nuk mund të merja dot me to. Shumë probleme të ngadalta, që zgjatën me dekada, u shfaqën në këtë mënyrë. Fakt zhvillimin e kompilatorëve C1 dhe C2 në Java është pasojë e drejtpërdrejtë e kësaj. Të mos kishte kompilim me shumë nivele në Java për një dekadë gjithashtu është një pasojë e drejtpërdrejtë. Është e qartë që na duhej një sistem i tillë, por nuk është e qartë pse nuk e kishim atë. Kam pasur probleme me një inxhinier… ose një grup inxhinierësh. Disa kohë më parë, kur fillova të punoja në Sun, isha… Mirë, jo vetëm atëherë, unë gjithmonë kam pasur mendimin tim për çdo gjë. Dhe mendova se ishte e vërtetë që mund ta merrja këtë të vërtetë timen dhe ta thosha hapur. Sidomos, duke qenë se isha befasuese i saktë shumicën e kohës. Dhe nëse ky qasje nuk të pëlqen… veçanërisht nëse je qartë gabim dhe po bën gjëra të kota… Në përgjithësi, pak njerëz mund të toleronin një formë të tillë komunikimi. Megjithatë, disa mundën, për shembull, unë. Kam ndërtuar të gjithë jetën time mbi parimet meritorike. Nëse më tregon diçka që është e gabuar, unë menjëherë do të kthehem dhe do të them: ke thënë një gjë të gabuar. Sigurisht, duke u ndjerë keq, dhe nëse ka ndonjë kontribut, do ta vlerësoj atë dhe do të bëj veprime të tjera të duhura. Në anën tjetër, unë jam befasuese i saktë një përqindje befasuese të përgjithshme të kohës. Dhe kjo nuk funksionon shumë mirë në marrëdhënien me njerëzit. Unë nuk përpiqem të jem i këndshëm, por e shtroj çështjen qartë. 'Kjo ndonjëherë nuk do të funksionojë, sepse një, dy dhe tre'. Dhe ata janë si: 'Oof!'. Pati dhe pasojat e tjera, që ndoshta është më mirë t’i kalojmë: për shembull, ato që çuan në divorcin me gruan time dhe dhjetë vjet depresion pas kësaj.
Sfidat janë lufta me njerëzit, me perceptimin e tyre mbi atë që ti mund ose nuk mund të bësh, çfarë është e rëndësishme dhe çfarë jo. Kishte shumë sfida mbi stilin e kodimit. Unë ende shkruaj shumë kod, dhe në ato kohëra më duhej madje të ngadalësohesha, sepse bëja shumë detyra paralel dhe i bëja ato keq, në vend që të përqendrohesha në një. Tani, duke u kthyer prapa, kam shkruar gjysmën e kodit të ekipit Java JIT, ekipit C2. Krijuesi tjetër më i shpejtë shkruante me gjysmën më të ngadalshëm, i treti – edhe më ngadalë dhe kjo ishte një rënie eksponenciale. Njeriu i shtatë në këtë radhë ishte shumë, shumë ngadalë – kështu ka ndodhur gjithmonë! Kam prekur shumë kod. Kam parë, çfarë shkruan çdo kush, pa përjashtim, kam vëzhguar kodin e tyre, kam rishikuar secilin prej tyre, dhe ende vazhdoja të shkruaja më shumë se ndonjëri prej tyre. Me njerëzit një qasje e tillë nuk funksionon aq mirë. Disa nuk e pëlqejnë atë. Dhe kur ata nuk mund ta përballojnë, fillojnë të lindin ankesat e ndryshme. Për shembull, një herë më thanë të ndalesha së shkruari kod, sepse shkruaja shumë kod dhe kjo vë në rrezik ekipin, dhe për mua e gjithë kjo tingëllonte si një shaka: njeri, nëse e gjithë ekipi tjetër zhduket, dhe unë vazhdoj të shkruaj kod, ti do të humbasësh vetëm gjysmën e ekipit. Nga ana tjetër, nëse vazhdoj të shkruaj kod dhe ti humbasësh gjysmën e ekipit – kjo tingëllon si menaxhim shumë të keq. Nuk kam menduar kurrë për këtë, kurrë nuk kam folur për këtë, por megjithatë ishte diku në mendjen time. Në pjesën e pasme të vetëdijes time, kishte një mendim: ‘A jeni të gjithë në shaka?’. Pra, sfida më e madhe isha unë dhe marrëdhëniet e mia me njerëzit. Tani e kuptoj veten më mirë, kam qenë një lider ekipi për programuesit për një kohë të gjatë, dhe tani u them njerëzve drejtpërdrejt: e di, unë jam ai që jam, dhe do t’ju duhet të përballeni me mua – nuk është problem që unë të qëndroj këtu? Dhe kur ata filluan ta përballonin këtë, gjithçka filloi të funksiononte. Në të vërtetë, unë nuk jam as i keq, as i mirë, nuk kam asnjë qëllim të keq apo ambicie egoiste, kjo është thjesht thelbi im, dhe ndonjëherë duhet të jetosh me të.
Andrei: Kohët e fundit, të gjithë kanë filluar të flasin për vetëdijen për introvertët, dhe në përgjithësi për aftësitë e buta. Çfarë mund të thuhet për këtë?
: Kështu është!Po, ishte një kuptim dhe mësim që e nxora nga zhvillimi me gruan time. Ajo që mësova nga divorci ishte njohja e vetvetes. Kështu fillova të kuptoja njerëzit e tjerë. Të kuptoja se si funksionon ky interaksion. Kjo solli zbulime një pas një. Pashë se kush isha dhe se çfarë përfaqësoja. Ajo që bëj: ose jam i shqetësuar për detyrën, ose shmang konfliktin, ose diçka tjetër – dhe një shkallë e tillë e vetëdijesit me të vërtetë ndihmon për të mbajtur veten nën kontroll. Pas kësaj, gjithçka bëhet më e lehtë. Një gjë që e kam ndjerë jo vetëm tek unë, por edhe tek programues të tjerë – është paaftësia për të shprehur mendimet kur je në një gjendje emocionale stresuese. Për shembull, je duke koduar, je në një gjendje fluksi, dhe papritmas dikush vjen dhe fillon të bërtasë në panik, se diçka ka shkuar keq dhe tani do të merren masa ekstreme ndaj teje. Dhe nuk mund të thuash asnjë fjalë, sepse je në një gjendje emocionale stresuese. Njohuritë e fituara të lejojnë të përgatitesh për këtë moment, ta kalosh dhe të kalosh në një plan tërheqjeje, pas të cilit mund të bësh diçka. Pra, po, kur fillon të kuptosh si funksionon gjithçka – kjo është një ngjarje të madhe që ndryshon jetën.
Unë vetë nuk arrita të gjej fjalët e duhura, por mbajta mend se si të veproj. Thelbi është se kjo reagim është po aq fizik sa është verbale dhe të nevojitet hapësirë. Një hapësirë e tillë, në kuptimin zen. Këtë duhet ta shpjegosh, dhe pastaj të shpërthesh anash – thjesht fizikisht. Kur hesht në fjalë, mund të përpunosh situatën në aspektin e emocioneve. Ndërsa adrenalina arrin në tru, të kalon në modin "godit ose iki", ti tashmë nuk mund të thuash asgjë, jo – tani je idiot, një inxhinier për të prekur, i paaftë për një përgjigje të denjë ose për të ndaluar sulmin, dhe sulmuesi mund të sulmojë përsëri dhe përsëri. Fillimisht duhet të bëhesh përsëri vetvetja, të rikuperosh kontrollin, të dalësh nga moda "godit ose iki".
Dhe për këtë është e nevojshme një hapësirë verbale. Thjesht një hapësirë të lirë. Nëse në të vërtetë do të thuash diçka, mund të theksohesh dhe pastaj të gjesh në të vërtetë "hapësirën": të shkosh për një shëtitje në park, të mbyllesh në dush – nuk ka rëndësi. Këtu është e rëndësishme të çlirohesh përkohësisht nga ajo situatë. Sapo për pak sekonda të shkëputesh, kontrolli rikthehet, fillon të mendosh qartë. "Mirë, nuk jam ndonjë idiot, nuk po bëj gjëra të çmendura, jam një person mjaft i dobishëm". Sapo arritëm të bindim veten, është koha të kalosh në fazën tjetër: të kuptosh se çfarë ndodhi. Të sulmohej, sulmi erdhi nga një vend i papritur, ishte një kurth i pabesë. Kjo është keq. Hapi tjetër është të kuptosh pse sulmuesi kishte nevojë për këtë. Me të vërtetë, përse? Ndoshta sepse ai vetë ishte në zemërim? Pse ai është në zemërim? Ndoshta sepse ai e kishte bërë një gabim dhe nuk mund ta pranojë përgjegjësinë? Kështu duhet të përpunosh të gjithë situatën me kujdes. Por për këtë, të nevojitet një hapësirë për manovrim, hapësirë verbale. Hapi i parë është të prishësh kontaktin verbal. Të largohesh nga diskutimi në fjalë. Të anulosh atë, të ikësh sa më shpejt të jetë e mundur. Nëse është një bisedë telefonike – thjesht vendos telefonin – kjo është një aftësi që kam fituar nga komunikimi me ish-gruan time. Nëse biseda nuk po çon askund, thjesht thuaj "mirupafshim" dhe e hedh telefonin. Nga ana tjetër: "bla-bla-bla", ti përgjigjesh: "po, mirë, deri sa!" dhe e hedh telefonin. Thjesht prish bisedën. Pesë minuta më vonë, kur të kthehet aftësia për të menduar racionalisht, kur t'i kesh ftohur pak, bëhet e mundur të mendosh çfarë ka ndodhur dhe çfarë do ndodhë më pas. Dhe të fillosh të formuloj një përgjigje të menduar, dhe jo thjesht të reagosh emocionalisht. Për mua, një shpërthim në vetëdije erdhi nga fakti se në rastin e stresit emocional, nuk mund të flas. Të dalësh nga ky gjendje, të mendosh dhe të planosh se si të përgjigjesh dhe të kompensosh problemet – këto janë hapat e duhur në rast se nuk mund të flasësh. Mënyra më e thjeshtë është të ikësh nga situata ku shfaqet stresi emocional dhe thjesht të ndalosh së pjesëmarruri në këtë stres. Pas kësaj, fitojnë aftësinë për të menduar, kur mund të mendosh, ndihesh në gjendje të flasësh, etj.
Përveç kësaj, në gjykatë avokati i palës tjetër përpiqet të bëjë këtë me ty – tani është e qartë pse. Sepse ai ka mundësinë të të shtypë deri në një gjendje, që ti nuk mund të thuash as emrin tënd, për shembull. Në kuptimin më të drejtpërdrejtë, nuk mund të flasësh. Nëse kjo të ndodh dhe nëse e di se do të jesh në një vend ku ka luftëra fjalësh, në një vend siç është gjykata, atëherë mund të vish me avokatin tuaj. Avokati do të të mbrojë dhe do të ndalë sulmin verbal, dhe do ta bëjë këtë në një mënyrë plotësisht ligjore, dhe do të rikthesh hapësirën tënde të humbur zen. Për shembull, mua më ndodhi disa herë të duhet të telefonoj familjen, gjykatësi e pranoi këtë me shumë miqësi, por avokati i palës tjetër po thërriste dhe thërriste ndaj meje, nuk mund të përfshihesha në bisedë. Në raste të tilla, më mirë funksionon përdorimi i një ndërmjetësi. Ndërmjetësi ndalon gjithë atë presion që bie mbi ty me një rrjedhë të vazhdueshme, ti gjen hapësirën e nevojshme zen, dhe bashkë me të rikthehet aftësia për të folur. Kjo është një fushë e gjithanshme e njohurive, ku së bashku duhet të mësohet shumë, të zbulohet shumë brenda vetes, dhe e gjithë kjo shndërrohet në vendime strategjike me nivel të lartë, të ndryshme për njerëz të ndryshëm. Disa njerëz nuk e kanë problemin e mësipërm, zakonisht nuk e kanë ata që merren profesionalisht me shitjet. Të gjithë ata që jetojnë me fjalë – këngëtarët e njohur, poetët, liderët fetarë dhe politikanët, ata gjithmonë kanë diçka për të thënë. Ata nuk kanë këto probleme, por unë i kam.
Andrei: Kjo ishte… e papritur. Mirë, ne kemi diskutuar mjaft dhe është koha për të përfunduar këtë intervistë. Patjetër do të takohemi në konferencë dhe mund të vazhdojmë këtë dialog. Shihemi në Hydra!
Mund të vazhdoni komunikimin me Kliffin në konferencën Hydra 2019, e cila do të mbahet më 11-12 korrik 2019 në Shën Petersburg. Ai do të vijë me një referat . Biletat mund të blihen .
Burimi: habr.com
