TLDR
- YaFF — это открытый zero‑copy wire‑format от Яндекса для Protobuf (Apache 2.0, C++, v0.1.0).
- .proto‑файл остаётся единственным источником истины; меняется только физическое расположение данных в памяти.
- По результатам бенчмарков Яндекса Flat Layout читает «горячие» данные почти в 4 раз быстрее, чем FlatBuffers, и почти не отстаёт от чистой структуры C++ (≈1.2×).
- Четыре типа раскладки — Fixed, Flat, Sparse, Dynamic — позволяют балансировать между скоростью чтения и гибкостью схемы; по умолчанию используется Dynamic.
- YaFF уже работает в системе рекомендаций рекламы, где даёт 10–20 % экономии CPU на продакшене.
- Внедрение поэтапное: подключаете YaFF к одному горячему пути, а на границе конвертируете в/из Protobuf.
Что такое YaFF
YaFF — не замена Protobuf, а альтернативный формат передачи сообщений Protobuf. Тот же .proto генерирует привычный C++‑API, но чтение происходит без парсинга: поля берутся сразу из буфера. При необходимости данные могут быть преобразованы обратно в обычный Protobuf‑сообщения, что делает возможным постепенный переход.
Какая проблема решается
Парсинг Protobuf часто съедает десятки процентов CPU в высоконагруженных бекендах. При масштабах в тысячи ядер это серьёзные затраты. Популярный zero‑copy‑вариант — FlatBuffers, но он требует отдельной схемы, двойного поддержания и ручных конвертеров, что почти всегда отпугивает команды. YaFF закрывает эту брешь: сохраняет семантику Protobuf и дает нулевое копирование при чтении.
Как работают раскладки
Раскладка описывает, как сообщение хранится в буфере; схема и сгенерированные интерфейсы остаются без изменений. YaFF предлагает четыре варианта:
- Fixed — простой упакованный struct без заголовка, схема фиксирована.
- Flat — два‑байтовый заголовок, поддержка ограниченной эволюции схемы.
- Sparse — метатаблица, подходит для разрежённых схем.
- Dynamic — выбирает Flat или Sparse в runtime, используется по умолчанию.
Бенчмарк
Итоги тестов на AMD EPYC 7713 (Clang 20, Release):
- Raw C++ struct — 8.14 ns (baseline)
- YaFF Flat Layout — 9.79 ns (≈1.2× от baseline)
- YaFF Sparse Layout — 21.23 ns (≈2.6×)
- FlatBuffers — 37.30 ns (≈4.6×)
- Protobuf — 219.35 ns (≈26.9×)
Отношения сохраняются на разном железе, меняются только абсолютные времена.
Тонкости компиляторного алиасинга
FlatBuffers и YaFF читают поля путём reinterpret_cast, из‑за чего LLVM переходит к консервативному MayAlias. YaFF добавляет аннотации в генерируемый код, позволяя компилятору безопасно кешировать цепочки доступа, если память не меняется между чтениями. Это дает дополнительные выигрыши без вашего вмешательства.
Где YaFF уместен
Идеальные сценарии — системы, где владеете и производителем, и потребителем данных:
- Рекомендательные и рекламные бекенды (экономия 10–20 % CPU).
- Memory‑mapped индексы: поисковые индексы, feature‑store, feed‑сервисы.
- Колонковые хранилища для аналитики и ML‑pipeline (планируемый Columnar Layout).
- Любые read‑heavy сервисы, где важна низкая латентность и высокое кэш‑поведение.
Пример кода
Чтение без парсинга выглядит так:
#include "feed.pb.h" // код, сгенерированный protoc
#include "feed.yaff.h" // код, сгенерированный yaff_generate()
// 1. Сериализуем обычное Protobuf‑сообщение в буфер YaFF.
feed::FeedResponse proto = LoadFeedResponse();
auto buffer = yaff::Serialize<feed::FeedResponse>(proto);
// 2. Читаем поля напрямую из буфера — парсинга нет.
const auto& response = yaff::ReadMessage<feed::FeedResponse>(buffer.Data());
for (const auto& item : response.items()) {
std::string_view title = item.title();
std::string_view author = item.author().name(); // пусто, если поле не задано
}
// 3. При необходимости конвертируем обратно в Protobuf.
feed::FeedResponse restored;
response.ParseTo(restored);
Подключение в CMake/Conan: find_package(yaff REQUIRED), затем yaff_generate() после обычного protobuf_generate(). Сгенерированные типы находятся в пространстве protoyaff::.
Полезные ресурсы
- GitHub‑репозиторий YaFF
- Официальная документация
- Исходные бенчмарки: yaff.tech/docs/en/benchmarks/access





















