python 我可以加速YAML吗?

ubof19bj  于 2023-01-16  发布在  Python
关注(0)|答案(5)|浏览(172)

我做了一个小测试用例来比较YAML和JSON的速度:

import json
import yaml
from datetime import datetime
from random import randint

NB_ROW=1024

print 'Does yaml is using libyaml ? ',yaml.__with_libyaml__ and 'yes' or 'no'

dummy_data = [ { 'dummy_key_A_%s' % i: i, 'dummy_key_B_%s' % i: i } for i in xrange(NB_ROW) ]

with open('perf_json_yaml.yaml','w') as fh:
    t1 = datetime.now()
    yaml.safe_dump(dummy_data, fh, encoding='utf-8', default_flow_style=False)
    t2 = datetime.now()
    dty = (t2 - t1).total_seconds()
    print 'Dumping %s row into a yaml file : %s' % (NB_ROW,dty)

with open('perf_json_yaml.json','w') as fh:
    t1 = datetime.now()
    json.dump(dummy_data,fh)
    t2 = datetime.now()
    dtj = (t2 - t1).total_seconds()
    print 'Dumping %s row into a json file : %s' % (NB_ROW,dtj)

print "json is %dx faster for dumping" % (dty/dtj)

with open('perf_json_yaml.yaml') as fh:
    t1 = datetime.now()
    data = yaml.safe_load(fh)
    t2 = datetime.now()
    dty = (t2 - t1).total_seconds()
    print 'Loading %s row from a yaml file : %s' % (NB_ROW,dty)

with open('perf_json_yaml.json') as fh:
    t1 = datetime.now()
    data = json.load(fh)
    t2 = datetime.now()
    dtj = (t2 - t1).total_seconds()
    print 'Loading %s row into from json file : %s' % (NB_ROW,dtj)

print "json is %dx faster for loading" % (dty/dtj)

结果是:

Does yaml is using libyaml ?  yes
Dumping 1024 row into a yaml file : 0.251139
Dumping 1024 row into a json file : 0.007725
json is 32x faster for dumping
Loading 1024 row from a yaml file : 0.401224
Loading 1024 row into from json file : 0.001793
json is 223x faster for loading

我在ubuntu12.04上使用PyYAML 3.11和libyaml C库。我知道json比yaml简单得多,但是json和yaml之间的比率是223x,我想知道我的配置是否正确。
你们有相同的速比吗?
如何加快yaml.load()的速度?

mmvthczy

mmvthczy1#

您可能已经注意到Python的数据结构语法与JSON的语法非常相似。
实际上,Python的json库编码了Python的内置数据类型directly into text chunks,将'替换为",并在各处删除,(有点过于简化)。
另一方面,pyyaml在将其序列化为字符串之前必须构造一个完整的表示图。
同样的事情必须在加载时向后发生。
加速yaml.load()的唯一方法是编写一个新的Loader,但我怀疑这可能是性能上的巨大飞跃,除非您愿意编写自己的单一用途的YAML解析器,并考虑以下评论:
YAML构建了一个图形,因为它是一个通用的序列化格式,能够表示对同一个对象的多个引用。如果你知道没有对象是重复的,只有基本类型出现,你可以使用json序列化器,它仍然是有效的YAML。

  • -更新
    我之前说的仍然是正确的,但是如果你运行的是Linux,有一种方法可以加速Yaml解析,默认情况下,Python的yaml使用Python解析器,你必须告诉它你想使用PyYamlC解析器。
    您可以这样做:
import yaml
from yaml import CLoader as Loader, CDumper as Dumper

dump = yaml.dump(dummy_data, fh, encoding='utf-8', default_flow_style=False, Dumper=Dumper)
data = yaml.load(fh, Loader=Loader)

为此,您需要安装yaml-cpp-dev(包后来重命名为libyaml-cpp-dev),例如使用apt-get:

$ apt-get install yaml-cpp-dev

PyYamlLibYaml也是一样的,但是根据你的输出已经是这样了。
我现在不能测试它,因为我运行的是OSX,brew在安装yaml-cpp-dev时遇到了一些麻烦,但如果你按照PyYaml documentation,他们很明显性能会好得多。

6uxekuva

6uxekuva2#

作为参考,我比较了几种人类可读的格式,Python的yaml阅读器确实是“迄今为止”最慢的(注意下图中的对数缩放)。如果您想要速度,那么您需要JSON加载器之一,例如orjson

用于重现绘图的代码:

import json
import tomllib

import numpy
import orjson
import pandas
import perfplot
import toml
import tomli
import ujson
import yaml
from yaml import CLoader, Loader

def setup(n):
    numpy.random.seed(0)
    data = numpy.random.rand(n, 3)

    with open("out.yml", "w") as f:
        yaml.dump(data.tolist(), f)

    with open("out.json", "w") as f:
        json.dump(data.tolist(), f, indent=4)

    with open("out.dat", "w") as f:
        numpy.savetxt(f, data)

    with open("out.toml", "w") as f:
        toml.dump({"data": data.tolist()}, f)

def yaml_python(arr):
    with open("out.yml", "r") as f:
        out = yaml.load(f, Loader=Loader)
    return out

def yaml_c(arr):
    with open("out.yml", "r") as f:
        out = yaml.load(f, Loader=CLoader)
    return out

# def zaml_load(arr):
#     with open("out.yml", "r") as f:
#         out = zaml.load(f)
#     return out["data"]

def json_load(arr):
    with open("out.json", "r") as f:
        out = json.load(f)
    return out

def ujson_load(arr):
    with open("out.json", "r") as f:
        out = ujson.load(f)
    return out

def orjson_load(arr):
    with open("out.json", "rb") as f:
        out = orjson.loads(f.read())
    return out

def loadtxt(arr):
    with open("out.dat", "r") as f:
        out = numpy.loadtxt(f)
    return out

def pandas_read(arr):
    out = pandas.read_csv("out.dat", header=None, sep=" ")
    return out.values

def toml_load(arr):
    with open("out.toml", "r") as f:
        out = toml.load(f)
    return out["data"]

def tomli_load(arr):
    with open("out.toml", "rb") as f:
        out = tomli.load(f)
    return out["data"]

def tomllib_load(arr):
    with open("out.toml", "r") as f:
        out = toml.load(f)
    return out["data"]

b = perfplot.bench(
    setup=setup,
    kernels=[
        yaml_python,
        yaml_c,
        json_load,
        loadtxt,
        pandas_read,
        toml_load,
        tomli_load,
        tomllib_load,
        ujson_load,
        orjson_load,
    ],
    n_range=[2**k for k in range(18)],
)

b.save("out.png")
b.show()
k3fezbri

k3fezbri3#

是的。
这里的其他回答是"使用CLoader",这是一个很好的提示,但是如果你没有使用任何定制类(YAML中的!!foo标记),你可以通过使用CBaseLoader而不是普通的CLoader来挤出另外大约20%的时间。
我有一个脚本,从约2分37秒到约2分7秒的变化。
应该就像这样简单:

import yaml

with open(...) as f:
    data = yaml.load(f, Loader=yaml.CBaseLoader)
qoefvg9y

qoefvg9y4#

这里还没有提到的还有CSafeLoaderC类实现。

with open(file_path, 'r', encoding="utf-8") as config_file:
    config_data = yaml.load(config_file, Loader=CSafeLoader)

我发现它比CLoader稍微快一点(至少对于50kb下的小文件来说,两者都比python实现快15倍左右)
由于noted aboveCBaseLoader比其他C类快大约20%,这是由于:
BaseLoader(stream)不解析或支持任何标签,只构造基本的Python对象:列表、字典和Unicode字符串。
CBaseLoader无法识别yaml中的boolean

8i9zcol2

8i9zcol25#

是的,我也注意到JSON要快得多。所以一个合理的方法是先把YAML转换成JSON。如果你不介意ruby,那么你可以得到一个很大的加速,并且完全抛弃yaml安装:

import commands, json
def load_yaml_file(fn):
    ruby = "puts YAML.load_file('%s').to_json" % fn
    j = commands.getstatusoutput('ruby -ryaml -rjson -e "%s"' % ruby)
    return json.loads(j[1])

以下是10万条记录的比较:

load_yaml_file: 0.95 s
yaml.load: 7.53 s

对于1M条记录:

load_yaml_file: 11.55 s
yaml.load: 77.08 s

如果您坚持使用yaml.load,请记住将其放在virtualenv中,以避免与其他软件冲突。

相关问题