Rust vs Go vs C# в 2026: почему наивный Rust проиграл Go
Спор «что быстрее: Rust, Go или C#» обычно заканчивается ссылкой на чей-то бенчмарк, где один язык обгоняет другой в полтора раза. Мы решили проверить простую вещь и сразу получили результат, который ломает привычную картину: наивный код на Rust проиграл Go по времени выполнения. Ниже разбираем, почему так вышло, что на самом деле означает «производительность» в 2026 году и как выбирать язык под конкретную нагрузку.
Почему готовым бенчмаркам больше нельзя верить на слово
Долгое время главным ориентиром для веб-фреймворков были TechEmpower Framework Benchmarks. В марте 2026 года проект перевели в архив. Последний, 23-й раунд вышел в феврале 2025 года и охватывал больше 330 реализаций, но тестовая платформа годами почти не менялась, результаты упирались примерно в 30 млн запросов в секунду, а участники научились выигрывать за счет низкоуровневых трюков, которые в обычном приложении никто не пишет. Сами разработчики фреймворков признавали, что такие цифры вводят в заблуждение.
Синтетические тесты вроде Benchmarks Game полезны, но измеряют предельно оптимизированный код, написанный экспертами по каждому языку. В реальном проекте код пишут обычные разработчики под дедлайн, а теперь еще и ИИ-ассистенты. Поэтому полезнее смотреть, как язык ведет себя на простом, «честном» коде и сколько усилий нужно, чтобы выжать из него максимум.
Эксперимент: миллионы маленьких объектов
Для теста мы взяли классическую задачу на аллокации: 20 раз построить полное двоичное дерево глубины 20 (около двух миллионов узлов) и обойти его. Такой профиль нагрузки встречается везде, где программа создает много мелких объектов: парсеры, AST, деревья состояний, графы зависимостей, ответы API, разобранные в структуры. Код написан максимально прямолинейно, без оптимизаций.
Rust, каждый узел в отдельной Box:
Go, обычные указатели и сборщик мусора:
C#, тот же алгоритм на классах:
Замеры проводились на виртуальной машине с двумя ядрами Intel Xeon 2,8 ГГц, Rust 1.97 и Go 1.24, каждый вариант запускался пять раз, в таблице медиана. C#-версию мы приводим для самостоятельной проверки: ее результат сильно зависит от режима (JIT или Native AOT) и настроек сборщика мусора, поэтому сравнивать ее стоит на своем железе с теми же настройками, что в продакшене.
Результаты (время, процессорное время, пик памяти):
Rust, Box на каждый узел: 3,18 с, 3,12 с, 66 МБ
Go, настройки по умолчанию: 2,89 с, 4,04 с, 78 МБ
Go, GOMAXPROCS=1: 3,45 с, 3,56 с, 119 МБ
Go, GOGC=400: 2,33 с, 2,75 с, 179 МБ
Go, GOGC=off: 2,23 с, 2,19 с, 667 МБ
Rust, арена (Vec и индексы): 0,41 с, 0,40 с, 17 МБ
Что показал тест
Наивный Rust оказался медленнее Go по реальному времени: 3,18 секунды против 2,89. Причина в модели памяти. Каждый Box в Rust означает отдельный вызов системного аллокатора на создание и отдельное освобождение при удалении дерева, то есть миллионы вызовов malloc и free. Аллокатор Go устроен иначе: мелкие объекты одного размера выделяются из заранее подготовленных блоков очень дешево, а освобождает их сборщик мусора пачками.
Но у победы Go есть цена, которую видно во втором столбце. Процессорного времени Go потратил 4,04 секунды против 3,12 у Rust. Сборщик мусора работал параллельно на втором ядре, и выигрыш по часам получен за счет дополнительной нагрузки на процессор. На сервере, где все ядра заняты полезной работой, этот запас исчезнет. Если ограничить Go одним ядром через GOMAXPROCS=1, время вырастает до 3,45 секунды, и Rust снова впереди.
Строки с GOGC показывают, как в Go обменивается память на скорость. При GOGC=400 сборщик запускается реже: время падает до 2,33 секунды, а пик памяти растет до 179 МБ. С отключенным сборщиком программа работает быстрее всего среди вариантов Go, но съедает 667 МБ. В продакшене для этого обмена есть более безопасный инструмент GOMEMLIMIT, который задает мягкий потолок памяти.
Самая интересная строка последняя. Rust с ареной, где все узлы лежат в одном векторе, а ссылки заменены индексами, справился за 0,41 секунды и 17 МБ памяти. Это почти в семь раз быстрее Go и наивного Rust. Именно здесь проявляется настоящее преимущество Rust: он дает полный контроль над размещением данных в памяти. Только этот контроль нужно использовать осознанно, сам по себе язык быструю программу не гарантирует.
Ось первая: чистая вычислительная скорость
В вычислительных циклах без аллокаций Rust стабильно держится на уровне C и C++: компилятор на базе LLVM агрессивно встраивает функции, разворачивает циклы и автоматически векторизует код. C# в последние годы заметно сократил отставание. Многоуровневая JIT-компиляция с динамической оптимизацией по профилю (Dynamic PGO) перекомпилирует горячие методы с учетом реальных типов и ветвлений, а .NET 10, вышедший 11 ноября 2025 года, улучшил встраивание, девиртуализацию методов, работу со структурами и добавил поддержку AVX10.2 и Arm64 SVE.
Компилятор Go сознательно проще: он оптимизирует менее агрессивно, зато собирает проекты очень быстро. Для числодробилок Go обычно отстает от Rust и современного C#, и именно поэтому в Go 1.26 появился экспериментальный пакет simd/archsimd для прямого доступа к векторным инструкциям. Если ваш сервис в основном ждет базу данных и сеть, эта разница почти не будет заметна.
Ось вторая: задержки и сборка мусора
Для сетевых сервисов средняя скорость важна меньше, чем хвостовые задержки: насколько медленно отвечают худшие 1% или 0,1% запросов. Здесь главную роль играет сборщик мусора.
Самая известная история на эту тему произошла в Discord в 2020 году. Сервис, отвечавший за статусы прочтения сообщений, был написан на Go и регулярно страдал от всплесков задержек, связанных со сборкой мусора. После переписывания на Rust всплески исчезли, а задержки и потребление ресурсов снизились. С тех пор сборщик Go сильно изменился. В Go 1.26, вышедшем 10 февраля 2026 года, по умолчанию включен новый сборщик Green Tea. Он сканирует память целыми страницами вместо отдельных объектов, что лучше работает с кэшем процессора. По данным команды Go, многие программы тратят на сборку мусора примерно на 10% меньше времени, а отдельные нагрузки до 40% меньше.
В .NET используется поколенческий сборщик мусора с режимами Workstation и Server, а для контейнеров есть адаптивный режим DATAS, который подстраивает размер кучи под нагрузку. В .NET 10 улучшен escape-анализ, поэтому больше короткоживущих объектов размещается на стеке и вообще не попадает в кучу, а на Arm64 доработанные барьеры записи сократили паузы сборщика на 8-20%. Rust сборщика мусора не имеет вовсе, память освобождается детерминированно, и всплесков из-за GC просто не бывает. Для систем с жесткими требованиями к задержкам, вроде торговых движков, игровых серверов или сетевых прокси, это решающий аргумент.
Ось третья: потребление памяти
Наш тест хорошо показывает закономерность. Языку со сборщиком мусора для скорости нужен запас памяти: чем реже он убирает, тем быстрее работает и тем больше занимает. Go позволяет управлять этим через GOGC и GOMEMLIMIT, .NET через настройки GC и режим DATAS. Rust занимает ровно столько, сколько нужно данным, плюс накладные расходы аллокатора.
В облаке это превращается в деньги. Если сервис на Go или C# требует 512 МБ на под, а аналог на Rust укладывается в 128 МБ, при сотнях реплик разница в счете за Kubernetes становится заметной. Правда, сэкономить так получится только на сервисах, которые реально упираются в память.
Ось четвертая: холодный старт и размер
Для serverless-функций, CLI-утилит и сервисов с частым масштабированием важно, как быстро процесс начинает отвечать. Go компилируется в один статический бинарник и стартует за миллисекунды, поэтому так популярен в инфраструктурных инструментах. Rust тоже дает компактный нативный бинарник с мгновенным стартом.
Классическое приложение на .NET стартует медленнее из-за загрузки рантайма и JIT-компиляции, и под нагрузкой ему нужно время на «прогрев». Эту проблему решает Native AOT: приложение заранее компилируется в машинный код, стартует быстро и занимает меньше памяти. В .NET 10 AOT-сборки стали меньше и быстрее, но у режима есть ограничения: часть кода с рефлексией и динамической генерацией не работает, а библиотеки должны поддерживать тримминг. Кроме того, без JIT пропадает динамическая оптимизация по профилю, и в долгоживущих сервисах пиковая производительность AOT-версии бывает ниже, чем у прогретой JIT-версии.
Ось пятая: скорость разработки
Производительность команды тоже производительность. Go собирается быстрее всех и читается почти без подготовки, а Go 1.27, вышедший в августе 2026 года, ускорил выделение мелких объектов до 30%, добавил generic-методы и новый пакет encoding/json/v2 с заметно более быстрым разбором JSON. C# дает богатую экосистему, отличные инструменты и производительность, которой хватает подавляющему большинству бизнес-систем. Rust требует больше времени на обучение и сборку, а borrow checker заставляет продумывать владение данными заранее. Зато многие ошибки, которые в других языках всплывают в продакшене, в Rust не проходят компиляцию.
Так что выбрать
Если вы пишете сетевой сервис, API или инфраструктурный инструмент и вам важны простота, быстрая сборка и предсказуемый результат без тонкой настройки, Go остается самым практичным выбором, особенно после появления Green Tea. Если у вас корпоративная система, богатая бизнес-логика, команда с опытом в .NET или нужна экосистема Microsoft, C# в 2026 году дает производительность, которой с запасом хватает, а Native AOT закрывает вопрос холодного старта.
Rust стоит выбирать там, где каждая миллисекунда хвостовой задержки и каждый мегабайт памяти превращаются в деньги или в требования к надежности: прокси и балансировщики, движки баз данных, обработка потоков данных, встраиваемые системы, горячие участки других сервисов. Но, как показал наш тест, Rust становится быстрым тогда, когда вы проектируете размещение данных, а не просто переписываете код с другого языка один в один.
Хорошее правило на практике: напишите прототип горячего участка на двух языках, прогоните его на своих данных и своем железе и смотрите на четыре числа сразу: время, процессорное время, пик памяти и p99 задержки. Команды для такого сравнения:
Итог
Вопрос «какой язык быстрее» в 2026 году почти потерял смысл. Rust дает максимальный потолок производительности и предсказуемость без сборщика мусора, но этот потолок нужно уметь достать. Go выигрывает простотой и обменивает процессор и память на скорость разработки, а Green Tea заметно снизил цену сборки мусора. C# за последние версии подтянулся к лидерам и стал универсальным выбором для больших систем. Правильный ответ всегда зависит от того, что именно вы измеряете.
Полная версия статьи с таблицей результатов на uproger.com.