Одличан интервју са Клифом Кликом, оцем ЈИТ компилације у Јави

Одличан интервју са Клифом Кликом, оцем ЈИТ компилације у ЈавиЦлифф Цлицк — ЦТО компаније Цратус (ИоТ сензори за побољшање процеса), оснивач и суоснивач неколико стартапова (укључујући Роцкет Реалтиме Сцхоол, Неуренсиц и Х2О.аи) са неколико успешних излаза. Клиф је написао свој први компајлер са 15 година (Паскал за ТРС З-80)! Најпознатији је по свом раду на Ц2 у Јави (Море чворова ИР). Овај компајлер је показао свету да ЈИТ може да произведе висококвалитетан код, што је био један од фактора за појаву Јаве као једне од главних модерних софтверских платформи. Затим је Цлифф помогао Азул Системс-у да направи главни рачунар од 864 језгра са чистим Јава софтвером који је подржавао ГЦ паузе на хрпи од 500 гигабајта у року од 10 милисекунди. Генерално, Цлифф је успео да ради на свим аспектима ЈВМ-а.

 
Овај хабрапост је одличан интервју са Клифом. Разговараћемо о следећим темама:

  • Прелазак на оптимизације ниског нивоа
  • Како направити велики рефакторинг
  • Модел трошкова
  • Обука за оптимизацију ниског нивоа
  • Практични примери побољшања перформанси
  • Зашто креирати сопствени програмски језик
  • Каријера инжењера перформанси
  • Технички изазови
  • Мало о расподели регистара и вишејезгри
  • Највећи изазов у ​​животу

Интервју води:

  • Андреј Сатарин са Амазон Веб Сервицес. У својој каријери успео је да ради у потпуно различитим пројектима: тестирао је дистрибуирану базу података НевСКЛ у Иандек-у, систем за детекцију облака у Касперски Лаб-у, игрицу за више играча у Маил.ру и сервис за израчунавање девизних цена у Дојче банци. Заинтересован за тестирање великих позадинских и дистрибуираних система.
  • Владимир Ситников од Нетцрацкер. Десет година рада на перформансама и скалабилности НетЦрацкер ОС-а, софтвера који телеком оператери користе за аутоматизацију процеса управљања мрежом и мрежном опремом. Занимају ме проблеми са перформансама Јава и Орацле базе података. Аутор више десетина побољшања перформанси у званичном ПостгреСКЛ ЈДБЦ драјверу.

Прелазак на оптимизације ниског нивоа

Андрија: Ви сте велико име у свету ЈИТ компилације, Јаве и перформанси уопште, зар не? 

Цлифф: Тако је то!

Андрија: Почнимо са неким општим питањима о перформансама. Шта мислите о избору између оптимизације високог и ниског нивоа као што је рад на нивоу ЦПУ-а?

Цлифф: Да, овде је све једноставно. Најбржи код је онај који никада не ради. Због тога увек треба кренути са високог нивоа, радити на алгоритмима. Боља О нотација ће победити лошију О нотацију, осим ако неке довољно велике константе не интервенишу. Ствари ниског нивоа иду последње. Обично, ако сте довољно добро оптимизовали остатак свог стека и још су неке занимљиве ствари остале, то је низак ниво. Али како почети са високог нивоа? Како знате да је довољно посла на високом нивоу обављено? Па... нема шансе. Нема готових рецепата. Морате да разумете проблем, одлучите шта ћете да радите (како не бисте предузимали непотребне кораке у будућности) и онда можете открити профилер, који може рећи нешто корисно. У неком тренутку и сами схватите да сте се ослободили непотребних ствари и да је време да извршите фино подешавање ниског нивоа. Ово је свакако посебна врста уметности. Много је људи који раде непотребне ствари, али се крећу тако брзо да немају времена да брину о продуктивности. Али то је све док се питање не постави отворено. Обично 99% времена никога није брига шта ја радим, све до тренутка када на критичном путу дође важна ствар за коју нико не брине. И овде сви почињу да вам приговарају „зашто није функционисало савршено од самог почетка“. Генерално, увек постоји нешто што треба побољшати у перформансама. Али 99% времена немате трагове! Само покушавате да нешто успете и у том процесу схватите шта је важно. Никада не можете знати унапред да овај комад мора бити савршен, тако да, у ствари, морате бити савршени у свему. Али ово је немогуће и ви то не радите. Увек има много ствари које треба поправити - и то је сасвим нормално.

Како направити велики рефакторинг

Андрија: Како радите на перформансу? Ово је свеобухватан проблем. На пример, да ли сте икада морали да радите на проблемима који настају услед пресека великог броја постојећих функционалности?

Цлифф: Трудим се да то избегнем. Ако знам да ће перформансе бити проблем, размислим о томе пре него што почнем да кодирам, посебно са структурама података. Али често све ово откријете врло касније. А онда морате ићи на крајње мере и урадити оно што ја зовем „препиши и освоји“: треба да зграбиш довољно велики комад. Неки од кодова ће и даље морати да се преписују због проблема са перформансама или нечег другог. Без обзира на разлог за поновно писање кода, скоро увек је боље преписати већи део него мањи део. У овом тренутку сви почну да се тресу од страха: „Боже, не можеш да додирнеш толико кода!“ Али у ствари, овај приступ скоро увек функционише много боље. Морате одмах да преузмете велики проблем, нацртате велики круг око њега и кажете: Преписаћу све у кругу. Граница је много мања од садржаја унутар ње који треба заменити. И ако вам такво оцртавање граница омогућава да савршено радите унутра, руке су вам слободне, радите шта желите. Једном када схватите проблем, процес поновног писања је много лакши, па узмите велики залогај!
У исто време, када урадите велику преправку и схватите да ће перформансе бити проблем, можете одмах почети да бринете о томе. Ово се обично претвара у једноставне ствари попут „не копирајте податке, управљајте подацима што је једноставније могуће, учините их малим“. У великим преписима постоје стандардни начини за побољшање перформанси. И скоро увек се врте око података.

Модел трошкова

Андрија: У једном од подцаста говорили сте о моделима трошкова у контексту продуктивности. Можете ли објаснити шта сте под овим мислили?

Цлифф: Сигурно. Рођен сам у ери када су перформансе процесора биле изузетно важне. И ова ера се поново враћа - судбина није без ироније. Почео сам да живим у данима осмобитних машина; мој први рачунар је радио са 256 бајтова. Тачно бајтова. Све је било јако мало. Инструкције су морале да се преброје, а како смо почели да се крећемо нагоре у групи програмских језика, језици су добијали све више и више. Постојао је Асемблер, затим Басиц, па Ц, а Ц се побринуо за многе детаље, као што је додела регистара и избор инструкција. Али тамо је све било сасвим јасно, и ако бих направио показивач на инстанцу променљиве, онда бих добио оптерећење, а цена ове инструкције је позната. Хардвер производи одређени број машинских циклуса, тако да се брзина извршавања различитих ствари може израчунати једноставним сабирањем свих инструкција које ћете покренути. Свако поређење/тестирање/филијала/позив/учитавање/продавница може се сабрати и рећи: то је време извршења за вас. Када радите на побољшању перформанси, свакако ћете обратити пажњу на то који бројеви одговарају малим врућим циклусима. 
Али чим пређете на Јаву, Питхон и сличне ствари, врло брзо се удаљите од хардвера ниског нивоа. Колика је цена позивања геттера у Јави? Ако је ЈИТ у ХотСпот-у исправан инлинед, учитаће се, али ако то није урадио, биће то позив функције. Пошто је позив у врућој петљи, он ће надјачати све остале оптимизације у тој петљи. Стога ће стварни трошак бити много већи. И одмах губите могућност да погледате део кода и схватите да би требало да га извршимо у смислу брзине процесора, меморије и кеша који се користи. Све ово постаје занимљиво само ако заиста уђете у представу.
Сада смо се нашли у ситуацији да се брзине процесора једва повећавају деценију. Враћају се стари дани! Више не можете рачунати на добре перформансе са једним навојем. Али ако изненада уђете у паралелно рачунарство, то је невероватно тешко, сви вас гледају као Џејмса Бонда. Десетострука убрзања се овде обично дешавају на местима где је неко нешто забрљао. Конкуренција захтева много рада. Да бисте добили то XNUMXк убрзање, морате разумети модел трошкова. Шта и колико кошта? А да бисте то урадили, морате разумети како се језик уклапа на основни хардвер.
Мартин Томпсон је одабрао сјајну реч за свој блог Мецханицал Симпатхи! Морате да разумете шта ће хардвер да ради, како ће то тачно урадити и зашто уопште ради то што ради. Користећи ово, прилично је лако започети бројање инструкција и схватити куда иде време извршења. Ако немате одговарајућу обуку, само тражите црну мачку у мрачној соби. Видим људе који све време оптимизују перформансе који немају појма шта дођавола раде. Много пате и не напредују много. А када узмем исти део кода, убацим неколико малих хакова и добијем пет или десетоструко убрзање, они су као: па, то није фер, већ смо знали да си бољи. Невероватно. О чему причам... модел трошкова је о томе какав код пишете и колико брзо ради у просеку у великој слици.

Андрија: А како можеш задржати толики волумен у глави? Да ли се то постиже са више искуства или? Откуд такво искуство?

Цлифф: Па, нисам на најлакши начин стекао своје искуство. Програмирао сам у асемблеру у време када сте могли да разумете сваку инструкцију. Звучи глупо, али од тада је З80 сет инструкција заувек остао у мојој глави, у сећању. Не сећам се имена људи у року од једног минута разговора, али се сећам шифре написане пре 40 година. Смешно је, личи на синдром"идиот научник'.

Обука за оптимизацију ниског нивоа

Андрија: Постоји ли лакши начин да уђете?

Цлифф: Да и не. Хардвер који сви користимо није се толико променио током времена. Сви користе к86, са изузетком Арм паметних телефона. Ако не радите неку врсту хардцоре ембеддинга, радите исту ствар. Ок, иди даље. Упутства се такође нису мењала вековима. Треба отићи и написати нешто у Скупштини. Не много, али довољно да почнете да разумете. Ви се смејете, али ја говорим потпуно озбиљно. Морате разумети кореспонденцију између језика и хардвера. После тога треба да одете и мало пишете и направите мали компајлер играчака за мали језик играчака. Слично играчки значи да га треба направити у разумном времену. Може бити супер једноставно, али мора да генерише упутства. Чин генерисања инструкције ће вам помоћи да разумете модел трошкова за мост између кода високог нивоа који сви пишу и машинског кода који ради на хардверу. Ова преписка ће бити спаљена у мозак у тренутку када компајлер буде написан. Чак и најједноставнији компајлер. Након тога можете почети да гледате на Јаву и чињеницу да је њен семантички понор много дубљи и да је много теже градити мостове преко ње. У Јави је много теже разумети да ли је наш мост испао добар или лош, шта ће довести до његовог распада, а шта не. Али потребна вам је нека почетна тачка на којој гледате код и разумете: „да, овај геттер треба да буде уметнут сваки пут.“ А онда се испостави да се то понекад дешава, осим у ситуацији када метода постане превелика, а ЈИТ почне све да умеће. Учинак таквих места може се предвидети одмах. Обично геттери добро функционишу, али онда погледате велике вруће петље и схватите да постоје неки позиви функција који плутају около и не знају шта раде. Ово је проблем са широко распрострањеном употребом геттера, разлог зашто они нису уметнути је тај што није јасно да ли су геттер. Ако имате супер малу базу кода, можете је једноставно запамтити и онда рећи: ово је геттер, а ово је сеттер. У великој бази кода, свака функција живи своју историју, која, генерално, никоме није позната. Профилер каже да смо изгубили 24% времена на некој петљи и да бисмо разумели шта ова петља ради, треба да погледамо сваку функцију унутра. Ово је немогуће разумети без проучавања функције, а то озбиљно успорава процес разумевања. Зато не користим гетере и сетере, достигао сам нови ниво!
Где добити модел трошкова? Па, можете прочитати нешто, наравно... Али мислим да је најбољи начин да се понашате. Прављење малог компајлера биће најбољи начин да разумете модел трошкова и уклопите га у своју главу. Мали компајлер који би био погодан за програмирање микроталасне пећнице је задатак за почетника. Па, мислим, ако већ имате вештине програмирања, онда би то требало да буде довољно. Све ове ствари као што је рашчлањивање стринга који имате као нека врста алгебарског израза, извлачење инструкција за математичке операције одатле у исправном редоследу, узимање тачних вредности из регистара - све се то ради одједном. И док то радите, то ће бити утиснуто у ваш мозак. Мислим да сви знају шта ради компајлер. И ово ће дати разумевање модела трошкова.

Практични примери побољшања перформанси

Андрија: На шта још треба обратити пажњу када радите на продуктивности?

Цлифф: Структуре података. Иначе, да, дуго нисам предавао ове часове... Роцкет Сцхоол. Било је забавно, али је захтевало много труда, а имам и живот! ОК. Дакле, на једном од великих и занимљивих часова, „Где иде ваш учинак“, дао сам ученицима пример: два и по гигабајта финтецх података су прочитани из ЦСВ датотеке и онда су морали да израчунају број продатих производа . Редовни подаци о тржишту крпеља. УДП пакети конвертовани у текстуални формат од 70-их година. Чикашка трговачка берза - све врсте ствари попут путера, кукуруза, соје, итд. Требало је пребројати ове производе, број трансакција, просечан обим кретања средстава и робе итд. То је прилично једноставна математика трговања: пронађите код производа (то је 1-2 знака у хеш табели), узмите износ, додајте га у један од трговачких скупова, додајте обим, додајте вредност и још пар ствари. Врло једноставна математика. Имплементација играчке је била врло једноставна: све је у датотеци, читам датотеку и крећем се кроз њу, делим појединачне записе на Јава низове, тражим у њима потребне ствари и сабирем их према горе описаној математици. И ради на малој брзини.

Са овим приступом, очигледно је шта се дешава, а паралелно рачунарство неће помоћи, зар не? Испоставило се да се петоструко повећање перформанси може постићи једноставним одабиром правих структура података. А ово изненађује чак и искусне програмере! У мом конкретном случају, трик је био у томе што не би требало да вршите доделу меморије у врућој петљи. Па, ово није цела истина, али уопштено - не треба да истичете „једном у Кс“ када је Кс довољно велик. Када је Кс два и по гигабајта, не би требало да додељујете ништа „једном по слову“, или „једном по реду“, или „једном по пољу“, било шта слично. Овде се троши време. Како ово уопште функционише? Замислите да зовем String.split() или BufferedReader.readLine(). Readline прави стринг од скупа бајтова који су дошли преко мреже, једном за сваки ред, за сваку од стотина милиона линија. Узимам овај ред, рашчланим га и бацим. Зашто га бацам - па, већ сам га обрадио, то је све. Дакле, за сваки бајт прочитан са ових 2.7Г у реду ће бити уписана два знака, односно већ 5.4Г, и не требају ми ни за шта даље, па се бацају. Ако погледате пропусни опсег меморије, учитавамо 2.7Г који пролази кроз меморију и меморијску магистралу у процесору, а затим се дупло више шаље на линију која лежи у меморији и све се то распада када се креира свака нова линија. Али морам то да прочитам, хардвер то чита, чак и ако је све касније похабано. И морам то да запишем јер сам направио линију и кешови су пуни - кеш не може да прими 2.7Г. Дакле, за сваки бајт који прочитам, прочитам још два бајта и запишем још два бајта, и на крају они имају однос 4:1 – у овом односу губимо меморијски пропусни опсег. А онда се испостави да ако то урадим String.split() – ово није последњи пут да ово радим, можда има још 6-7 поља унутра. Дакле, класични код читања ЦСВ-а и затим рашчлањивања стрингова резултира губитком меморијског пропусног опсега од око 14:1 у односу на оно што бисте заправо желели да имате. Ако одбаците ове селекције, можете добити петоструко убрзање.

И није тако тешко. Ако погледате код из правог угла, све постаје прилично једноставно када схватите проблем. Не би требало да престанете да додељујете меморију у потпуности: једини проблем је што нешто доделите и оно одмах умре, а успут сагорева важан ресурс, а то је у овом случају меморијски пропусни опсег. А све то резултира падом продуктивности. На к86 обично морате активно да снимате циклусе процесора, али овде сте спалили сву меморију много раније. Решење је смањење количине пражњења. 
Други део проблема је у томе што ако покренете профилер када понестане меморијске траке, тачно када се то догоди, обично чекате да се кеш врати јер је пун смећа које сте управо произвели, све те линије. Стога, свака операција учитавања или складиштења постаје спора, јер доводе до промашаја кеша - цео кеш је постао спор, чекајући да га смеће напусти. Због тога ће профилер само показати топли насумични шум размазан кроз читаву петљу - неће бити одвојене вруће инструкције или места у коду. Само бука. А ако погледате ГЦ циклусе, сви су они Млада генерација и супер брзи - максимално микросекунде или милисекунде. На крају крајева, све ово сећање умире истог тренутка. Ви доделите милијарде гигабајта, а он их сече, сече и поново сече. Све се ово дешава веома брзо. Испоставило се да постоје јефтини ГЦ циклуси, топла бука дуж целог циклуса, али желимо да добијемо 5к убрзање. У овом тренутку, нешто би требало да вам се затвори у глави и звучи: „Зашто је ово?!” Преливање меморијске траке се не приказује у класичном програму за отклањање грешака; потребно је да покренете дебагер бројача хардверских перформанси и видите га сами и директно. Али ово се не може директно посумњати из ова три симптома. Трећи симптом је када погледате шта истичете, питате профилера, а он вам одговара: „Направили сте милијарду редова, али ГЦ је радио бесплатно. Чим се то догоди, схватате да сте креирали превише објеката и спалили целу траку меморије. Постоји начин да се ово открије, али није очигледно. 

Проблем је у структури података: гола структура која лежи у основи свега што се дешава, превелика је, има 2.7Г на диску, тако да је прављење копије ове ствари веома непожељно - желите да је одмах учитате из бафера бајтова мреже у регистре, како не би пет пута читао-писао у ред напред-назад. Нажалост, Јава вам подразумевано не даје такву библиотеку као део ЈДК-а. Али ово је тривијално, зар не? У суштини, ово је 5-10 линија кода који ће се користити за имплементацију вашег сопственог баферованог учитавача стрингова, који понавља понашање класе стрингова, док је омотач бафера бајтова. Као резултат тога, испоставља се да радите скоро као са стринговима, али у ствари се показивачи на бафер померају тамо, а сирови бајтови се нигде не копирају, па се исти бафери поново користе изнова и изнова, и оперативни систем радо преузима на себе ствари за које је дизајниран, као што је скривено двоструко баферовање ових бафера бајтова, а ви више не пролазите кроз бескрајни ток непотребних података. Узгред, да ли разумете да када радите са ГЦ-ом, гарантовано је да свака додела меморије неће бити видљива процесору након последњег ГЦ циклуса? Дакле, све ово никако не може бити у кешу, а онда долази до 100% загарантованог промашаја. Када радите са показивачем, на к86, одузимање регистра из меморије траје 1-2 такта, а чим се то деси, плаћате, плаћате, плаћате, јер је меморија сва укључена ДЕВЕТ кеш меморије – а ово је трошак алокације меморије. Права вредност.

Другим речима, структуру података је најтеже променити. А када једном схватите да сте изабрали погрешну структуру података која ће касније убити перформансе, обично има много посла који треба да се уради, али ако то не учините, ствари ће се погоршати. Пре свега, морате размишљати о структурама података, ово је важно. Главни трошак овде пада на масне структуре података, које почињу да се користе у стилу „Копирао сам структуру података Кс у структуру података И јер ми се више свиђа облик И“. Али операција копирања (која изгледа јефтина) заправо губи пропусни опсег меморије и ту је закопано сво изгубљено време извршења. Ако имам огроман низ ЈСОН-а и желим да га претворим у структурирано ДОМ стабло ПОЈО-а или тако нешто, операција рашчлањивања тог стринга и прављења ПОЈО-а, а затим поновног приступа ПОЈО-у касније, резултираће непотребним трошковима - то је није јефтин. Осим ако трчиш око ПОЈО-а много чешће него што трчиш око низа. Насумично, уместо тога можете покушати да дешифрујете стринг и извучете само оно што вам је потребно, а да га не претварате у ПОЈО. Ако се све ово дешава на путу са којег се захтевају максималне перформансе, без ПОЈО-а за вас, морате некако да копате директно у линију.

Зашто креирати сопствени програмски језик

Андрија: Рекли сте да да бисте разумели модел трошкова, морате написати свој мали језик...

Цлифф: Није језик, већ компајлер. Језик и компајлер су две различите ствари. Најважнија разлика је у вашој глави. 

Андрија: Иначе, колико ја знам, ви експериментишете са стварањем сопствених језика. За шта?

Цлифф: Зато што могу! Ја сам полу-пензионер, па ми је ово хоби. Целог живота примењујем туђе језике. Такође сам много радио на свом стилу кодирања. И зато што видим проблеме на другим језицима. Видим да постоје бољи начини да се раде познате ствари. И ја бих их користио. Уморан сам да видим проблеме у себи, у Јави, у Пајтону, на било ком другом језику. Сада пишем у Реацт Нативе, ЈаваСцрипт и Елм као хоби који се не односи на пензију, већ на активан рад. Такође пишем у Питхон-у и највероватније ћу наставити да радим на машинском учењу за Јава позадинске програме. Постоји много популарних језика и сви имају занимљиве карактеристике. Свако је добар на свој начин и можете покушати да спојите све ове карактеристике. Дакле, проучавам ствари које ме занимају, понашање језика, покушавам да смислим разумну семантику. И за сада успевам! Тренутно се борим са семантиком меморије, јер желим да је имам као у Ц и Јави, и да добијем јак модел меморије и семантику меморије за учитавање и складиштење. У исто време, имајте аутоматско закључивање типа као у Хаскелл-у. Овде покушавам да помешам закључивање типа налик Хаскелл-у са радом на меморији и у Ц и у Јави. То је оно чиме се ја, на пример, бавим последња 2-3 месеца.

Андрија: Ако изградите језик који преузима боље аспекте од других језика, да ли мислите да ће неко учинити супротно: узети ваше идеје и користити их?

Цлифф: Управо тако се појављују нови језици! Зашто је Јава слична Ц? Зато што је Ц имао добру синтаксу коју су сви разумели и Јава је била инспирисана овом синтаксом, додајући безбедност типова, проверу граница низа, ГЦ, а такође су побољшали неке ствари из Ц-а. Додали су своје. Али били су доста инспирисани, зар не? Сви стају на рамена великана који су дошли пре вас – тако се напредује.

Андрија: Колико сам разумео, ваш језик ће бити безбедан у меморији. Да ли сте размишљали о имплементацији нечег попут провере позајмице од Руста? Да ли сте га погледали, шта мислите о њему?

Цлифф: Па, писао сам Ц годинама, са свим овим маллоц и бесплатним, и ручним управљањем животним веком. Знате, 90-95% ручно контролисаног животног века има исту структуру. И веома је, веома болно то радити ручно. Желео бих да вам компајлер једноставно каже шта се тамо дешава и шта сте постигли својим поступцима. За неке ствари, провера за позајмљивање то ради ван кутије. И требало би аутоматски да прикаже информације, да разуме све, а не да ме оптерећује изношењем овог разумевања. Мора да уради барем локалну есцапе анализу, а само ако не успе, онда треба да дода напомене типа које ће описати животни век – а таква шема је много сложенија од провере позајмљивања или било које постојеће провере меморије. Избор између „све је у реду“ и „ништа не разумем“ - не, мора да постоји нешто боље. 
Дакле, као неко ко је написао много кода у Ц-у, мислим да је подршка за аутоматску контролу животног века најважнија ствар. Такође ми је доста колико Јава користи меморију, а главна замерка је ГЦ. Када доделите меморију у Јави, нећете добити назад меморију која је била локална у последњем ГЦ циклусу. Ово није случај у језицима са прецизнијим управљањем меморијом. Ако позовете маллоц, одмах добијате меморију која се обично управо користила. Обично радите неке привремене ствари са меморијом и одмах је вратите назад. И одмах се враћа у маллоц базен, а следећи маллоц циклус га поново извлачи. Због тога је стварна употреба меморије сведена на скуп живих објеката у датом тренутку, плус цурење. А ако све не процури на потпуно непристојан начин, већина меморије завршава у кеш меморији и процесору и ради брзо. Али захтева много ручног управљања меморијом са маллоц-ом и бесплатним позивима у правом редоследу, на правом месту. Руст може сам да се носи са овим како треба, иу многим случајевима даје још боље перформансе, пошто је потрошња меморије сужена само на тренутно израчунавање - за разлику од чекања на следећи ГЦ циклус да би се ослободила меморија. Као резултат тога, добили смо веома интересантан начин да побољшамо перформансе. И прилично моћан - мислим, радио сам такве ствари приликом обраде података за финтецх, и то ми је омогућило да убрзам око пет пута. То је прилично велико повећање, посебно у свету где процесори не постају бржи и још увек чекамо побољшања.

Каријера инжењера перформанси

Андрија: Хтео бих да вас питам и о вашој каријери уопште. Истакли сте се својим ЈИТ радом у ХотСпот-у, а затим прешли у Азул, који је такође ЈВМ компанија. Али већ смо радили више на хардверу него на софтверу. А онда су одједном прешли на велике податке и машинско учење, а затим на откривање превара. Како се то догодило? То су веома различите области развоја.

Цлифф: Програмирам већ неко време и успео сам да похађам много различитих часова. А када људи кажу, "ох, ти си тај који је урадио ЈИТ за Јаву!", увек је смешно. Али пре тога, радио сам на клону ПостСцрипт-а - језика који је Аппле некада користио за своје ласерске штампаче. А пре тога сам урадио имплементацију Фортх језика. Мислим да је заједничка тема за мене развој алата. Целог живота сам правио алате помоћу којих други људи пишу своје цоол програме. Али такође сам био укључен у развој оперативних система, драјвера, отклањања грешака на нивоу кернела, језика за развој ОС, који је почео тривијално, али је временом постајао све сложенији. Али главна тема је и даље развој алата. Велики део мог живота прошао је између Азула и Сунца, а радило се о Јави. Али када сам почео да се бавим великим подацима и машинским учењем, поново сам ставио свој фенси шешир и рекао: „Ох, сада имамо не-тривијалан проблем, и дешава се много занимљивих ствари и људи раде ствари.“ Ово је одличан развојни пут који треба ићи.

Да, стварно волим дистрибуирано рачунарство. Мој први посао је био као студент у Ц, на рекламном пројекту. Ово је дистрибуирано рачунарством на Зилог З80 чиповима који су прикупљали податке за аналогни ОЦР, произведен од правог аналогног анализатора. Била је то кул и потпуно луда тема. Али било је проблема, неки део није препознат како треба, па сте морали да извадите слику и покажете је особи која је већ могла очима да чита и да јави шта пише, па је било послова са подацима и ови послови имали свој језик. Постојао је бацкенд који је све ово обрађивао - З80-и раде паралелно са вт100 терминалима који раде - један по особи, а постојао је и модел паралелног програмирања на З80. Неки заједнички део меморије који деле сви З80 у оквиру звездасте конфигурације; Задња плоча је такође била дељена, а половина РАМ-а је дељена унутар мреже, а друга половина је била приватна или је отишла на нешто друго. Смислено сложен паралелни дистрибуирани систем са заједничком... полудељеном меморијом. Када је то било... Не могу ни да се сетим, негде средином 80-их. Прилично давно. 
Да, претпоставимо да је 30 година доста давно. Проблеми везани за дистрибуирано рачунарство постоје доста дуго; људи су дуго били у рату са Беовулф-кластери. Такви кластери изгледају као... На пример: постоји Етернет и ваш брзи к86 је повезан на овај Етернет, а сада желите да добијете лажну заједничку меморију, јер тада нико није могао да ради дистрибуирано рачунарско кодирање, било је претешко и стога постоји је била лажна дељена меморија са заштитним меморијским страницама на к86, и ако сте писали на ову страницу, онда смо рекли другим процесорима да ако приступе истој дељеној меморији, мораће да се учита од вас, а самим тим и нешто попут протокола за подршку појавио се кеш кохерентност и софтвер за ово. Занимљив концепт. Прави проблем је, наравно, било нешто друго. Све је ово функционисало, али сте брзо добили проблеме са перформансама, јер нико није разумео моделе перформанси на довољно добром нивоу – који обрасци приступа меморији су постојали, како се уверити да чворови не пингују једни друге и тако даље.

Оно што сам смислио у Х2О је да су сами програмери одговорни за одређивање где је паралелизам скривен, а где није. Смислио сам модел кодирања који је учинио писање кода високих перформанси лаким и једноставним. Али писање кода који се споро покреће је тешко, изгледаће лоше. Морате озбиљно покушати да напишете спор код, мораћете да користите нестандардне методе. Шифра кочења је видљива на први поглед. Као резултат тога, обично пишете код који брзо ради, али морате да схватите шта да радите у случају заједничке меморије. Све ово је везано за велике низове и понашање тамо је слично непроменљивим великим низовима у паралелној Јави. Мислим, замислите да две нити пишу у паралелни низ, једна од њих побеђује, а друга, сходно томе, губи, а ви не знате која је која. Ако нису променљиви, онда редослед може бити какав год желите - и ово функционише заиста добро. Људима је заиста стало до редоследа операција, постављају волатиле на права места и очекују проблеме са перформансама у вези са меморијом на правим местима. У супротном, они би једноставно написали код у облику петљи од 1 до Н, где је Н неколико трилиона, у нади да ће сви сложени случајеви аутоматски постати паралелни - а то тамо не функционише. Али у Х2О ово није ни Јава ни Сцала; можете то сматрати „Јава минус минус“ ако желите. Ово је веома јасан стил програмирања и сличан је писању једноставног Ц или Јава кода са петљама и низовима. Али у исто време, меморија се може обрадити у терабајтима. Још увек користим Х2О. Користим га с времена на време у различитим пројектима - и даље је најбржа ствар, десетине пута бржа од својих конкурената. Ако радите велике податке са колонским подацима, веома је тешко победити Х2О.

Технички изазови

Андрија: Шта вам је био највећи изазов у ​​читавој каријери?

Цлифф: Да ли разговарамо о техничком или нетехничком делу питања? Рекао бих да највећи изазови нису технички. 
Што се тиче техничких изазова. Једноставно сам их победио. Не знам ни која је била највећа, али било је неких прилично занимљивих за које је требало доста времена, менталне борбе. Када сам отишао у Сун, био сам сигуран да ћу направити брз компајлер, а гомила старијих је одговорила да никада нећу успети. Али ја сам следио овај пут, записао компајлер у алокатор регистара, и било је прилично брзо. Био је брз као модерни Ц1, али алокатор је тада био много спорији, а гледајући уназад, то је био велики проблем са структуром података. Требао ми је да напишем графички алокатор регистара и нисам разумео дилему између експресивности кода и брзине, која је постојала у то доба и била је веома важна. Испоставило се да структура података обично премашује величину кеша на к86с тог времена, и стога, ако сам у почетку претпоставио да ће алокатор регистра радити 5-10 процената укупног времена подрхтавања, онда се у стварности испоставило да је 50 посто.

Како је време одмицало, компајлер је постајао чистији и ефикаснији, престао је да генерише ужасан код у више случајева, а перформансе су све више почеле да личе на оно што компајлер Ц производи. Осим ако, наравно, не напишете неко срање које чак ни Ц не убрзава . Ако пишете код као што је Ц, добићете перформансе као Ц у више случајева. И што сте даље ишли, чешће сте добијали код који се асимптотски поклапао са нивоом Ц, алокатор регистра је почео да изгледа као нешто потпуно... без обзира да ли ваш код ради брзо или споро. Наставио сам да радим на алокатору да би направио боље селекције. Постајао је све спорији, али је давао све боље резултате у случајевима када нико други није могао да се носи. Могао бих да зароним у алокатор регистара, закопам тамо месец дана рада и одједном би цео код почео да се извршава 5% брже. То се дешавало с времена на време и алокатор регистара је постао уметничко дело – сви су га волели или мрзели, а људи са академије су постављали питања на тему „зашто се све ради на овај начин“, зашто не линијско скенирање, и која је разлика. Одговор је и даље исти: алокатор заснован на бојењу графа плус веома пажљив рад са кодом бафера једнак је оружју победе, најбољој комбинацији коју нико не може да победи. А ово је прилично неочигледна ствар. Све остало што састављач тамо ради су прилично добро проучене ствари, мада су и доведене на ниво уметности. Увек сам радио ствари које су требале да претворе компајлер у уметничко дело. Али ништа од овога није било ништа необично - осим алокатора регистра. Трик је у томе да будете пажљиви смањити под оптерећењем и, ако се то деси (могу да објасним детаљније ако сте заинтересовани), то значи да можете агресивније да уграђујете, без ризика да паднете преко кикса у распореду перформанси. У то време, постојала је гомила компајлера у пуној величини, окачених куглицама и звиждаљкама, који су имали алокаторе регистара, али нико други то није могао да уради.

Проблем је у томе што ако додате методе које подлежу уметању, повећању и повећању површине уметања, скуп коришћених вредности тренутно надмашује број регистара и морате да их исечете. Критични ниво обично долази када алокатор одустане, а један добар кандидат за изливање вреди другог, продаћете неке генерално дивље ствари. Вредност уметања овде је у томе што губите део режијских трошкова, режијских трошкова за позивање и чување, можете видети вредности унутра и можете их даље оптимизовати. Цена уметања је да се формира велики број живих вредности, а ако ваш алокатор регистра сагоре више него што је потребно, одмах губите. Стога, већина алокатора има проблем: када уметање пређе одређену линију, све на свету почиње да се смањује и продуктивност може да се испусти у тоалет. Они који имплементирају компајлер додају неке хеуристике: на пример, да престану са уметањем, почевши од неке довољно велике величине, пошто ће алокације све упропастити. Овако се формира кинк у графу перформанси - ти инлине, инлине, перформансе полако расту - а онда бум! – пада као брзи џак зато што си превише нанизао. Овако је све функционисало пре појаве Јаве. Јава захтева много више уметања, тако да сам морао да учиним свој алокатор много агресивнијим тако да се изједначи, а не да се сруши, и ако уметнете превише, почиње да се прелива, али онда и даље долази тренутак „нема више проливања“. Ово је занимљиво запажање и дошло ми је ниоткуда, није очигледно, али се добро исплатило. Започео сам агресивно уметање и то ме је одвело на места где перформансе Јава и Ц раде раме уз раме. Они су заиста блиски – могу да напишем Јава код који је знатно бржи од Ц кода и сличних ствари, али у просеку, у великој слици ствари, они су отприлике упоредиви. Мислим да је део ове заслуге алокатор регистра, који ми омогућава да уградим што је глупље могуће. Само стављам све што видим. Овде се поставља питање да ли алокатор добро ради, да ли је резултат интелигентно радни код. Ово је био велики изазов: разумети све ово и учинити да то функционише.

Мало о расподели регистара и вишејезгри

Владимир: Проблеми попут алокације регистара изгледају као нека врста вечне, бескрајне теме. Питам се да ли је икада постојала идеја која је изгледала обећавајуће, а затим пропала у пракси?

Цлифф: Сигурно! Алокација регистра је област у којој покушавате да пронађете неке хеуристике да бисте решили НП-комплетан проблем. И никада не можете постићи савршено решење, зар не? Ово је једноставно немогуће. Погледајте, компилација испред времена - такође лоше ради. Овде се говори о неким просечним случајевима. О типичним перформансама, тако да можете да одете и измерите нешто за шта мислите да је добар типичан учинак - на крају крајева, радите на томе да га побољшате! Додела регистара је тема која се односи на перформансе. Када имате први прототип, он ради и слика оно што је потребно, почиње рад на извођењу. Морате научити да добро мерите. Зашто је важно? Ако имате јасне податке, можете погледати различите области и видети: да, овде је помогло, али ту је све пукло! Дођу неке добре идеје, додаш нове хеуристике и одједном све почне да ради мало боље у просеку. Или не почиње. Имао сам гомилу случајева у којима смо се борили за учинак од пет процената који је разликовао наш развој од претходног алокатора. И сваки пут то изгледа овако: негде победиш, негде изгубиш. Ако имате добре алате за анализу учинка, можете пронаћи идеје које губе и разумети зашто не успевају. Можда је вредно оставити све како јесте, или можда озбиљније приступити фином подешавању, или отићи и поправити нешто друго. То је читава гомила ствари! Направио сам овај кул хак, али ми треба и овај, и овај, и овај - и њихова укупна комбинација даје нека побољшања. И усамљеници могу пропасти. Ово је природа рада перформанси на НП-комплетним проблемима.

Владимир: Стиче се осећај да су ствари попут сликања у алокаторима проблем који је већ решен. Па, одлучено је за тебе, судећи по ономе што причаш, па да ли се онда уопште исплати...

Цлифф: Није решено као такво. Ви сте ти који то морате претворити у „решено“. Постоје тешки проблеми и треба их решити. Када се ово уради, време је да се поради на продуктивности. Овом послу треба да приступите у складу са тим – урадите бенцхмаркове, прикупите метрику, објасните ситуације када је, када сте се вратили на претходну верзију, ваш стари хак поново почео да ради (или обрнуто, престао). И не одустајте док нешто не постигнете. Као што сам већ рекао, ако постоје цоол идеје које нису функционисале, али у области алокације регистара идеја то је отприлике бескрајно. Можете, на пример, читати научне публикације. Иако је сада ово подручје почело да се креће много спорије и постало је јасније него у младости. Међутим, безброј је људи који раде на овом пољу и све њихове идеје вреди испробати, сви чекају у својим рукама. И не можете рећи колико су добри осим ако их не пробате. Колико се добро интегришу са свим осталим у вашем алокатору, јер алокатор ради много ствари, а неке идеје у вашем специфичном алокатору неће радити, али у другом алокатору ће лако. Главни начин да се победи алокатор је да се споре ствари извуку ван главне путање и натерају да се раздвоје дуж граница спорих путања. Дакле, ако желите да покренете ГЦ, идите спорим путем, деоптимизујте, избаците изузетак, све те ствари - знате да су ове ствари релативно ретке. И заиста су ретки, проверио сам. Радите додатни посао и то уклања многа ограничења на овим спорим стазама, али то заправо није важно јер су споре и ретко се путују. На пример, нулти показивач - то се никада не дешава, зар не? За различите ствари морате имати неколико путева, али они не би требало да ометају главни. 

Владимир: Шта мислите о више језгара, када постоје хиљаде језгара одједном? Да ли је ово корисна ствар?

Цлифф: Успех ГПУ-а показује да је прилично користан!

Владимир: Они су прилично специјализовани. Шта је са процесорима опште намене?

Цлифф: Па, то је био Азулов пословни модел. Одговор се вратио у ери када су људи заиста волели предвидљиве перформансе. Тада је било тешко написати паралелни код. Модел кодирања Х2О је веома скалабилан, али није модел опште намене. Можда мало општије него када користите ГПУ. Да ли говоримо о сложености развоја такве ствари или о сложености њеног коришћења? На пример, Азул ме је научио занимљиву лекцију, прилично неочигледну: мали кешови су нормални. 

Највећи изазов у ​​животу

Владимир: Шта је са нетехничким изазовима?

Цлифф: Највећи изазов је био не бити... љубазан и фин према људима. И као резултат тога, стално сам се налазио у крајње конфликтним ситуацијама. Оне у којима сам знао да ствари иду наопако, али нисам знао како да напредујем са тим проблемима и нисам могао да их решим. Тако су настали многи дугорочни проблеми, који трају деценијама. Чињеница да Јава има Ц1 и Ц2 компајлере је директна последица овога. Директна последица је и чињеница да десет година заредом у Јави није било компилације на више нивоа. Очигледно је да нам је такав систем био потребан, али није очигледно зашто није постојао. Имао сам проблема са једним инжењером... или групом инжењера. Једном давно, када сам почео да радим у Суну, био сам... Добро, не само тада, генерално увек имам своје мишљење о свему. И мислио сам да је истина да можете само да узмете ову своју истину и кажете је директно. Поготово што сам већину времена био шокантно у праву. А ако вам се не свиђа овај приступ... поготово ако очигледно грешите и радите глупости... Генерално, мало људи би могло да толерише овај облик комуникације. Иако би неки могли, попут мене. Цео свој живот сам изградио на меритократским принципима. Ако ми покажеш нешто погрешно, одмах ћу се окренути и рећи: рекао си глупости. Истовремено, наравно, извињавам се и све то, констатоваћу основаност, ако их има, и предузети друге исправне радње. С друге стране, шокантно сам у праву за шокантно велики проценат укупног времена. И не функционише баш добро у односима са људима. Не покушавам да будем фин, али постављам питање отворено. "Ово никада неће успети, јер један, два и три." А они су били као, "Ох!" Било је и других последица које је вероватно било боље игнорисати: на пример, оне које су довеле до развода од супруге и десет година депресије након тога.

Изазов је борба са људима, са њиховом перцепцијом шта можете, а шта не можете, шта је важно, а шта није. Било је много изазова око стила кодирања. Још увек пишем много кода, а тих дана сам чак морао да успорим јер сам радио превише паралелних задатака и радио их лоше, уместо да се фокусирам на један. Гледајући уназад, написао сам пола кода за Јава ЈИТ команду, команду Ц2. Следећи најбржи кодер је писао упола спорије, следећи упола спорије, и то је био експоненцијални пад. Седма особа у овом низу је била веома, веома спора - то се увек дешава! Дотакао сам много кода. Гледао сам ко је шта написао, без изузетка, загледао сам се у њихов код, прегледао сваког од њих, и даље сам наставио да пишем више него било ко од њих. Овај приступ не функционише баш најбоље са људима. Некима се ово не свиђа. А када не могу да поднесу, почињу свакакве жалбе. На пример, једном су ми рекли да престанем да кодирам јер сам писао превише кода и угрожавао тим, и све ми је то звучало као шала: друже, ако остатак тима нестане, а ја наставим да пишем код, ти Изгубићу само пола тима. С друге стране, ако наставим да пишем код, а ви изгубите пола тима, то звучи као веома лош менаџмент. Никада нисам стварно размишљао о томе, никада нисам причао о томе, али то је још увек било негде у мојој глави. У позадини ми се вртела мисао: „Зар ме сви зезате?“ Дакле, највећи проблем сам био ја и моји односи са људима. Сада много боље разумем себе, дуго сам био вођа тима програмера, а сада директно кажем људима: знате, ја сам оно што јесам и мораћете да се носите са мном – да ли је у реду ако стојим овде? А када су почели да се баве, све је успело. У ствари, нисам ни лош ни добар, немам никакве лоше намере или себичне тежње, то је само моја суштина и са тим морам некако да живим.

Андрија: Недавно су сви почели да говоре о самосвести за интроверте и меким вештинама уопште. Шта можете рећи о овоме?

Цлифф: Да, то је био увид и лекција коју сам научио из развода од супруге. Оно што сам научио од развода је разумевање себе. Тако сам почео да разумем друге људе. Схватите како ова интеракција функционише. То је довело до открића једно за другим. Постојала је свест ко сам и шта представљам. Шта радим: или сам заокупљен задатком, или избегавам конфликт, или нешто друго – а овај ниво самосвести заиста помаже да се држим под контролом. После овога све иде много лакше. Једна ствар коју сам открио не само код себе, већ и код других програмера је немогућност вербализације мисли када сте у стању емоционалног стреса. На пример, седите тамо и кодирате, у стању тока, а онда они притрче вама и почну хистерично вриштати да је нешто покварено и да ће сада против вас бити предузете екстремне мере. И не можете да кажете ни реч јер сте у стању емоционалног стреса. Стечено знање вам омогућава да се припремите за овај тренутак, преживите га и пређете на план повлачења, након чега можете нешто учинити. Дакле, да, када почнете да схватате како све то функционише, то је огроман догађај који вам мења живот. 
Ни сам нисам могао да нађем праве речи, али сам запамтио редослед радњи. Поента је да је ова реакција колико физичка толико и вербална и да вам је потребан простор. Такав простор, у зен смислу. То је управо оно што треба објаснити, а затим одмах одступити - чисто физички. Када ћутим вербално, могу емоционално да обрадим ситуацију. Како адреналин дође до вашег мозга, пребаци вас у режим борбе или бекства, више не можете ништа да кажете, не - сада сте идиот, инжењер за бичевање, неспособан да пристојно одговорите или чак зауставите напад, а нападач је слободан да нападне изнова и изнова. Прво морате поново постати свој, повратити контролу, изаћи из режима „бори се или бежи“.

А за ово нам је потребан вербални простор. Само слободан простор. Ако уопште нешто кажете, онда можете рећи управо то, а затим идите и заиста пронађите „простор“ за себе: идите у шетњу парком, закључајте се под тушем - није важно. Главна ствар је да се привремено искључите из те ситуације. Чим се искључите бар на неколико секунди, контрола се враћа, почињете да размишљате трезвено. „Добро, нисам ја неки идиот, не радим глупости, ја сам прилично корисна особа. Када сте успели да се уверите, време је да пређете на следећу фазу: разумевање онога што се догодило. Били сте нападнути, напад је дошао одакле нисте очекивали, била је то непоштена, подла заседа. Ово је лоше. Следећи корак је разумети зашто је нападачу то било потребно. Стварно зашто? Можда зато што је и сам бесан? Зашто је љут? На пример, зато што се зезнуо и не може да прихвати одговорност? Ово је начин да се пажљиво реши читава ситуација. Али ово захтева маневарски простор, вербални простор. Први корак је прекид вербалног контакта. Избегавајте дискусију речима. Откажите, идите што је пре могуће. Ако је у питању телефонски разговор, само спусти слушалицу - ово је вештина коју сам научио у комуникацији са бившом женом. Ако разговор не иде нигде добро, само реците „збогом“ и спустите слушалицу. Са друге стране телефона: "бла бла бла", одговарате: "да, ћао!" и спусти слушалицу. Само завршите разговор. Пет минута касније, када вам се врати способност разумног размишљања, мало сте се охладили, постаје могуће размишљати о свему, шта је било и шта ће бити даље. И почните да формулишете промишљен одговор, уместо да реагујете само из емоција. За мене је искорак у самосвести била управо чињеница да у случају емоционалног стреса не могу да говорим. Излазак из овог стања, размишљање и планирање како да одговорите и надокнадите проблеме - то су прави кораци у случају када не можете да говорите. Најлакши начин је побећи из ситуације у којој се манифестује емоционални стрес и једноставно престати да учествујете у овом стресу. Након тога постајете способни да мислите, када можете да мислите, постајете способни да говорите, итд.

Узгред, на суду, противнички адвокат покушава да вам то уради - сада је јасно зашто. Јер он има способност да те потисне до таквог стања да не можеш ни да изговориш своје име, на пример. У стварном смислу, нећете моћи да говорите. Ако вам се то деси, и ако знате да ћете се наћи на месту где се воде вербалне борбе, на месту као што је суд, онда можете доћи са својим адвокатом. Адвокат ће се заузети за вас и зауставити вербални напад, и то на потпуно легалан начин, а изгубљени зен простор ће вам се вратити. На пример, морао сам да зовем породицу неколико пута, судија је био прилично пријатељски настројен у вези са овим, али је противнички адвокат вриштао и викао на мене, нисам могао ни да проговорим. У овим случајевима ми најбоље одговара коришћење посредника. Посредник зауставља сав овај притисак који се у непрекидном току слива на вас, проналазите неопходан зен простор, а са њим се враћа и способност говора. Ово је читава област знања у којој постоји много тога за проучавање, много тога за откривање у себи, а све се то претвара у стратешке одлуке на високом нивоу које су различите за различите људе. Неки људи немају горе описане проблеме; обично их немају људи који су професионални продавци. Сви ови људи који живе од речи – познати певачи, песници, верски поглавари и политичари, увек имају шта да кажу. Они немају такве проблеме, али ја имам.

Андрија: Било је... неочекивано. Одлично, већ смо доста разговарали и време је да завршимо овај интервју. Дефинитивно ћемо се састати на конференцији и моћи ћемо да наставимо овај дијалог. Видимо се у Хидри!

Разговор са Клифом можете наставити на конференцији Хидра 2019, која ће се одржати од 11. до 12. јула 2019. године у Санкт Петербургу. Доћи ће са извештајем „Азул хардверско искуство трансакционе меморије“. Улазнице се могу купити на званичној веб страници.

Извор: ввв.хабр.цом

Додај коментар