Почему сериализация json намного быстрее, чем сериализация yaml в Python? - PullRequest
51 голосов
/ 16 марта 2010

У меня есть код, который сильно зависит от yaml для межязыковой сериализации, и, работая над ускорением некоторых вещей, я заметил, что yaml был безумно медленным по сравнению с другими методами сериализации (например, pickle, json).

Итак, что меня действительно поражает, так это то, что json намного быстрее, чем yaml, когда вывод почти идентичен.

>>> import yaml, cjson; d={'foo': {'bar': 1}}
>>> yaml.dump(d, Dumper=yaml.SafeDumper)
'foo: {bar: 1}\n'
>>> cjson.encode(d)
'{"foo": {"bar": 1}}'
>>> import yaml, cjson;
>>> timeit("yaml.dump(d, Dumper=yaml.SafeDumper)", setup="import yaml; d={'foo': {'bar': 1}}", number=10000)
44.506911039352417
>>> timeit("yaml.dump(d, Dumper=yaml.CSafeDumper)", setup="import yaml; d={'foo': {'bar': 1}}", number=10000)
16.852826118469238
>>> timeit("cjson.encode(d)", setup="import cjson; d={'foo': {'bar': 1}}", number=10000)
0.073784112930297852

Pyaaml CSafeDumper и cjson написаны на C, поэтому не похоже, что это проблема скорости C и Python. Я даже добавил некоторые случайные данные, чтобы посмотреть, выполняет ли cjson какое-либо кэширование, но это все же намного быстрее, чем PyYaml. Я понимаю, что yaml - это расширенный набор json, но как может сериализатор yaml быть на 2 порядка медленнее при таком простом вводе?

Ответы [ 5 ]

57 голосов
/ 16 марта 2010

Как правило, не сложность вывода определяет скорость разбора, а сложность принятого ввода. Грамматика JSON очень краткая . Парсеры YAML сравнительно сложны , что приводит к увеличению накладных расходов.

Главная цель JSON - простота и универсальность. Таким образом, JSON тривиален для генерации и анализа, ценой сокращения человека читаемость. Он также использует самый низкий информационная модель общего знаменателя, обеспечение любых данных JSON может быть легко обрабатывается каждым современным программированием окружающая среда.

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

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

Обновление Упс, неправильно прочитал вопрос. :-( Сериализация все еще может быть невероятно быстрой, несмотря на большую входную грамматику, однако, просматривая исходный код, похоже, что сериализация PyYAML на уровне Python создает граф представления , тогда как simplejson кодирует встроенные типы данных Python непосредственно в текстовые блоки.

28 голосов
/ 17 февраля 2012

В приложениях, над которыми я работал, вывод типа между строками и числами (float / int) - это то место, где самые большие издержки при разборе yaml, потому что строки могут быть написаны без кавычек. Поскольку все строки в json заключены в кавычки, при синтаксическом анализе строк не происходит возврата. Отличным примером, где это может замедлиться, является значение 0000000000000000000s. Вы не можете сказать, что это значение является строкой, пока не прочитаете ее до конца.

Другие ответы верны, но это конкретная деталь, которую я обнаружил на практике.

20 голосов
/ 16 марта 2010

Говоря об эффективности, какое-то время я использовал YAML, и меня привлекла простота, которую некоторые назначения имени / значения выполняют на этом языке. Тем не менее, в процессе, о котором я так часто говорил о одной из сфер деятельности YAML, о тонких вариациях грамматики, которые позволяют вам писать особые случаи в более лаконичном стиле и тому подобное. В конце концов, хотя грамматика YAML почти наверняка формально последовательна, она оставила у меня определенное чувство «неопределенности». Затем я ограничил себя тем, чтобы не трогать существующий работающий код YAML и писать все новое в более окольном, отказоустойчивом синтаксисе, что заставило меня отказаться от всего YAML. В результате YAML пытается выглядеть как стандарт W3C и выпускает небольшую библиотеку трудно читаемой литературы, касающейся ее концепций и правил.

Я чувствую, что это намного больше интеллектуальных затрат, чем нужно. Посмотрите на SGML / XML: разработанный IBM в бурные 60-е годы, стандартизированный ISO, известный (в упрощенном и измененном виде) как HTML для бесчисленных миллионов людей, документированный и задокументированный и вновь задокументированный во всем мире. Приходит маленький JSON и убивает этого дракона. Как JSON может стать настолько широко используемым за такое короткое время, имея всего один скудный веб-сайт (и javascript luminary для его поддержки)? Он в своей простоте, абсолютном отсутствии сомнений в грамматике, в простоте изучения и использования.

XML и YAML трудны для людей, и они трудны для компьютеров. JSON довольно дружелюбен и прост как для людей, так и для компьютеров.

12 голосов
/ 16 марта 2010

Беглый взгляд на python-yaml предполагает, что его дизайн гораздо сложнее, чем у cjson:

>>> dir(cjson)
['DecodeError', 'EncodeError', 'Error', '__doc__', '__file__', '__name__', '__package__', 
'__version__', 'decode', 'encode']

>>> dir(yaml)
['AliasEvent', 'AliasToken', 'AnchorToken', 'BaseDumper', 'BaseLoader', 'BlockEndToken',
 'BlockEntryToken', 'BlockMappingStartToken', 'BlockSequenceStartToken', 'CBaseDumper',
'CBaseLoader', 'CDumper', 'CLoader', 'CSafeDumper', 'CSafeLoader', 'CollectionEndEvent', 
'CollectionNode', 'CollectionStartEvent', 'DirectiveToken', 'DocumentEndEvent', 'DocumentEndToken', 
'DocumentStartEvent', 'DocumentStartToken', 'Dumper', 'Event', 'FlowEntryToken', 
'FlowMappingEndToken', 'FlowMappingStartToken', 'FlowSequenceEndToken', 'FlowSequenceStartToken', 
'KeyToken', 'Loader', 'MappingEndEvent', 'MappingNode', 'MappingStartEvent', 'Mark', 
'MarkedYAMLError', 'Node', 'NodeEvent', 'SafeDumper', 'SafeLoader', 'ScalarEvent', 
'ScalarNode', 'ScalarToken', 'SequenceEndEvent', 'SequenceNode', 'SequenceStartEvent', 
'StreamEndEvent', 'StreamEndToken', 'StreamStartEvent', 'StreamStartToken', 'TagToken', 
'Token', 'ValueToken', 'YAMLError', 'YAMLObject', 'YAMLObjectMetaclass', '__builtins__', 
'__doc__', '__file__', '__name__', '__package__', '__path__', '__version__', '__with_libyaml__', 
'add_constructor', 'add_implicit_resolver', 'add_multi_constructor', 'add_multi_representer', 
'add_path_resolver', 'add_representer', 'compose', 'compose_all', 'composer', 'constructor', 
'cyaml', 'dump', 'dump_all', 'dumper', 'emit', 'emitter', 'error', 'events', 'load', 
'load_all', 'loader', 'nodes', 'parse', 'parser', 'reader', 'representer', 'resolver', 
'safe_dump', 'safe_dump_all', 'safe_load', 'safe_load_all', 'scan', 'scanner', 'serialize', 
'serialize_all', 'serializer', 'tokens']

Более сложные конструкции почти всегда означают более медленные конструкции, и это намного сложнее, чем большинству людей когда-либо понадобится.

2 голосов
/ 03 сентября 2018

Хотя у вас есть принятый ответ, к сожалению, это только делает некоторые рукопожатия в направлении документации PyYAML и цитирует утверждение в этой документации, которое является неправильным: PyYAML не создает граф представления во время дампа, он создает Lineair Stream (и, как и json, хранит список идентификаторов, чтобы увидеть, есть ли рекурсии).


Прежде всего, вы должны понимать, что в то время как cjson самосвал Только C-код, созданный вручную, CSafeDumper YAML разделяет два из четырех этапов дампа (Representer и Resolver) с обычным чистым Python SafeDumper и что другие две стадии (Сериализатор и Эмиттер) не являются написанный полностью вручную на C, но состоит из модуля Cython который вызывает библиотеку C libyaml для излучения.


Помимо этой важной части, простой ответ на ваш вопрос почему это занимает больше времени, является то, что сброс YAML делает больше. Это не так во многом потому, что YAML сложнее, как утверждает @flow, а потому, что что делает YAML, делает его гораздо более мощным, чем JSON, а также удобный для пользователя, если вам нужно обработать результат с помощью редактора. Тот означает, что больше времени тратится в библиотеке YAML даже при применении этих дополнительных функций, и во многих случаях также просто проверяет, применимо ли что-либо.

Вот пример: даже если вы никогда не проходили PyYAML код, вы заметили, что дампер не цитирует foo и bar. Это не потому, что эти строки являются ключами, как YAML не есть ограничение, которое имеет JSON, что ключ для отображения должен быть строкой. Например. строка Python, которая является значением в отображении , может также не цитируемый (то есть простой).

Акцент на может , потому что это не всегда так. Принимать за экземпляр строки, состоящей только из цифровых символов: 12345678. Это должно быть записано в кавычках, иначе это будет выглядеть точно так же, как число (и будет считываться как таковой при разборе).

Как PyYAML знает, когда заключать в кавычки строку, а когда нет? На сброс на самом деле сначала дамп строки, затем анализирует результат, чтобы сделать уверен, что когда он читает этот результат обратно, он получает исходное значение. И если это не так, то применяются кавычки.

Позвольте мне повторить важную часть предыдущего предложения снова, поэтому Вам не нужно перечитывать это:

выводит строку, затем анализирует результат

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


В любом реальном приложении со сложными данными на основе JSON самосвал / загрузчик слишком прост для непосредственного использования и многое другое интеллект должен быть в вашей программе по сравнению с тем же дампом сложные данные непосредственно в YAML. Упрощенный пример, когда вы хотите работать с отметками даты и времени, в этом случае вы должны преобразовать строку обратно и далее datetime.datetime, если вы используете JSON. Во время загрузки Вы должны сделать это либо на основе того факта, что это значение связанный с каким-то (можно надеяться, узнаваемым) ключом:

{ "datetime": "2018-09-03 12:34:56" }

или с позицией в списке:

["FirstName", "Lastname", "1991-09-12 08:45:00"]

или в зависимости от формата строки (например, с помощью регулярного выражения).

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

Позволяет восстановить ваши тайминги с тем, что я получаю на моей машине поэтому мы можем сравнить их с другими измерениями. Я переписал твой код в некоторой степени, потому что он был неполным (timeit?) и импортирован другим вещи дважды. Также было невозможно просто вырезать и вставить из-за подсказок >>>.

from __future__ import print_function

import sys
import yaml
import cjson
from timeit import timeit

NR=10000
ds = "; d={'foo': {'bar': 1}}"
d = {'foo': {'bar': 1}}

print('yaml.SafeDumper:', end=' ')
yaml.dump(d, sys.stdout, Dumper=yaml.SafeDumper)
print('cjson.encode:   ', cjson.encode(d))
print()


res = timeit("yaml.dump(d, Dumper=yaml.SafeDumper)", setup="import yaml"+ds, number=NR)
print('yaml.SafeDumper ', res)
res = timeit("yaml.dump(d, Dumper=yaml.CSafeDumper)", setup="import yaml"+ds, number=NR)
print('yaml.CSafeDumper', res)
res = timeit("cjson.encode(d)", setup="import cjson"+ds, number=NR)
print('cjson.encode    ', res)

и это выводит:

yaml.SafeDumper: foo: {bar: 1}
cjson.encode:    {"foo": {"bar": 1}}

yaml.SafeDumper  3.06794905663
yaml.CSafeDumper 0.781533956528
cjson.encode     0.0133550167084

Теперь давайте вывести простую структуру данных, которая включает datetime

import datetime
from collections import Mapping, Sequence  # python 2.7 has no .abc

d = {'foo': {'bar': datetime.datetime(1991, 9, 12, 8, 45, 0)}}

def stringify(x, key=None):
    # key parameter can be used to dump
    if isinstance(x, str):
       return x
    if isinstance(x, Mapping):
       res = {}
       for k, v in x.items():
           res[stringify(k, key=True)] = stringify(v)  # 
       return res
    if isinstance(x, Sequence):
        res = [stringify(k) for k in x]
        if key:
            res = repr(res)
        return res
    if isinstance(x, datetime.datetime):
        return x.isoformat(sep=' ')
    return repr(x)

print('yaml.CSafeDumper:', end=' ')
yaml.dump(d, sys.stdout, Dumper=yaml.CSafeDumper)
print('cjson.encode:    ', cjson.encode(stringify(d)))
print()

Это дает:

yaml.CSafeDumper: foo: {bar: '1991-09-12 08:45:00'}
cjson.encode:     {"foo": {"bar": "1991-09-12 08:45:00"}}

Для синхронизации вышеперечисленного я создал модуль myjson, который оборачивает cjson.encode и имеет вышеуказанное значение stringify. Если вы используете это:

d = {'foo': {'bar': datetime.datetime(1991, 9, 12, 8, 45, 0)}}
ds = 'import datetime, myjson, yaml; d=' + repr(d)
res = timeit("yaml.dump(d, Dumper=yaml.CSafeDumper)", setup=ds, number=NR)
print('yaml.CSafeDumper', res)
res = timeit("myjson.encode(d)", setup=ds, number=NR)
print('cjson.encode    ', res)

дает:

yaml.CSafeDumper 0.813436031342
cjson.encode     0.151570081711

Этот довольно простой вывод уже возвращает вас из двух заказов разница в скорости менее чем на один порядок.


Простые скаляры YAML и форматирование блочного стиля обеспечивают лучшую читаемость данных. То, что вы можете иметь запятую в последовательности (или отображении), делает для меньше ошибок при ручном редактировании данных YAML, как с теми же данными в JSON.

Теги YAML позволяют в ваших данных указывать ваши (сложные) типы. когда используя JSON , вы должны позаботиться в своем коде о чем-либо еще сложнее, чем отображения, последовательности, целые числа, числа с плавающей запятой, логические и строки. Такой код требует времени разработки и вряд ли будет так быстро, как python-cjson (вы, конечно, можете написать свой код в Си.

Вывод некоторых данных, таких как рекурсивные структуры данных (например, топологические данные) или сложные ключи предварительно определены в библиотеке PyYAML. Там Библиотека JSON просто выдает ошибки и реализует обходной путь для этого нетривиально и, скорее всего, замедляет то, что различия в скорости менее актуальны.

Такая мощность и гибкость достигается за счет более низкой скорости. когда выбрасывать много простых вещей JSON - лучший выбор, вы вряд ли в любом случае собираюсь редактировать результат вручную. Для любого, что включает редактирование или сложные объекты или оба, вы все равно должны рассмотреть возможность использования YAML.


¹ Можно принудительно сбросить все строки Python как YAML скаляры с (двойными) кавычками, но установки стиля недостаточно для запретить обратное чтение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...