Itinai.com it company office background blured chaos 50 v 41eae118 fe3f 43d0 8564 55d2ed4291fc 3

Ускорьте Protobuf в Яндексе: YaFF Zero‑Copy без потерь

Itinai.com it company office background blured chaos 50 v 41eae118 fe3f 43d0 8564 55d2ed4291fc 3

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::.

Полезные ресурсы

Запустите свой ИИ проект бесплатно

ИИ-агенты искусственный интеллект онлайн для бизнеса

Лучший ИИ онлайн