Жизнеспособная альтернатива для хранения больших конфигураций

Если вы используете JSON, CSV, XML или даже SQLite в своей игре для хранения данных, вам обязательно нужно уделить несколько минут чтению этого сообщения в блоге. Если нет, вы все равно можете узнать что-то полезное 😀.

WTF это FlexBuffers?

FlexBuffers — это двоичный формат, сопоставимый с JSON, разработанный как альтернатива без схемы в проекте FlatBuffers, который изначально был создан в Google.

Я понял тебя в Google, верно?

Если вас интересуют корни, перейдите по ссылке.

Что вы подразумеваете под сравнением JSON?

Под этим я подразумеваю, что все, что вы можете представить с помощью JSON, вы также можете представить с помощью FlexBuffers и наоборот. Вот почему FlexBuffers Unity предоставляет простой способ преобразования файла JSON в файл FlexBuffer. И экспортируйте файл FlexBuffer как файл JSON. Но об этой теме чуть позже.

Почему я должен использовать FlexBuffers вместо JSON?

Файл JSON представляет собой текстовый файл. Чтобы получить доступ к данным из файла JSON, нам нужно преобразовать текст в дерево объектов C#. Это означает, что мы должны прочитать содержимое файла в память, проанализировать текст и создать дерево объектов C#. Существуют разные анализаторы JSON, которые могут сделать некоторые шаги более или менее эффективными, но парсер JSON не может избежать одного из шагов.

Когда вы читаете значения из файла FlexBuffers, есть только два шага:

  1. Загрузить содержимое файла в память
  2. Доступ к значению в массиве байтов

С помощью FlexBuffers мы не отражаем структуру ваших данных в виде дерева объектов C#. Мы можем напрямую считывать данные из массива байтов, используя следующее выражение:

var root = FlxValue.FromBytes(bytes);
Assert.AreEqual("Berlin", root["address"]["city"].AsString);

С помощью этого выражения мы говорим, что root имеет свойство address, и это свойство имеет собственное свойство, называемое city. Мы знаем, что city имеет тип string, и просим преобразовать это значение в объект типа C# string.

Обход байтов на основе семантики доступа [] происходит довольно быстро, в чем мы можем убедиться, изучив следующий снимок экрана профилировщика:

Здесь вы видите, как я нажимаю кнопку пользовательского интерфейса, которая загружает в память файл FlexBuffer размером 161 КБ (см. TextAsset.get_bytes() (0.38ms)), который содержит вектор из 1000 записей, а затем мы получаем доступ к 50 записям из этого вектора:

var bytes = Resources.Load<TextAsset>("flx_data").bytes;
var root = FlxValue.FromBytes(bytes);
var names = new List<string>(50);
for (var i = 0; i < 50; i++)
{
    names.Add(root[i]["name"].AsString);
}
Debug.Log(names);

50 сосулек в середине показывают время, необходимое для доступа к name в виде строки из массива байтов. Вот скриншот, на котором я увеличиваю сосульки:

Это доказывает, что доступ к значению сам по себе является очень легкой операцией. (Что, вероятно, можно было бы оптимизировать еще больше).

Сравним аналогичный процесс с использованием собственного JSONUtility Unity:

Файл JSON, содержащий те же данные, которые занимают 246 КБ, загружается за 1,28 мс. Это связано с тем, что JSON-представление тех же данных на 80 КБ (35%) больше, а также с тем, что в случае FlexBuffers нам нужен просто массив необработанных байтов, а в случае JSON нам нужен UTF-16. закодированный объект C# string.

Затем мы также видим, что большая часть времени тратится на JsonUtility.FromJsonInternal(), так как нам нужно проанализировать и преобразовать string в объекты C#.

var jsonText = Resources.Load<TextAsset>("json_data2").text;
var cities = JsonUtility.FromJson<CityList>(jsonText);
var names = new List<string>(50);
for (var i = 0; i < 50; i++)
{
    names.Add(cities.list[i].name);
}
Debug.Log(names);

Фактический доступ к значениям и добавление их в список строк совершенно незначительны:

Таким образом, основным узким местом производительности JSON является этап синтаксического анализа. Если вам интересно, лучше ли другие парсеры JSON, чем Unity, JsonUtility. Посмотреть на себя.

Вот тест с использованием библиотеки Utf8Json, которая заявляет о себе как:

Определенно самый быстрый сериализатор JSON с нулевым выделением памяти для C# (NET, .NET Core, Unity, Xamarin)

JsonSerializer.Deserialize() запускается за 47,87 мс.

И тестовый код ничем не отличается:

var jsonText = Resources.Load<TextAsset>("json_data2").text;
var cities = Utf8Json.JsonSerializer.Deserialize<CityList>(jsonText);
var names = new List<string>(50);
for (var i = 0; i < 50; i++)
{
    names.Add(cities.list[i].name);
}
Debug.Log(names);

Если вы считаете, что это плохо, взгляните на этот скриншот для Json.Net:

С Json.Net нам удается разобрать файл JSON за 447,67 мс. Таким образом, примерно в 10 раз медленнее, чем Utf8Json и в 100 раз медленнее, чем JsonUtility.

var jsonText = Resources.Load<TextAsset>("json_data").text;
var jArray = JsonConvert.DeserializeObject(jsonText) as JArray;
var names = new List<string>(50);
for (var i = 0; i < 50; i++)
{
    names.Add(jArray[i]["name"].ToString());
}
Debug.Log(names);

С Json.Net мы также не преобразовываем текст JSON в экземпляры класса C#. Мы преобразуем структуру в общие экземпляры JArray и JObject. Доступ к значениям из этих экземпляров приводит к некоторым накладным расходам, которые немного ниже, чем те, которые мы видели для FlexBuffers:

Оплата авансом по сравнению с оплатой по мере использования

Как видно из этого короткого сеанса профилирования, с помощью FlexBuffers мы платим за загрузку данных в память и за каждое значение, к которому нам нужно получить доступ. С JSON у нас есть большой авансовый налог из-за необходимого шага синтаксического анализа. Налог также зависит от размера файла, а не от количества значений, которые необходимо извлечь из файла.

Вот почему FlexBuffers имеет смысл, когда речь идет о файлах конфигурации. Файл конфигурации часто содержит данные для всех этапов вашей игры. Но вы получите доступ только к небольшой части данных, в зависимости от прогресса игрока. То же самое касается файлов локализации, файлов сложного уровня и других коллекций данных. Я знаю, что некоторые игры используют SQLite для чтения «случайных» данных во время выполнения. Я считаю, что во многих случаях файлы FlexBuffers были бы лучшим решением.

Но читабельны ли FlexBuffers для человека?

FlexBuffers не является текстовым форматом, поэтому вы не сможете открыть файл FlexBuffers в текстовом редакторе, однако FlexBuffer Unity имеет окно браузера FlexBuffer:

Здесь вы можете видеть, что я смог открыть сложный файл FlexBuffers размером 127,1 МБ и перемещаться по нему без каких-либо значительных задержек. По моему опыту, ни один текстовый редактор не откроет файл размером 127 МБ с сопоставимой скоростью.

Могу ли я искать значения в файле FlexBuffer?

В настоящее время FlexBuffer Browser имеет только функцию фильтрации путей:

Как видите, мы можем определить путь и диапазоны векторов, чтобы уменьшить количество дочерних элементов дерева. Полный поиск в настоящее время невозможен, но может быть реализован в будущем выпуске 😉.

Как создать файл FlexBuffer?

Вы можете создать FlexBuffer программно, но это, вероятно, не то, что вы хотите делать для большой конфигурации. Вот почему я представил следующие пункты меню:

Выбрав JSON as FlexBuffer... CSV as FlexBuffer... или XML as FlexBuffer..., вы увидите средство выбора файлов для выбора исходного файла и другое средство выбора файлов для выбора места хранения .bytes файла, содержащего FlexBuffer.

Что делать, если я хочу что-то изменить в моем существующем файле FlexBuffers?

Изменение данных непосредственно в файле FlexBuffers, к сожалению, не так уж и тривиально. Однако мы можем экспортировать файл FlexBuffer в формате JSON:

Откройте файл JSON в текстовом редакторе и отредактируйте его:

Нажмите кнопку «Импорт из JSON»:

Как я могу попробовать это сам?

FlexBuffers Unity основан на FlexBuffers-CSharp. А также имеет самодостаточный собственный репозиторий Github:



Который структурирован как пакет Unity. Итак, если вы хотите использовать FlexBuffers Unity в своей игре, просто поместите следующую строку в файл manifest.json:

"com.mzaks.flexbuffers": "https://github.com/mzaks/FlexBuffersUnity.git",

Спасибо за прочтение, и, пожалуйста, дайте мне знать, если вы пробовали использовать FlexBuffers Unity.