Максим Байтович – От транзистора до трансформера. Настольная книга программиста (страница 1)
Максим Байтович
От транзистора до трансформера. Настольная книга программиста
Лекция 00. Введение. Зачем программисту кремний и внимание
Когда студент впервые приходит ко мне на курс, он обычно уже умеет писать код. Он собирал проекты, выкладывал сервисы в облако, может быть, даже поднимал собственный сервер и гордился этим вполне заслуженно. Но если спросить его, что происходит между нажатием клавиши и пикселем на экране, он почти наверняка скажет «магия». Не потому, что он ленив, а потому, что современная разработка устроена так, чтобы этот вопрос не возникал. Фреймворк прячет сеть, сборщик прячет компилятор, облако прячет железо. Каждый слой честно делает свою работу и каждый слой делает вас чуть более беспомощным, когда что-то идёт не так.
Эта книга — попытка снять эти слои по одному. Мы пройдём путь, который инженеры прошли за семьдесят с лишним лет: от транзистора, который умеет только отличать высокое напряжение от низкого, до трансформера, который пишет текст на человеческом языке. Ни одно из этих устройств не умнее другого. Разница лишь в том, сколько раз простые операции повторили и как аккуратно их сложили вместе.
Почему это вообще нужно программисту, а не только инженеру железа? Потому что цена ошибки растёт вместе с уровнем абстракции. Пропущенный индекс в низкоуровневом коде стоит вам часа под санитайзером. Неверное предположение о кэше стоит дня профилирования. Ошибка в расчёте нагрузки на кластер стоит недели простоя и разговора, который вы не забудете. Чем дальше вы от железа, тем дороже обходится незнание железа и тем меньше шансов, что ошибку поймает за вас кто-то другой.
Я буду настаивать на одной мысли всю книгу. Программисту не нужно уметь проектировать процессор. Ему нужно понимать, какие договоры заключают между собой слои системы и что происходит, когда договор нарушен. Эти договоры не записаны в документации: они живут в головах инженеров и в поведении системы под нагрузкой. Наша задача — перевести их в слова, которые можно проверить экспериментом.
Каждая лекция устроена одинаково, и я предупреждаю об этом заранее. Сначала я рассказываю историю: реальную, из практики, часто с чужой или своей ошибкой в центре. Потом разбираю механику — что именно произошло и почему это было неизбежно. Затем показываю, как это знание меняет ваши повседневные решения. В конце каждой лекции есть домашнее задание, и это не упражнения ради упражнений: я прошу что-то измерить на вашей собственной машине или в вашем собственном проекте, потому что знание, которое вы не проверили руками, испаряется за две недели.
Про измерения скажу отдельно. В этой книге почти нет утверждений вида «это быстрее в десять раз» без оговорки, в каком контексте и на какой нагрузке. Инженерная правда всегда условна: быстрее для этого размера данных, для этого профиля доступа, на этом поколении железа. Как только вы перестаёте бояться этих оговорок, вы перестаёте спорить о технологиях в интернете и начинаете решать задачи. Я считаю это главным признаком взросления инженера: он перестаёт искать лучшую технологию и начинает искать подходящую.
Ещё одно предупреждение. Я буду говорить о вещах, которые в вашем стеке, возможно, не встречаются: о барьерах памяти, о протоколах консенсуса, о механизме внимания. Не спешите пропускать. Почти все эти темы возвращаются в неожиданном месте и обычно в тот момент, когда вы уже в проде и у вас горит дедлайн. Лучше встретить странный термин здесь, на странице, чем ночью в логах продакшена.
И последнее, о чём я прошу перед тем, как мы начнём. Держите под рукой свой проект — любой, пусть учебный. Все примеры из этой книги можно прогнать на нём. Когда вы увидите, как теория объясняет поведение вашего собственного кода, она перестанет быть теорией. Именно в этот момент книга делает свою работу, а не тогда, когда вы дочитали последнюю страницу.
Итак, двадцать лекций, один вечер на каждую. Начнём с самого низа — с куска кремния, который умеет принимать решение. От транзистора мы поднимемся к трансформеру, и по дороге вы увидите, что между ними меньше разрыва, чем принято думать.
Лекция 01. Транзистор и логика: кусок кремния, который решает
Начну с вопроса, который я задаю на первой лекции и на который почти никто не отвечает правильно. Что общего у процессора в вашем ноутбуке и у водопроводного крана? Студенты называют материалы, размер, цену. А общее — одно: и то и другое является клапаном, который либо пропускает поток, либо нет. Вся цифровая цивилизация построена на том, что мы научились делать такие клапаны микроскопическими, дешёвыми и переключаемыми напряжением, а не рукой. Этот клапан называется транзистор, и с него начинается всё.
Чтобы понять транзистор, не нужна квантовая механика. Достаточно одной идеи: у нас есть три вывода — исток, сток и затвор. Между истоком и стоком ток идёт только тогда, когда на затвор подано напряжение. Нет напряжения — тока нет. Есть — есть. То есть транзистор — это выключатель, которым управляет не палец, а электрический сигнал. А раз сигнал управляет выключателем, то один выключатель может управлять другим, а тот — третьим, и из этой цепочки управляемых переключателей можно собрать всё.
Почему именно два состояния? Потому что аналоговый сигнал, у которого «сколько угодно» значений, легко испортить: добавился шум — и значение уехало, и вы не отличите полезный сигнал от помехи. У сигнала с двумя уровнями есть запас прочности: пока шум не перевалил половину разницы между уровнями, мы уверенно читаем ноль или единицу. Цифровая схема — это способ получить надёжность из ненадёжных компонентов, и цена этой надёжности — отказ от всей середины между нулем и единицей. Это первое и главное решение, которое приняли инженеры, и всё остальное из него следует.
Из одного транзистора собирается простейший элемент — инвертор: подали единицу, получили ноль, и наоборот. Из двух транзисторов — элемент «и-не»: он выдаёт ноль только тогда, когда оба входа единицы. Кажется, это мелочь, но именно «и-не» — кирпич, из которого собирается всё остальное. Любой вентиль, любой сумматор, любой регистр, любой процессор — это в конечном счёте дерево из таких переключателей. Есть теорема, что любую булеву функцию можно выразить через «и-не», и на практике инженеры любят этот вентиль именно за универсальность: фабрике проще штамповать один тип ячейки.
Здесь стоит остановиться и посмотреть, что произошло. У нас был физический прибор с нелинейной характеристикой, а мы договорились считать его чёрным ящиком с двумя состояниями. Потом мы собрали из двух таких ящиков логический вентиль и договорились считать его функцией. Потом из вентилей — сумматор и считаем его арифметикой. На каждом шаге мы поднимались на уровень абстракции выше, и на каждом шаге нижний уровень продолжал честно работать. Это и есть инженерный метод: строить башню договоров, где каждый этаж опирается на предыдущий и не заглядывает ему внутрь. Запомните эту картину — мы будем возвращаться к ней в каждой части книги.
Теперь арифметика. Как из переключателей получить сложение? Очень просто, если записать сложение одного бита как таблицу: ноль плюс ноль — ноль, ноль плюс единица — единица, единица плюс единица — ноль с переносом в следующий разряд. Сумма бита — это «исключающее или», перенос — это «и». Обе функции собираются из вентилей за несколько штук. Сложите такие одноразрядные сумматоры цепочкой, передавая перенос, и вы получите сумматор на любое число бит. Всё, что делает ваш процессор с числами, в основе — эта цепочка переключателей, передающих перенос. Умножение — это сложение и сдвиги, деление — это вычитание и сдвиги. Арифметика — надстройка над переключением.
От арифметики до памяти один шаг. Если выход схемы завести обратно на вход через вентиль, управляемый тактом, получится ячейка, которая хранит бит, пока есть питание. Регистры, кэши, оперативная память — все это вариации этой идеи: цепь, которая держит собственное состояние. Разница между ними — в плотности, скорости и цене, но не в принципе. Память — это тоже переключатели, просто включённые так, чтобы сохранять, а не вычислять.
И вот тут появляется то, что определяет всю дальнейшую жизнь инженера, — цена переключения. Каждый раз, когда транзистор меняет состояние, он расходует энергию: заряжает и разряжает крошечную ёмкость затвора. Один переход — ничтожно мало, но переключателей в современном чипе десятки миллиардов, и частота — миллиарды переключений в секунду на каждый. Перемножьте — и получите десятки ватт на кристалле размером с ноготь. Отсюда главное физическое ограничение микроэлектроники: мы упираемся не в то, сколько вентилей умеем делать, а в то, сколько тепла умеем снять. Вся история последних двадцати лет — это история борьбы за то, чтобы переключать меньше и реже.
Эта борьба объясняет, почему закон Мура в прежнем виде закончился и почему вместо роста частоты появились многоядерные процессоры. Поднять частоту — значит переключать чаще, а значит, греть сильнее. Проще поставить два ядра на меньшей частоте, чем одно на большей: суммарно они сделают больше работы на том же тепловом бюджете. Отсюда и весь сдвиг в программировании: параллелизм перестал быть экзотикой и стал способом уложиться в ватты. Когда вы пишете код и он не использует все ядра, вы буквально отапливаете комнату половиной купленного вами транзисторного бюджета.