Сравнение форматов и библиотек сериализации / Антон...
TRANSCRIPT
Сравнение форматов и библиотек сериализацииАнтон Рыжов, Qrator Labs
Задача
Сериализовать данные:● Без потерь● Компактно● Быстро● Удобно
JSON
✔ Привычно
✔ Удобно
✔ Человекочитаемо
[
{
"very": "simple"
}
]
JSON
✔ Привычно
✔ Удобно
✔ Человекочитаемо
✗ Не компактно
[
{
"very": "simple"
},
{
"very": "big"
}
]
JSON — компактность
7 1 byte (1:1)→
JSON — компактность
7 1 byte (1:1)→
65535 5 bytes (5:2)→
1000 000 000 10 bytes (10:4)→
JSON — компактность
3 / 2 1.5 (3 byte)→
JSON — компактность
3 / 2 1.5 (3 byte)→
1 / 3 0.3333333333333333 (18 bytes)→
JSON — компактность
Hello "Hello" (+2 bytes)→
JSON — компактность
Hello "Hello" (+2 bytes)→
"\u0420\u0418\u0422" (×3)РИТ →
JSON — компактность
Hello "Hello" (+2 bytes)→
"\u0420\u0418\u0422" (×3)РИТ →
" " ( )РИТ → РИТ опция
JSON — компактность
[
{"userId": 1, "userName": "John Smith"},
{"userId": 2, "userName": "Jane Dow"}
]
JSON — компактность
[
{"userId": 1, "userName": "John Smith"},
{"userId": 2, "userName": "Jane Dow"}
]
JSON — компактность
[
"keys": ["userId", "userName"],
"values": [
[1, "John Smith"],
[2, "Jane Dow"]
]
]
JSON — целостность
dict(1: "foo") { "1": "foo" }→
JSON — целостность
dict(1: "foo") { "1": "foo" }→
object = { "type_id": 5 }
types = { "5": "Type name" }
JSON — целостность
Python JSON:→{ 1: "foo", "1": "bar" } ?→
PHP JSON:→
Array(1=> "foo", "1"=> "bar") ?→
JSON — целостность
Python JSON:→{ 1: "foo", "1": "bar" } {"1": "bar", "1": "foo"}→
PHP JSON:→
Array(1=> "foo", "1"=> "bar") {"1": "bar"}→
JSON — целостность
Python JSON:→{ 1: "foo", "1": "bar" } {"1": "bar", "1": "foo"}→
PHP → PHP:
Array(1=> "foo", "1"=> "bar") → Array(1=> "bar")
JSON — binary
{"message": "0KDQmNCiKys="} +30%
JSON — binary
{"message": "0KDQmNCiKys="} +30%
{"message_bin": "0KDQmNCiKys="}
{"message": { "type": "bin", "value": "0KDQmNCiKys="}}
Альтернативы
Схема данных Binary Ключи map
BSON ✗ ✓ str
Msgpack ✗ ✓ any
Google Protobuf ✓ ✓ ✗Apache Thrift ✓ ✓ any
Apache Avro ✓ ✓ str
Зачем схема?
✔Описывает данные
Зачем схема?
✔Описывает данные
✔Уменьшает дублирование
Зачем схема?
✔Описывает данные
✔Уменьшает дублирование
✔Валидирует данные
Зачем схема?
✔Описывает данные
✔Уменьшает дублирование
✔Валидирует данные
✔Приводит типы
Зачем схема?
✔Описывает данные
✔Уменьшает дублирование
✔Валидирует данные
✔Приводит типы
✗ Надо писать
Зачем схема?
✔Описывает данные
✔Уменьшает дублирование
✔Валидирует данные
✔Приводит типы
✗ Надо писать
✗ … а иногда ещё и компилировать
Protobuf
✔ Старое проверенное решение
✔ Разработан Google
✔ Хорошие результаты в тестах в интернете
✔ Хорошая документация
Protobuf
✔ Старое проверенное решение
✔ Разработан Google
✔ Хорошие результаты в тестах в интернете
✔ Хорошая документация
✗ Не всё можно описать– Список списков, map
Protobuf — схема
message Point {
required int32 x = 1;
required int32 y = 2;
}
message PointsList {
repeated Point points = 1;
}
Thrift
● Более новый формат● Разработал Facebook, отдал в Apache● Документация — справочник типов и BNF● Есть “Thrift: The Missing Guide”● RPC-клиент/сервер
Thrift — схема
struct PointsList {
1: required list< list<i32> > points,
}
Apache Avro
● Ещё более новый формат● Разработан для Apache Hadoop● Документация на схему — подробная● Кодогенерации нет
Avro — схема
{
"namespace": "test.avro",
"type": "array",
"items": {
"type": "array",
"items": "int"
}
}
Размер integer: «0»; 1-, 2-, 4-byte
Msgpack
Avro
Protobuf
JSON
Thrift
BSON*
0 2 4 6 8 10 12
Размер float: «0», 2.5, 1/3, 1e9/3
Avro
Protobuf
Msgpack
Thrift
BSON*
JSON
0 2 4 6 8 10 12 14 16 18 20
Размер string: "", "Z"×10, "Я"×10
Msgpack
Avro
Protobuf
JSON
Thrift
BSON*
0 5 10 15 20 25 30 35
Размер string: "Z"×1000, "Я"×1000
Msgpack
Avro
Protobuf
Thrift
BSON*
JSON
0 500 1000 1500 2000 2500
Размер 0, 10 bytes
Avro
Msgpack
Protobuf
Thrift
JSON
BSON*
0 5 10 15 20 25
Размер 1000 bytes
Avro
Msgpack
Protobuf
Thrift
BSON*
JSON
0 200 400 600 800 1000 1200 1400 1600
Размер array [0], [10]
Avro
Msgpack
Protobuf
JSON
Thrift
BSON*
0 5 10 15 20 25
Размер map [0], [10]
Msgpack
Avro
Protobuf*
JSON
BSON
Thrift
0 2 4 6 8 10 12 14 16 18 20
Размер struct vs map
Msgpack
Avro
Protobuf*
BSON
JSON
Thrift
0 20 40 60 80 100 120 140 160 180 200
Топ по размеру
1.Apache Avro
2.Msgpack
3.Google Protobuf
4.Apache Thrift
5.JSON
6.BSON
Методика бенчмарка
Характерные объекты — строки, байты, словари разных размеров, массивы
Замер времени на сериализацию и десериализацию каждой библиотекой в python2 и python3
Сравнение данных до и после
Железо:
Intel(R) Xeon(R) CPU E5-2683 v4 @ 2.10GHz
64 Gb RAM
Без схемы
Поддержка Установка Реализация Генерация кода
uJSON 2, 3 pip C-ext ✗BSON 2, 3 pip Python ✗
Msgpack 2, 3 pip C-ext ✗
Без схемы — затраченное время
msgpack
ujson
json
bson
encode py2.7
decode py2.7
encode py3.5
decode py3.5
Protobuf
Поддержка Установка Реализация Генерация кодаprotobuf 2, 2to3 make + pip Python ✓cprotobuf 3 protobuf + pip C-ext ✓protobuf3 3 protobuf + pip Python ✓
Protobuf — пример кода protobuf, cprotobuf
data = test_pb2.PointsList(
points=[
test_pb2.Point(x=1, y=2),
test_pb2.Point(x=3, y=4),
]
).SerializeToString()
pl = test_pb2.PointsList()
pl.ParseFromString(data)
print(pl.points[0].x)
Protobuf — пример для protobuf3
pl = test_pb2.PointsList()
po = test_pb2.Point()
po.x = 1; po.y = 2
pl.points.append(po)
data = pl.encode_to_bytes()
pl = test_pb2.PointsList()
pl.parse_from_bytes(data)
print(pl.points[0].x)
Protobuf — затраченное время
cprotobuf*
google_proto
protobuf3*
encode py2.7
decode py2.7
encode py3.5
decode py3.5
Thrift
Поддержка Установка Реализация Генерация кода
(apache) thrift
2 make + pip Python ✓(facebook)
Thrift2, 3 ☠nightmare☠ Python ✓
thriftpy 2, 3 pip Cython ✗thriftrw 2, 3 pip Cython ✗
(Apache) thrift — пример кода
data = serialize(
test_ttypes.PointsList([(1, 2)])
)
points = deserialize(
test_ttypes.PointsList(),
points_list,
).points
print(points[0][0])
(facebook)Thrift, thriftpy, thriftrw
Совместим с (Apache) thrift по API
Thrift — затраченное время
thriftrw
fb_thrift**
apache_thrift*
thriftpy+cython*
thriftpy*
Py2.7 Py3.5
Thrift — затраченное время
thriftrw
fb_thrift**
apache_thrift*
thriftpy+cython*
thriftpy*
encode py2.7
decode py2.7
encode py3.5
decode py3.5
Avro
Поддержка Установка Реализация Генерация кода(apache)
avro2, 3
2 pip3 src
Python ✗fastavro 2, 3 pip Cython ✗pyavroc 2, 3 shell + make libavro + wrapper ✗
(Apache) avro — пример упаковки
schema = avro.schema.parse(open('points_list.avsc', 'r').read())
io_stream = io.BytesIO()
avro.io.DatumWriter(schema).write(
[(1, 2), (3, 4)],
avro.io.BinaryEncoder(io_stream),
)
data = io_stream.getvalue()
(Apache) avro — пример распаковки
schema = avro.schema.parse(open('points_list.avsc', 'r').read())
io_stream = io.BytesIO(data)
points = avro.io.DatumReader(schema).read(
avro.io.BinaryDecoder(io_stream),
)
print(points[0][0])
fastavro — пример упаковки
schema = json.loads(open('points_list.avsc', 'r').read())
io_stream = io.BytesIO()
fastavro.schemaless_writer(
io_stream,
schema,
[(1, 2), (3, 4)],
)
return io_stream.getvalue()
fastavro — пример распаковки
schema = json.loads(open('points_list.avsc', 'r').read())
io_stream = io.BytesIO(points_list)
points = fastavro.schemaless_reader(
io_stream,
schema,
)
print(points[0][0])
pyavroc — пример кода
schema = open('specs/points_list.avsc', 'r').read()
points_writer = _pyavroc.AvroSerializer(schema)
data = points_writer.serialize(
[(1, 2), (3, 4)],
)
points_reader = _pyavroc.AvroDeserializer(schema)
points = points_reader.deserialize(data)
print(points[0][0])
Avro — затраченное время
pyavroc*
fastavro
avro
encode py2.7
decode py2.7
encode py3.5
decode py3.5
Топ — затраченное время
pyavroc*
msgpack
cprotobuf*
thriftrw
ujson
fastavro
thriftpy+cython*
Py2.7 Py3.5
Общие рекомендации
● Следить за новыми разработками● Рассматривать альтернативы● Референсная библиотека — не всегда самая лучшая● Альтернативные — бывают ещё хуже● Не верить бенчмаркам в интернете (В том числе
этому)● Кодогенерация только мешает
Всё
[email protected]://github.com/QratorLabs/ritfest2016