1. Что такое FlatBuffers?
FlatBuffers — это библиотека сериализации с открытым исходным кодом, которая реализует формат сериализации, аналогичный протоколам Protocol Buffers, Thrift, Apache Avro, SBE и Cap'n Proto. Оортмерссен изначально разработал FlatBuffers для игр Android и приложений, ориентированных на производительность. Теперь у него есть порты для C++, C#, C, Go, Java, PHP, Python и JavaScript.
FlatBuffer — это двоичный буфер, который организует вложенные объекты (структуры, таблицы, векторы и т. д.) с использованием смещений, обеспечивая доступ к данным на месте, как к любой структуре данных на основе указателя. Однако, в отличие от большинства структур данных в памяти, FlatBuffer использует строгие правила выравнивания и порядок следования байтов, чтобы обеспечить кроссплатформенность буферов. Кроме того, для табличных объектов FlatBuffers обеспечивает прямую/обратную совместимость и дополнительные поля для поддержки большинства изменений формата.
Основная цель FlatBuffers — избежать десериализации. Это достигается путем определения протокола двоичных данных, метода преобразования определенных данных в двоичные данные. Двоичные структуры, созданные этим протоколом, могут быть отправлены по сети и прочитаны без дальнейшей обработки. Напротив, при передаче JSON нам нужно преобразовать данные в строку, отправить их по сети, проанализировать строку и преобразовать ее в собственный объект. Плоские буферы не требуют этих операций. Вы загружаете данные в двоичном виде, отправляете тот же двоичный файл и читаете непосредственно из двоичного файла.
Хотя FlatBuffers имеет собственный язык определения интерфейса для определения сериализуемых данных, он также поддерживает.proto
Формат.
Типы объектов определяются в схеме, которую затем можно скомпилировать в различные основные языки, такие как C++ или Java, для чтения и записи с нулевыми накладными расходами. FlatBuffers также поддерживает динамический анализ данных JSON в буферах.
Помимо эффективности синтаксического анализа, двоичный формат дает еще одно преимущество: двоичное представление данных часто более эффективно. Мы можем использовать 4 байта UInt вместо 10 символов для хранения 10-значных целых чисел.
2. Зачем изобретать FlatBuffers?
JSON — это формат данных, который существует независимо от языка, но он потребляет наше время и ресурсы памяти при анализе данных и преобразовании их в объекты, такие как Java. Клиенту требуется почти 35 мс для анализа потока JSON размером 20 КБ, а время обновления пользовательского интерфейса составляет 16,6 мс. В играх с высоким реалтаймом лагов быть не может, поэтому нужен новый формат данных, при парсинге JSON сервер иногда создает много мелких объектов Для JSON нужно обрабатывать миллионы игроков в секунду Данные и сервер давление увеличится.Если каждый раз, когда анализируется JSON, генерируется много мелких объектов, то массивные мелкие объекты, принесенные большим количеством игроков, могут вызвать проблемы, связанные с GC, при освобождении памяти. FlatBuffers был разработан сотрудником Google Воутером ван Оортмерсеном для решения проблем с производительностью в играх. (Примечание: буферы протокола создаются Google, а FlatBuffers создаются в Google)
Несколько лет назад Facebook заявил, что его приложение для Android значительно повысило производительность обработки данных. Почти для всего приложения они отказались от JSON в пользу FlatBuffers.
FlatBuffers(9490 звезд) иCap'n Proto(5527 звезд),simple-binary-encoding(1351 звезда), поддерживает десериализацию "zero-copy", при сериализации не создаются временные объекты, не выделяется дополнительное выделение памяти, а доступ к сериализованным данным не требует предварительного их копирования в отдельную часть памяти, что делает доступ к данным в эти форматы намного быстрее, чем данные в требуемых форматах, таких как JSON, CSV и protobuf.
FlatBuffers действительно похожи на Protocol Buffers, главное отличие состоит в том, что FlatBuffers не нужно анализировать/распаковывать перед доступом к данным. Оба кода также имеют порядок величины. Но протокольные буферы не имеют ни опциональной функции импорта/экспорта текста, ни функции объединенного языка, которые есть у FlatBuffers.
FlatBuffers фокусируется на мобильном оборудовании (где объем памяти и пропускная способность памяти более ограничены, чем на настольном оборудовании) и приложениях с самыми высокими требованиями к производительности: играх.
3. Использование FlatBuffers
Сказав так много, читатели спросят, сколько людей используют FlatBuffers? На официальной странице Google упоминаются 3 известных приложения и 1 фреймворк, использующий его.
BobbleApp, первое приложение для стикеров в Индии. После использования FlatBuffers в BobbleApp производительность приложения значительно повысилась.
Facebook использует FlatBuffers для связи клиент-сервер в приложениях для Android. они написали статью«Улучшение производительности Facebook на Android с помощью FlatBuffers»чтобы описать, как FlatBuffers ускоряют загрузку контента.
Google Fun Propulsion Labs активно использует FlatBuffers во всех своих библиотеках и играх.
Cocos2d-X, первый движок для мобильных игр с открытым исходным кодом, использует FlatBuffers для сериализации всех игровых данных.
Видно, что FlatBuffers широко используются в игровых приложениях.
4. Определите файл схемы .fbs
Напишите файл схемы, который позволит вам определить структуру данных, которую вы хотите сериализовать. Поля могут иметь скалярный тип (целое/плавающее всех размеров) или строку, массив любого типа, ссылку на другой объект или набор возможных объектов (объединение). Поля могут быть необязательными или иметь значения по умолчанию, поэтому они не должны существовать в каждом экземпляре объекта.
Например:
// example IDL file
namespace MyGame;
attribute "priority";
enum Color : byte { Red = 1, Green, Blue }
union Any { Monster, Weapon, Pickup }
struct Vec3 {
x:float;
y:float;
z:float;
}
table Monster {
pos:Vec3;
mana:short = 150;
hp:short = 100;
name:string;
friendly:bool = false (deprecated, priority: 1);
inventory:[ubyte];
color:Color = Blue;
test:Any;
}
root_type Monster;
Выше приведен синтаксис языка схемы.Схема также известна как IDL (язык определения интерфейса).Код очень похож на язык семейства C.
В файле схемы FlatBuffers есть два очень важных понятия: структура и таблица.
1. Table
Таблица — это основной способ определения объектов в FlatBuffers, состоящий из имени (в данном случае Monster) и списка полей. Каждое поле имеет имя, тип и необязательное значение по умолчанию (если оно опущено, оно по умолчанию равно 0 / NULL).
Каждое поле в таблице является необязательным: он не обязательно должен отображаться в представлении проводов и может опционально опускать поля для каждого отдельного объекта. Таким образом, у вас есть возможность добавлять поля, не беспокоясь о раздувании данных. Этот дизайн также является механизмом прямой и обратной совместимости FlatBuffer.
Предположим, что текущая схема выглядит следующим образом:
table { a:int; b:int; }
Теперь хочу внести изменения в эту схему.
Несколько замечаний:
добавить поле
Новые поля можно добавлять только в конце определения таблицы. Старые данные по-прежнему будут считываться правильно и при чтении будут давать вам значения по умолчанию. Старый код просто проигнорирует новое поле. Если вам нужна гибкость для использования любого порядка полей в схеме, вы можете назначить идентификаторы вручную (так же, как буферы протокола), см. ниже.идентификатор атрибута.
Пример:
table { a:int; b:int; c:int; }
Ты можешь это сделать. Чтение новой структуры данных из старой схемы игнорирует существование нового поля c. Новая схема считывает старые данные и получает значение c по умолчанию (в данном случае 0, поскольку оно не указано).
table { c:int a:int; b:int; }
Добавление новых полей в начало не допускается, так как это сделало бы старые и новые версии схемы несовместимыми. При использовании старого кода для чтения новых данных при чтении нового поля c фактически считывается старое поле a. Когда старые данные считываются с новым кодом, когда читается старое поле a, фактически считывается старое поле b.
table { c:int (id: 2); a:int (id: 0); b:int (id: 1); }
Это возможно. Если вы намерены упорядочить/сгруппировать семантику значимым образом, вы можете сделать это с явным назначением идентификатора. После введения id порядок полей в таблице не имеет значения, новая схема полностью совместима со старой схемой, пока мы сохраняем последовательность id.
удалить поле
Поля, которые больше не используются, не могут быть удалены из схемы, но вы можете просто перестать записывать их в данные, и запись и удаление полей имеют почти такой же эффект. Кроме того, они могут быть помечены как устаревшие, как в приведенном выше примере, а отмеченное поле больше не генерирует метод доступа C++, что заставляет поле больше не использоваться. (Внимание: это может нарушить код!).
table { b:int; }
Этот метод удаления полей невозможен. Мы можем удалить поле, только объявив его устаревшим, независимо от того, используется ли явный идентификатор или нет.
table { a:int (deprecated); b:int; }
Возможен и описанный выше подход. При чтении новой структуры данных из старой схемы будет получено значение по умолчанию a, поскольку оно не существует. Код новой схемы не может ни читать, ни записывать (попытка сделать это существующим кодом вызовет ошибку компиляции), но он по-прежнему может читать старые данные (они будут игнорировать это поле).
изменить поле
Имена полей и имена таблиц могут быть изменены, и если ваш код работает нормально, вы также можете изменить их.
table { a:uint; b:uint; }
Непосредственное изменение типа поля может работать, а может и не работать. Это возможно только в том случае, если изменения типа имеют одинаковый размер. Это будет безопасно, если старые данные не содержат отрицательных чисел, если они есть, это изменение будет проблематичным.
table { a:int = 1; b:int = 2; }
Эта модификация невозможна. Любые старые данные, записанные со значением 0, не будут снова записываться в буфер и полагаться на воссозданное значение по умолчанию. Теперь значения будут отображаться как 1 и 2. Бывают случаи, когда это может и не пойти не так, но нужно соблюдать осторожность.
table { aa:int; bb:int; }
Вышеупомянутый метод модификации может вызвать проблемы после изменения исходного имени переменной. Это нарушит весь код (и файлы JSON), использующий эту версию схемы, поскольку поля были переименованы, что несовместимо с фактическим двоичным буфером.
Таблицы являются краеугольным камнем FlatBuffers, поскольку изменения структуры данных необходимы для большинства приложений, требующих сериализации. Как правило, обработка изменений в структурах данных выполняется прозрачно в процессе синтаксического анализа большинства решений для сериализации. Но FlatBuffer не анализируется до тех пор, пока к нему не будет осуществлен доступ.
Чтобы решить проблему изменения структуры данных, таблица косвенно обращается к полям через vtable. Каждая таблица содержит виртуальную таблицу (которая может совместно использоваться несколькими таблицами с одним и тем же макетом) и содержит информацию о полях, в которых хранятся экземпляры этого конкретного типа виртуальной таблицы. Виртуальная таблица также может указывать, что поле не существует (поскольку этот FlatBuffer был написан с использованием более старой версии программного обеспечения, просто потому, что информация не требуется для этого экземпляра или считается устаревшей), и в этом случае возвращается значение по умолчанию.
Накладные расходы памяти таблицы невелики (поскольку виртуальные таблицы небольшие и совместно используемые), а стоимость доступа также невелика (косвенный доступ), но обеспечивает большую гибкость. Таблица может даже занимать меньше памяти, чем эквивалентная структура, поскольку поля не нужно хранить в буфере, когда они равны своим значениям по умолчанию.
2. Structs
структуры очень похожи на таблицы, за исключением того, что в структурах нет полей, которые являются необязательными (и, следовательно, не имеют значений по умолчанию), и поля нельзя добавлять или устареть. Структуры могут содержать только скаляры или другие структуры. Используйте его для простых объектов, если вы уверены, что не будете ничего менять позже (как видно из примера с Vec3). структуры используют меньше памяти, чем таблицы, и доступ к ним осуществляется быстрее (они всегда хранятся объединенными в своих родителях и не используют виртуальные таблицы).
структуры не обеспечивают прямой/обратной совместимости, но занимают меньше памяти. Хранение в виде структуры полезно для очень маленьких объектов, которые вряд ли изменятся (таких как пары координат или цвета RGBA).
3. Types
Скалярные типы, поддерживаемые FlatBuffers, следующие:
- 8 bit: byte (int8), ubyte (uint8), bool
- 16 bit: short (int16), ushort (uint16)
- 32 bit: int (int32), uint (uint32), float (float32)
- 64 bit: long (int64), ulong (uint64), double (float64)
Имена в скобках соответствуют псевдонимам типов.
Нескалярные типы, поддерживаемые FlatBuffers, следующие:
- любой тип массива. Однако вложенные массивы не поддерживаются, и вложенные массивы можно заменить определением массивов в таблице.
- Строки в UTF-8 и 7-битном ASCII. Закодированные строки или двоичные данные в других форматах необходимо заменить на [byte] или [ubyte].
- таблица, структуры, перечисления, объединения
Поле скалярного типа имеет значение по умолчанию, а поле нескалярного типа (строка/вектор/таблица) имеет значение по умолчанию NULL, если оно не имеет значения.
Как только тип объявлен,Старайтесь не менять его тип, как только вы это сделаете, скорее всего, будут ошибки.. Как упоминалось выше, если вы измените int на uint, если данные имеют отрицательное число, произойдет ошибка.
4. Enums
Определите серию именованных констант, каждой именованной константе может быть присвоено фиксированное значение, или оно может быть увеличено на единицу по сравнению с предыдущим значением по умолчанию. Первое значение по умолчанию равно 0. Как видно из объявления перечисления выше, используйте: (byte в приведенном выше примере), чтобы указать базовый целочисленный тип перечисления, а затем определите тип каждого поля, объявленного с этим типом перечисления.
Как правило, вы должны добавлять только значения перечисления, а не удалять их (перечисления не устаревают).Это требует, чтобы код разработчика самостоятельно обрабатывал проблемы прямой совместимости, обрабатывая неизвестные значения перечисления..
5. Unions
Этот тип еще не поддерживается в буферах протоколов..
Union — это концепция языка C. Несколько типов могут быть объединены в объединение и совместно использовать область памяти.
Но в FlatBuffers Unions могут иметь много общих свойств, как Enums, но вместо новых имен для констант использовать имя таблицы. Можно объявить поле Unions, которое может содержать ссылку на любой из этих типов,То есть эта область памяти может использоваться только одним из типов. Кроме того, суффикс с_type
Скрытое поле, содержащее соответствующее значение перечисления, чтобы во время выполнения можно было узнать, в какие типы следует преобразовать.
Union похож на enum, но union содержит таблицу, enum содержит скаляр или структуру.
Объединения — отличный способ отправлять несколько типов сообщений в один FlatBuffer. Обратите внимание: поскольку поле объединения на самом деле представляет собой два поля (со скрытым полем), оно всегда должно быть частью таблицы и не может быть корнем самого FlatBuffer.
Если вам нужно более открыто различать разные FlatBuffers, например файлы, см. ниже.функция идентификации файла.
Наконец, есть экспериментальная функция, которая поддерживается только в реализации версии C++, как в приведенном выше примере [Any] (объединенный массив) добавляется в качестве типа в определение таблицы Monster.
6. Root type
Это объявляет то, что вы считаете корневой таблицей (или структурой) сериализованных данных. Это особенно важно для анализа данных JSON, которые не содержат информации о типе объекта.
7. File identification and extension
Как правило, двоичный буфер FlatBuffer не является самоописываемым, т. е. требует, чтобы вы знали его схему, чтобы правильно анализировать данные. Но если вы хотите использовать FlatBuffer в качестве формата файла, удобно иметь там «магическое число», как и в большинстве форматов файлов, чтобы иметь возможность выполнить полную проверку, чтобы увидеть, читаете ли вы файл, который вы ожидаете тип.
FlatBuffers позволяют разработчикам добавлять свои собственные заголовки к FlatBuffers, но FlatBuffers имеет встроенный метод, который позволяет идентификаторам занимать минимальное пространство, а также делает FlatBuffers совместимыми с FlatBuffers, не имеющими таких идентификаторов.
Способ объявления формата файла аналогичен root_type:
file_identifier "MYFI";
Идентификаторы должны состоять ровно из 4 символов. Эти 4 символа будут [4,7] байтами в конце буфера..
Для любой схемы с таким идентификатором, flatc автоматически добавит идентификатор к любому бинарному файлу, который он сгенерирует (с -b), и сгенерированные вызовы, такие как FinishMonsterBuffer, также добавят идентификатор. Если вы указали идентификатор и хотите сгенерировать буфер без идентификатора, вы можете сделать это, явно вызвав FlatBufferBuilder::Finish.
После загрузки данных буфера вы можете использовать такой вызов, как MonsterBufferHasIdentifier, чтобы проверить, существует ли идентификатор.
Добавление идентификаторов к файлам является лучшей практикой. Если вы просто хотите отправить одно из множества возможных сообщений по сети, лучше всего подойдет Union.
По умолчанию flatc выводит бинарные файлы как.bin
. Это объявление в схеме изменит его на все, что вы хотите:
file_extension "ext";
8. RPC interface declarations
RPC объявляет набор функций, которые принимают FlatBuffer в качестве запроса и возвращают FlatBuffer в качестве ответа (обе функции должны иметь тип table):
rpc_service MonsterStorage {
Store(Monster):StoreResponse;
Retrieve(MonsterId):Monster;
}
Полученный код и способ его использования зависят от используемого языка и системы RPC, и это можно сделать, добавив--grpc
Параметры компиляции, генератор кода будет иметь предварительную поддержку GRPC.
9. Attributes
Атрибуты могут быть прикреплены к объявлениям полей, после поля или после имени таблицы/структуры/перечисления/объединения. Эти поля могут иметь или не иметь значения.
Некоторые атрибуты распознаются только компилятором, например устаревшие. Пользователи также могут определять некоторые Атрибуты, но они должны объявить Атрибуты заранее. Позже объявление можно будет запросить при анализе схемы во время выполнения. Это очень полезно для разработки собственного компилятора/генератора кода. Или хотите добавить некоторую специальную информацию (справочную информацию и т. д.) в свой инструмент FlatBuffers.
Последняя версия распознает 11 атрибутов.
-
id:n
(on a table field)
id представляет установку идентификатора поля на n . После включения этого идентификатора идентификатора все поля должны быть идентифицированы идентификатором, а идентификатор должен быть последовательным числом, начинающимся с 0. Особое внимание следует уделить Union, т.к. Union состоит из 2-х полей, а скрытое поле располагается перед полем union. (Предположим, что идентификатор поля перед объединением равен 6, тогда объединение будет занимать два идентификатора 7 и 8, 7 — скрытое поле, а 8 — поле объединения) После добавления идентификатора идентификатора взаимный порядок полей внутри схемы Не имеет значения. Идентификатор, используемый для нового поля, должен быть следующим доступным идентификатором (идентификатор не может прыгать, он должен быть непрерывным). -
deprecated
(on a field)
deprecated означает, что методы доступа больше не генерируются для этого поля, и код должен перестать использовать эти данные. Старые данные могут по-прежнему содержать это поле, но новый код не может получить к нему доступ. Обратите внимание, что старый код может не проверять новые данные (при использовании дополнительных валидаторов), если вы устарели ранее обязательные поля. -
required
(on a non-scalar table field)
обязательный означает, что это поле нельзя опустить. По умолчанию все поля необязательны, т.е. могут быть опущены. Это желательно, поскольку облегчает прямую/обратную совместимость и гибкость структур данных. Кроме того, чтение кода утомительно, потому что для нескалярных полей требуется проверка на NULL и выполнение соответствующих действий. Указав обязательное поле, вы можете заставить код, создающий FlatBuffers, гарантировать, что это поле инициализировано, чтобы код чтения мог обращаться к нему напрямую, не проверяя наличие NULL. Если код построения не инициализирует это поле, он получит утверждение, что требуемое поле отсутствует. Обратите внимание, что добавление этого свойства в существующее поле будет работать только в том случае, если существующие данные всегда содержат это поле/существующий код всегда записывает в это поле. -
force_align: size
(on a struct)
force_align означает принудительное выравнивание этой структуры выше ее естественного выравнивания. Если буфер создан с объявлением force_align, то все структуры в нем будут принудительно выровнены. (Это не обязательно относится к буферам, доступ к которым осуществляется непосредственно в FlatBufferBuilder ) -
bit_flags
(on an enum)
Значение поля bit_flags представляет биты, что означает, что любое значение N, указанное в схеме, в конечном итоге будет представлять 1 -
nested_flatbuffer: "table_name"
(on a field)
nested_flatbuffer означает, что поле (должно быть массивом байтов), вложенное, содержит данные плоского буфера, корневой тип которых задается table_name. Сгенерированный код сгенерирует удобный метод доступа для вложенного FlatBuffer. -
flexbuffer
(on a field)
flexbuffer указывает, что это поле (должно быть массивом байтов) содержит данные flexbuffer. Сгенерированный код создаст удобный метод доступа к корню FlexBuffer. -
key
(on a field)
Ключевое поле используется в текущей таблице и используется как ключ при сортировке массива своего типа. Может использоваться для бинарного поиска на месте. -
hash
(on a field)
Это 32/64-битное целочисленное поле без знака, потому что его значение может быть строкой во время синтаксического анализа JSON, которое затем сохраняется как его хэш. Значением этого свойства является алгоритм хэширования, т. е. используйте один из fnv1_32, fnv1_64, fnv1a_32, fnv1a_64. -
original_order
(on a table)
Поскольку элементы в таблице не обязательно хранить в каком-либо определенном порядке, они обычно сортируются по размеру для оптимизации занимаемого места. А original_order предотвращает это. Как правило, не должно быть никаких причин для использования этого флага. - 'native_*'
Несколько свойств были добавлены для поддержки на основеAPI для объектов C++, все эти свойства имеют префикс "native_". В частности, вы можете нажатьСсылка на сайтОзнакомьтесь с поддерживаемыми инструкциями,native_inline
,native_default
,native_custom_alloc
,native_type
,native_include: "path"
.
10. Предложения по дизайну
FlatBuffers — это эффективный формат данных, но для достижения эффективности вам нужна эффективная схема. Часто существует несколько вариантов представления данных с характеристиками совершенно разных размеров.
Благодаря гибкости и расширяемости FlatBuffers очень распространена практика представления любого типа данных в виде словаря (как в JSON). Хотя это можно эмулировать в FlatBuffers (в виде массивов таблиц с ключами и значениями), это неэффективный способ сделать это со строго типизированной системой, такой как FlatBuffers, что приводит к относительно большому двоичному файлу. В большинстве систем таблицы FlatBuffer более гибкие, чем классы/структуры, потому что таблица обрабатывает очень большое количество полей, но фактическое использование лишь нескольких из них по-прежнему очень эффективно. Поэтому организационные данные должны быть максимально организованы в виде таблицы.
Точно так же, если возможно, попробуйте использовать форму перечисления вместо строки.
В FlatBuffers нет концепции наследования, поэтому вы хотите представить группу связанных структур данных с помощью объединения. Тем не менее, объединение имеет свою цену, другой эффективный способ — создать таблицу. Если в этих структурах данных много полей, которые похожи или могут использоваться совместно, очень эффективно предложить таблицу. Достаточно включить в эту таблицу все поля всех структур данных. Причина эффективности в том, что необязательные поля очень дешевы и менее дороги.
FlatBuffers могут поддерживать все целые числа, хранящиеся по умолчанию, поэтому попробуйте выбрать минимальный требуемый размер вместо значения по умолчанию int/long.
Вы можете рассмотреть возможность использования строки или таблицы в буфере для обмена некоторыми общими данными, что повысит эффективность, поэтому стоит разделить повторяющиеся данные на общие структуры данных + частные структуры данных.
5. JSON-разбор FlatBuffers
FlatBuffers поддерживает синтаксический анализ JSON в собственном формате. То есть синтаксический анализатор, анализирующий схему, также может анализировать объекты JSON, соответствующие правилам схемы. Таким образом, в отличие от других парсеров JSON, этот парсер строго типизирован, и результатом синтаксического анализа являются просто FlatBuffers. Дополнительные сведения см. в документации flatc и документации FlatBuffers, соответствующей C++, чтобы узнать, как анализировать JSON во FlatBuffers во время выполнения.
Для разбора JSON, помимо определения схемы, парсер FlatBuffers имеет следующие изменения:
- Он принимает имена полей с кавычками и без них, как это уже делают многие парсеры JSON. Он также может выводить их без кавычек, но вы можете использовать
strict_json
флаг для их вывода. - Если поле имеет тип перечисления, синтаксический анализатор обрабатывает значение перечисления идентификатора перечисления (с кавычками или без них) вместо числа, например, поле: EnumVal. Если поле имеет целочисленный тип, вы все равно можете использовать символические имена, но значения должны начинаться с их типа и заключаться в кавычки. поле: "Enum.EnumVal". Для перечислений, представляющих флаги, вы можете вставлять пробелы в несколько строк или использовать синтаксис с точками, например. поле: "EnumVal1 EnumVal2" или поле: "Enum.EnumVal1 Enum.EnumVal2".
- Для объединения их необходимо указать с двумя полями, как при сериализации из кода. Например. Для поля foo вы должны добавить перед полем foo
foo_type:FooOne
, FooOne — это таблица, которую можно использовать вне объединения. - Если значение поля равно null (например, field: null ), это означает, что поле имеет значение по умолчанию (что имеет тот же эффект, что и полное отсутствие указания поля).
- Парсер имеет встроенные функции преобразования, так что вы можете использовать функцию rad(180) вместо записи 3.14159. В настоящее время поддерживаются следующие функции: rad, deg, cos, sin, tan, acos, asin, atan.
При разборе JSON парсер распознает следующие escape-коды в строках:
\n
- Новая линия.
\t
- Этикетка.
\r
- Входить.
\b
- назад.
\f
- Подача формы.
\“
- Двойные кавычки.
\\
- обратная косая черта.
\/
- Косая черта.
\uXXXX
- 16-битный юникод, преобразованный в эквивалентное представление UTF-8.
\xXX
- 8-битное двоичное шестнадцатеричное число ХХ. Это единственное место, которое не является частью спецификации JSON (см.json.org/), но должна иметь возможность кодировать произвольный двоичный код в строке в текст и обратно без потери информации (например, байты 0xFF не могут быть представлены в виде стандартного JSON).
Он также снова генерирует эти escape-коды при преобразовании JSON из двоичного в обратное представление.
6. Соглашение об именовании FlatBuffers
Идентификаторы в схеме предназначены для перевода на множество различных языков программирования, поэтому изменение стиля кодирования схемы на стиль, используемый текущим языком проекта, является ошибкой. Стиль кода схемы следует сделать более общим.
- В именах (типах) таблиц, структур, enum и rpc используется верхний верблюжий регистр.
- В именах полей таблиц и структур используется символ подчеркивания. Этот метод автоматически генерирует код нижнего регистра верблюда.
- Значения Enum используют верхний верблюжий регистр.
- пространства имен используют верхний верблюжий регистр.
Есть еще 2 предложения по формату написания:
- Фигурные скобки: в той же строке, что и начало объявления.
- Интервал: отступ на 2 пробела.
:
С обеих сторон нет пробелов,=
По одному месту с каждой стороны.
7. FlatBuffers какие-то "ямы"
Большинство сериализуемых форматов (таких как JSON или протокольные буферы) очень точно указывают, существует ли поле в объекте и может ли оно использоваться в качестве «дополнительной» информации.
Но в FlatBuffers это также работает для всего, кроме скалярных значений. FlatBuffers по умолчанию не будет записывать поля, равные значению по умолчанию (для скаляров), что экономит много места. Однако это также означает, что проверка того, существует ли поле, несколько бессмысленна, поскольку она не скажет вам, было ли поле установлено вызовом метода add_field, если только вас не интересует информация, отличная от значения по умолчанию. Значение по умолчанию не записывается в буфер.
Изменяемый FlatBufferBuilder реализует метод force_defaults, который позволяет избежать такого поведения, поскольку поля записываются, даже если они равны значениям по умолчанию. Затем вы можете использовать IsFieldPresent, чтобы узнать, существует ли поле в буфере.
Другой способ — обернуть скалярные поля в структуру. Таким образом, он вернет null, если он не существует. Самое замечательное в этом подходе то, что структуры не занимают больше места, чем скаляры, которые они представляют.
8. Наконец
Прочитав этот принцип кодирования FlatBuffers, читатели должны понять следующие моменты:
По сравнению с буферами протокола файл определения структуры данных FlatBuffers имеет следующие «улучшения»:
- Устаревшие поля, нет необходимости вручную назначать идентификаторы полей. существует
.proto
Расширение объекта в числе требует поиска свободного слота в числе (поскольку буферы протоколов имеют более компактное представление, необходимо выбрать меньшее число). Помимо этого неудобства, это также делает проблематичным удаление полей: если вы их сохраняете, семантически не очевидно, что поле нельзя прочитать или записать, а если вы их сохраните, будет сгенерирован метод доступа. Если вы удалите их, есть риск серьезных ошибок, потому что когда кто-то повторно использует эти идентификаторы, это приведет к считыванию старых данных, и данные будут повреждены. - FlatBuffers различают таблицу и структуру. Все поля таблицы необязательны, а все поля структуры обязательны.
- FlatBuffers имеют собственные типы массивов вместо повторяющихся. Это дает длину без необходимости собирать все элементы, а в скалярном случае обеспечивает более компактное представление и гарантирует смежность.
- FlatBuffers имеют тип union, которого нет у протокольных буферов. Объединение может заменить множество необязательных полей, что также экономит время на проверку каждого поля по одному.
- FlatBuffers могут определять значения по умолчанию для всех скаляров без необходимости иметь дело с их необязательными параметрами каждый раз, когда к ним обращаются, а значения по умолчанию не существуют в буфере, и вам не нужно беспокоиться о проблемах с пространством.
- Синтаксический анализатор, который единообразно обрабатывает определения схемы и данных (и совместим с JSON). Буферы протоколов несовместимы с JSON. Параметры, которые может принимать компилятор flatc для FlatBuffers, также более мощные.Подробности см. в списке параметров, которые можно использовать.этот документ
- Схема расширяет некоторые атрибуты, которых нет у буферов протокола.
Помимо функциональных различий, есть некоторые тонкие различия в синтаксисе схемы:
- Определите объект, буферы протокола — это сообщения, FlatBuffers — это таблица.
- ID, буферы протоколов по умолчанию начинаются с 1, а FlatBuffers по умолчанию начинаются с 0.
Для всего синтаксиса схемы вы можете обратиться кэтот документ
Принципиальный анализ и анализ исходного кода, связанные с производительностью кодирования и декодирования плоских буферов, будут выполнены в следующей части.
Ссылка:
Официальная документация плоских буферов
Improving Facebook's performance on Android with FlatBuffers
Репозиторий GitHub:Halfrost-Field
Follow: halfrost · GitHub
Source: Буфер HAL frost.com/flat…