Каждый знает, что бывают «десятикратные» программисты, которые в 10 раз более производительны, чем программист обыкновенный. Мы не можем измерить производительность, поэтому и не знаем, правда ли это. Но на самом деле людей необыкновенно производительных существует немало, достаточно, чтобы доказать существование «десятикратного программиста».
Как же они этого добиваются?
Часто считают, что десятикратная производительность вытекает из десятикратных способностей или десятикратных знаний. Я так не думаю. Не хочу сказать, что способности или знания бесполезны. Но за много лет я заметил, что самое главное тут — десятикратная разборчивость. Фокус в том, чтобы постоянно уклоняться от паршивой работенки.
А под ней я имею в виду совсем не «интеллектуально неудовлетворительную». Определение паршивой работы — в том, что ее результат идет прямо в унитаз.
Я сам переделал немало паршивой работы, особенно когда был неопытным и наивным. (Одно из больших преимуществ опыта в том, что человек становится менее доверчивым — а это более, чем компенсирует испарение из памяти школьных знаний).
Я приведу вам просто показательный пример работы тяжелой, развивающей и отправляющейся прямо в унитаз: мои приключения десятилетней давности с фиксированной точкой.
Вы знаете, что такое «арифметика с фиксированной точкой»? Я вам расскажу. Это когда вы работаете с целыми, но делаете вид, что это дроби, неявно предполагая, что целое x на самом деле представляет собой x/2^N для какого-то значения N.
Чтобы сложить два числа, вы просто вычисляете x+y. Для умножения вам нужно x*y>>N, потому что просто x*y — это будет x*y/2^2N, правильно? Еще необходимо быть осторожным, чтобы эта дрянь не переполнилась, как-то разбираться с разными N в одном выражении и так далее.
Тогда, в начале 90-х, я портировал программы на процессор, который разрабатывали в нашей фирме. Аппаратный блок для плавающей точки ему не запланировали — «мы будем все делать с фиксированной точкой».
Вот часть моих тогдашних дел:
- Был готов сляпанный на скорую руку шаблон C++ под названием InteliFixed<N> (он и сейчас существует, я не обманываю). Я вложил немало труда, чтобы сделать его, гм… сляпанным на медленную руку (это противоположность «скорой руки»?). Надо было, например, сделать operator+ коммутативным, когда он получает два числа с фиксированной точкой различных типов (каким будет тип результата?); заставить ужасный инлайновый код на ассемблере, выполняющий 64-битовые промежуточные умножения, инлайниться получше; и т. д., и т. п.
- Мой босс велел мне держать две версии кода — одну с плавающий точкой, для благородных разработчиков алгоритмов, а другую с фиксированной, — для нас, чернорабочих, возящихся с рабочим кодом. Поэтому я синхронизировал эти версии вручную.
- Босс также приказал придумать способ, как запускать часть кода с плавающей точкой, а часть — нет, чтобы найти ошибки потери точности. Поэтому я написал эвристический парсер С++, который автоматически сливал две версии в одну. Он брал одни функции из «плавающей» версии, а другие — из «фиксированной», выбирая их по специальному файлу типа заголовочного.
Конечно, все это слитое дерьмо не запускалось и даже просто так не компилировалось, точно? Поэтому я сделал макросы, благодаря которым вы передавали в функцию не vector<float>&, а REFERENCE(vector<float>), и написал жуткий кусок кода, который поддерживал во время исполнения тот случай, когда передается vector<InteliFixed> (код внутри функции превращал его в vector<float>). - И кроме всего этого мета-программирования, было, конечно же, и само программирование. Например, решение системы уравнений 5x5, чтобы подобрать многочлены под зашумленные ряды данных, всё с фиксированной точкой. Я ухитрился заставить его работать, используя жуткие фокусы с нормализацией, а ассемблерный код использовал 96 битов точности. Мой код работал даже лучше, чем код с плавающей точкой одинарной точности без нормализации! Вот это да!
Месяцы и месяцы я работал в полную силу, выдавая сложный и отлаженный код — все, как обычно.
А вот что мне на самом деле нужно было сделать:
- Убедить начальство запихать вонючий блок с плавающей точкой в этот вонючий чип. Это потребовало бы не так уж много квадратных миллиметров кремния — я должен был настоять на том, чтобы высчитать, сколько именно (аппаратный FPU добавили в следующей версии процессора).
- Если бы это не получилось, мне надо было пробраться к симулятору схемы, измерить стоимость эмуляции плавающей точки и применять ее в тех местах кода, где это приемлемо (постепенно мы так и сделали).
- Сказать боссу, что синхронизировать две версии, как он хочет, невозможно — они будут расходиться все дальше и дальше, и в конце концов даже с помощью самого дьявола не удастся их слить и запустить получившийся код (конечно, так оно в результате и вышло).
Почему вся эта эпопея выродилась во многие месяцы паршивой работы, за которые можно было сделать что-то полезное? Потому что я не знал, что к чему; потому что я не догадывался, что можно спорить с начальником; и потому что работа была творческая и интересная. Которая очень быстро отправилась в унитаз.
Самое сложная вещь в «управлении» этими десятикратными людьми — теми, о которых все знают, что они очень производительны — убедить их заняться задачей. (все остальное уже гораздо легче — они сами знают, что почем; если они взялись что-то делать, то оно будет сделано).
Вы ждали совсем другого, признайтесь? Я имею в виду, что если вы такой продуктивный, то о чём вообще переживать? Вы работаете быстро; худшее, что вас ждёт, — что в результате ничего не получится — и тогда вы быстро займётесь чем-нибудь другим, ведь так? Я имею в виду, что это медлительным и не очень производительным людям надо быть переборчивыми — они же более медленные и у них меньше возможностей переключиться на что-то новое — ведь так?
Но это всего лишь оптическая иллюзия: более производительные люди не настолько быстро работают — не в десять раз быстрее. Причина, по которой они кажутся в 10 раз более быстрыми — в том, что почти ничего из ими сделанного не выбрасывается — в отличие от кучи работы, которой занимаются другие.
А выкинутые дела в производительности не учитываются. Вы расцениваете человека как «парня, который сделал Х», где полезность Х известна всем — и забываете о всех тех Y, которые не были особенно полезны, несмотря на усилия и талант, пошедший в их изготовление. Даже если в этом было что-то виновато — менеджер, или недостаток времени, или что там еще.
Если брать известные примеры — вы знаете Кена Томпсона по Си и Юниксу — но не по Плану 9, в самом-то деле, и не по языку Go. Пока что наоборот — Go привлек ваше внимание только потому, что это детище тех парней, которые сделали Юникс. Вы знаете Линуса Торвальдса, хотя Линукс — это клон Юникса, а Гит — клон Биткипера — собственно, потому что это клоны успешных продуктов, которые имели прекрасные шансы преуспеть, если бы появились вовремя.
Первое, на что вы смотрите — не на оригинальность и не на то, как сложно было писать, или как хороша эта вещь по какому-нибудь конкретному критерию: вы смотрите, как ее можно использовать.
Десятикратный программист, как правило, устраивает настоящую войну, только чтобы не заниматься тем, что никогда не будет использоваться.
Один из этих умных людей спросил меня как-то о checkedthreads, которые я только что закончил: «Кто-нибудь это использует?» с той самой фирменной иронией. Я сказал, что не знаю; на «Hacker News» кто-то написал в комментариях, что, может, попробует.
Я знаю, что это замечательная штука; с ее помощью можно найти все ваши ошибки в многопоточных программах. Но это не замена pthreads, вам придется писать код, используя новые интерфейсы — хорошие, простые интерфейсы, но не те, которые вы уже применяете. Так что вполне вероятно, что заинтересует моя библиотека немногих; хотя Helgrind и thread sanitizer имеют кучу ложных срабатываний, они по крайней мере работают с теми интерфейсами, которые люди широко используют.
Зачем мне тогда вообще было этим заниматься? Потому что для написания первой версии понадобился всего один день (я еще не решил тогда завести параллельные вложенные циклы и всё такое), и я посчитал, что какой-то шанс будет, если я напишу о моей программе в блоге (что я делаю прямо сейчас). Если бы я написал несколько постов, в которых объяснил, как практически можно отследить ошибки в старомодным параллельном, разделяющем общую память, коде на С даже легче, чем на Rust, или Go, или Erlang, тогда, может быть, люди обратили бы внимание.
Но и так слишком много шансов на провал среди той десятикратной толпы, которую я знаю лично — не стоит и пытаться. Даже если мы используем что-то типа checkedthreads у себя и очень даже успешно. Собственно, иронический вопрос я услышал от того парня, который вложил немало труда в эту самую внутреннюю версию — потому что очень вероятно, что у нас программа будет использоваться.
Видите? Не заниматься тем, что скорее всего провалится — вот где производительность.
Как выбрать то, над чем стоит работать? Есть множество вещей, которые следует учесть:
- Есть ли уже доступная альтернатива? Насколько она плоха? Если терпимая, тогда не делайте ничего сами — хорошую вещь сложно улучшить, и еще сложнее всех убедить, что это улучшение стоит перехода на новую версию
- Насколько важна эта штука? Без нее не будет ничего работать или это просто рюшечки, которые никто и не заметит?
- Сколько труда потребуется пользователям, чтобы получить какие-то преимущества? Она уже работает с существующим кодом или данными? Людям придется учить что-то новое или они смогут работать, как привыкли?
- Сколько людей должны узнать об этой штуке, чтобы она хотя бы распространилась? Пользователи будут запускать код, ничего о нем не зная, потому что он идет вместе со старым кодом, или им придется что-то устанавливать? (Получить какую-нибудь функцию автоматически и затем заняться ее практическим изучением, часто лучше, чем установить что-нибудь новое и тут же забыть. Подумайте, сколько людей в конце концов изучили какую-нибудь новую функцию в Экселе; и сколько используют программы резервного копирования в фоновом режиме).
- Какое количество кода дает какую пользу? Оптимизация до потери пульса маленького ядра, сжимающего звуки в формат mpeg, выглядит полезнее, чем просмотр миллиона строк кода, чтобы добиться ускорения в 1,2 раза (хотя последний вариант может быть полезен сам по себе; но обычно он требует в десять раз больше программистов, а совсем не одного «десятикратного» программиста).
- Сможет ли новая возможность сама себя защитить? Если пользователи сделают что-нибудь неправильно (или «неправильно»), станет ли она тихо бесполезна (вроде статического анализа кода, когда анализатор уже не понимает программу) или же остановит работу, пока ошибка не будет исправлена (как проверка на выход за границы массива)?
- …
Вы можете легко продолжить этот список; основная его идея — какова вероятность, что я закончу эту штуку и ее потом будут действительно использовать? Эта же идея рекурсивно применима к каждой функции, подфункции и строчке кода: целое благодаря им будет полезным? И не потратить ли мне время на что-то другое, что принесет больше пользы?
Конечно, все еще сложнее; некоторые полезные вещи по разным причинам ценятся выше других. Вот где появляется Ричард Столман и требует, чтобы мы называли Линукс «GNU/Линуксом», потому что GNU написала большую часть пользовательских программ. И хотя я не собираюсь называть систему «Гну-Линукс», в его претензиях, к сожалению, есть своя правда, в том смысле, что да, к сожалению, некоторая тяжелая и важная работа не так заметна, как другая тяжелая и важная работа.
Но справедливость — это уже другая тема. В конце концов, вряд ли десятикратная производительность даст вам десятикратную компенсацию.
Поэтому не так-то много поводов «жульничать» и казаться более производительным, чем вы есть. Главная причина, по которой люди производительны — это шило в заднице, не дающее покоя, а не какие-то осязаемые выгоды.
Вот что я хочу сказать: чтобы сделать больше, вам надо не столько добиваться успеха быстрее (хотя и это не повредит), сколько пореже испытывать неудачу. И не все неудачи вызваны недостатком знаний или опыта; большинство их бывает, когда программу бросают в той стадии, когда ее еще невозможно использовать — или потому, что ее с самого начала никто использовать не собирался.
Поэтому я, как человек, написавший немало кода для унитаза, считаю, что добиваться производительности надо не столько работой, сколько отсутствием работы — не заниматься тем, что в конце концов будет выкинуто на помойку.
Автор: Иосиф Крейнин
Оригинал: www.yosefk.com/blog/10x-more-selective.html
Автор: gatoazul