Создание движка базы данных с микросекундной задержкой на C#
Проект Typhon Лоика Бауманна нацелен на ACID-коммиты за 1-2 микросекунды с использованием ref struct, аппаратных интринсиков и закреплённой памяти, доказывая, что C# может конкурировать на уровне системного программирования.
Предположение, что высокопроизводительные движки баз данных требуют C, C++ или Rust, глубоко укоренилось. Проект Typhon Лоика Бауманна бросает этому прямой вызов: встраиваемый ACID-движок базы данных, написанный на C#, нацеленный на транзакционные коммиты за 1-2 микросекунды. Проект недавно попал на главную страницу Hacker News, вызвав оживлённую дискуссию о том, что современный .NET действительно может делать.
Инструментарий производительности в современном C#
Ключевой аргумент Бауманна — что узким местом в дизайне движков баз данных является раскладка памяти, а не выбор языка. Современный C# предоставляет инструменты для контроля памяти на уровне, который был бы невозможен десятилетие назад.
Типы ref struct живут исключительно на стеке, устраняя выделения в куче на горячих путях:
ref struct TransactionContext
{
public Span<byte> WriteBuffer;
public int PageIndex;
public bool IsDirty;
}
Для областей памяти, которые никогда не должны перемещаться, GCHandle.Alloc с GCHandleType.Pinned держит сборщик мусора подальше от критических участков. В сочетании с [StructLayout(LayoutKind.Explicit)] вы получаете контроль на уровне C над каждым байтовым смещением:
[StructLayout(LayoutKind.Explicit, Size = 64)]
struct PageHeader
{
[FieldOffset(0)] public long PageId;
[FieldOffset(8)] public long TransactionId;
[FieldOffset(16)] public int RecordCount;
[FieldOffset(20)] public PageFlags Flags;
}
Аппаратные интринсики для горячих путей
Пространство имён System.Runtime.Intrinsics даёт прямой доступ к SIMD-инструкциям. Для движка базы данных, сканирующего страницы или вычисляющего контрольные суммы, это разница между “достаточно быстро” и “конкурентоспособно с C”:
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
static unsafe uint Crc32Page(byte* data, int length)
{
uint crc = 0;
int i = 0;
for (; i + 8 <= length; i += 8)
crc = Sse42.Crc32(crc, *(ulong*)(data + i));
for (; i < length; i++)
crc = Sse42.Crc32(crc, data[i]);
return crc;
}
Принуждение дисциплины во время компиляции
Один из самых интересных аспектов подхода Typhon — использование анализаторов Roslyn в качестве защитных перил. Пользовательские анализаторы навязывают доменно-специфичные правила (отсутствие случайных выделений в куче в транзакционном коде, отсутствие непроверенной арифметики указателей вне утверждённых модулей) во время компиляции, вместо того чтобы полагаться на обзор кода.
Ограниченные дженерики с where T : unmanaged обеспечивают ещё один уровень, гарантируя, что обобщённые структуры данных работают только с blittable-типами, имеющими предсказуемые раскладки памяти.
Что это значит для .NET
Typhon ещё не продакшн-база данных. Но проект демонстрирует, что разрыв между C# и традиционными системными языками значительно сократился. Между Span<T>, аппаратными интринсиками, ref struct и явным контролем раскладки памяти, .NET 10 даёт вам строительные блоки для критически важной по производительности системной работы, не покидая управляемую экосистему.
Полный материал стоит прочитать ради архитектурных деталей и бенчмарков.