Python(和Java)中最快的数据打包

Wil*_*ill 20 python java optimization performance serialization

(有时我们的主人是错的;纳秒很重要;)

我有一个Python Twisted服务器与一些Java服务器进行通信,并且分析显示在JSON编码器/解码器中花费约30%的运行时间; 它的工作是每秒处理数千条消息.

youtube的这个演讲提出了有趣的适用点:

  • 序列化格式 - 无论您使用哪种格式,它们都很昂贵.测量.不要使用泡菜.不是一个好选择.找到协议缓冲区很慢.他们编写了自己的BSON实现,比你下载的快了10-15倍.

  • 你必须衡量.Vitess为HTTP实现交换了一个协议.即使它在C中它也很慢.所以他们破解了HTTP并使用python进行了直接套接字调用,这在全局CPU上便宜了8%.HTTP的包络非常昂贵.

  • 测量.在Python中,测量就像读茶叶一样.Python中的很多东西都是反直觉的,就像垃圾收集的成本一样.他们的应用程序的大部分时间都花时间序列化.分析序列化很大程度上取决于您所使用的内容.序列化整数与序列化大blob非常不同.

无论如何,我控制了消息传递API的Python和Java两端,并且可以选择与JSON不同的序列化.

我的消息如下:

  • 可变数量的长; 在1到10K之间的任何地方
  • 和两个已经UTF8的文本字符串; 均为1到3KB

因为我正在从套接字中读取它们,所以我想要能够优雅地处理流的库 - 例如,如果它没有告诉我它消耗了多少缓冲区,那就太烦人了.

当然,这个流的另一端是Java服务器; 我不想选择对Python端有用的东西,但是将问题转移到Java端,例如性能或曲折或片状的API.

我显然会做自己的分析.我在这里问,希望你能描述我不会想到的方法,例如使用struct和最快的字符串/缓冲区是什么.

一些简单的测试代码给出了惊人的结果:

import time, random, struct, json, sys, pickle, cPickle, marshal, array

def encode_json_1(*args):
    return json.dumps(args)

def encode_json_2(longs,str1,str2):
    return json.dumps({"longs":longs,"str1":str1,"str2":str2})

def encode_pickle(*args):
    return pickle.dumps(args)

def encode_cPickle(*args):
    return cPickle.dumps(args)

def encode_marshal(*args):
    return marshal.dumps(args)

def encode_struct_1(longs,str1,str2):
    return struct.pack(">iii%dq"%len(longs),len(longs),len(str1),len(str2),*longs)+str1+str2

def decode_struct_1(s):
    i, j, k = struct.unpack(">iii",s[:12])
    assert len(s) == 3*4 + 8*i + j + k, (len(s),3*4 + 8*i + j + k)
    longs = struct.unpack(">%dq"%i,s[12:12+i*8])
    str1 = s[12+i*8:12+i*8+j]
    str2 = s[12+i*8+j:]
    return (longs,str1,str2)

struct_header_2 = struct.Struct(">iii")

def encode_struct_2(longs,str1,str2):
    return "".join((
        struct_header_2.pack(len(longs),len(str1),len(str2)),
        array.array("L",longs).tostring(),
        str1,
        str2))

def decode_struct_2(s):
    i, j, k = struct_header_2.unpack(s[:12])
    assert len(s) == 3*4 + 8*i + j + k, (len(s),3*4 + 8*i + j + k)
    longs = array.array("L")
    longs.fromstring(s[12:12+i*8])
    str1 = s[12+i*8:12+i*8+j]
    str2 = s[12+i*8+j:]
    return (longs,str1,str2)

def encode_ujson(*args):
    return ujson.dumps(args)

def encode_msgpack(*args):
    return msgpacker.pack(args)

def decode_msgpack(s):
    msgunpacker.feed(s)
    return msgunpacker.unpack()

def encode_bson(longs,str1,str2):
    return bson.dumps({"longs":longs,"str1":str1,"str2":str2})

def from_dict(d):
    return [d["longs"],d["str1"],d["str2"]]

tests = [ #(encode,decode,massage_for_check)
    (encode_struct_1,decode_struct_1,None),
    (encode_struct_2,decode_struct_2,None),
    (encode_json_1,json.loads,None),
    (encode_json_2,json.loads,from_dict),
    (encode_pickle,pickle.loads,None),
    (encode_cPickle,cPickle.loads,None),
    (encode_marshal,marshal.loads,None)]

try:
    import ujson
    tests.append((encode_ujson,ujson.loads,None))
except ImportError:
    print "no ujson support installed"

try:
    import msgpack
    msgpacker = msgpack.Packer()
    msgunpacker = msgpack.Unpacker()
    tests.append((encode_msgpack,decode_msgpack,None))
except ImportError:
    print "no msgpack support installed"

try:
    import bson
    tests.append((encode_bson,bson.loads,from_dict))
except ImportError:
    print "no BSON support installed"

longs = [i for i in xrange(10000)]
str1 = "1"*5000
str2 = "2"*5000

random.seed(1)
encode_data = [[
        longs[:random.randint(2,len(longs))],
        str1[:random.randint(2,len(str1))],
        str2[:random.randint(2,len(str2))]] for i in xrange(1000)]

for encoder,decoder,massage_before_check in tests:
    # do the encoding
    start = time.time()
    encoded = [encoder(i,j,k) for i,j,k in encode_data]
    encoding = time.time()
    print encoder.__name__, "encoding took %0.4f,"%(encoding-start),
    sys.stdout.flush()
    # do the decoding
    decoded = [decoder(e) for e in encoded]
    decoding = time.time()
    print "decoding %0.4f"%(decoding-encoding)
    sys.stdout.flush()
    # check it
    if massage_before_check:
        decoded = [massage_before_check(d) for d in decoded]
    for i,((longs_a,str1_a,str2_a),(longs_b,str1_b,str2_b)) in enumerate(zip(encode_data,decoded)):
        assert longs_a == list(longs_b), (i,longs_a,longs_b)
        assert str1_a == str1_b, (i,str1_a,str1_b)
        assert str2_a == str2_b, (i,str2_a,str2_b)
Run Code Online (Sandbox Code Playgroud)

得到:

encode_struct_1 encoding took 0.4486, decoding 0.3313
encode_struct_2 encoding took 0.3202, decoding 0.1082
encode_json_1 encoding took 0.6333, decoding 0.6718
encode_json_2 encoding took 0.5740, decoding 0.8362
encode_pickle encoding took 8.1587, decoding 9.5980
encode_cPickle encoding took 1.1246, decoding 1.4436
encode_marshal encoding took 0.1144, decoding 0.3541
encode_ujson encoding took 0.2768, decoding 0.4773
encode_msgpack encoding took 0.1386, decoding 0.2374
encode_bson encoding took 55.5861, decoding 29.3953
Run Code Online (Sandbox Code Playgroud)

bson,msgpackujson都是通过easy_install安装的

我会要显示我错了,这样做; 我应该使用cStringIO接口,否则你加快了速度!

必须有一种方法可以将这些数据序列化,确保更快一个数量级吗?

Pet*_*rey 6

虽然JSon是灵活的,但它是Java中最慢的序列化格式之一(也可能是python),在纳秒时间我会使用本机字节顺序的二进制格式(可能是小端)

这是一个库,我确切地说,AbstractExcerptUnsafeExcerpt一个典型的消息需要50到200 ns来序列化和发送或读取和反序列化.

  • 有趣的是,也许你使用的是在DOM状结构操作JSON库,我觉得事件处理JSON会更快,我必须做一些测试,当我有时间了. (2认同)

Wil*_*ill 6

最后,我们选择使用msgpack.

如果您使用JSON,那么您在Python和Java上选择的库对性能至关重要:

在Java上,http://blog.juicehub.com/2012/11/20/benchmarking-web-frameworks-for-games/说:

在我们为Jackon的ObjectMapper换掉JSON Lib(json-simple)之前,性能绝对是残酷的.这使RPS达到35至300+ - 增加10倍