2012-03-27 32 views
20

Sometimes我们的主机是错误的;纳秒事;)最快的包装

我有一个会谈到一些Java服务器和剖析一个Python扭曲服务器显示消费〜30%它的运行时间在JSON编码器/解码器中;它的工作是每秒处理数千条消息。

This talk由YouTube提出了有趣的应用点:

  • 序列化格式 - 不管你使用哪一个,他们都是 昂贵。测量。不要使用泡菜。不是一个好的选择。发现 协议缓冲区缓慢。他们写了他们自己的BSON实现,其中 比你可以下载的速度快10-15倍。

  • 你必须测量。 Vitess为HTTP 实现换出了一个协议。尽管它在C中,但速度很慢。所以他们将 从HTTP中剥离出来,并使用python进行直接套接字调用,并且在全局CPU上的成本更便宜8% 。 HTTP的封装非常昂贵。

  • 测量。在Python中,度量就像阅读茶叶一样。 Python中有很多与直觉相反的东西,比如 垃圾收集的成本。他们的应用程序中的大部分应用程序都花费了 时间序列化。分析序列化很大程度上取决于您要插入的内容。序列化整数与 序列化较大的分组非常不同。

无论如何,我控制我的消息传递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) 

给出:

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 

bsonmsgpackujson所有通过的easy_install

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

肯定有一种方法可以将数据序列化得更快吗?

+0

为了在python结尾进行序列化,你可以使用cpickle,因为它比pickle快10倍以上。在服务器端,你可以使用StringBuilder(如果你正在寻找优化,并且不需要并发访问) – 2012-03-27 06:08:11

+0

我不确定用其他东西替换json是个好主意。如果python结尾已经在jython中,那么在java中的序列化将是非常好的想法。 – 2012-03-27 06:29:19

+0

为什么不尝试一个简单的分隔字符串。 “1 | 2 | 3 | 4 | foo | bar”如果你能找到一个永远不会出现在你的字符串值中的分隔符,那么使用String.Split将是最快的'反序列化' – 2012-03-27 19:34:55

回答

6

虽然JSON是灵活的,它是在Java中最慢的序列化格式中的一种在纳米秒(可能蟒蛇为好)事我会用本地字节顺序的二进制格式(可能是小端)

下面是我正在做的一个库AbstractExcerptUnsafeExcerpt一个典型的消息需要50到200 ns来序列化和发送,读取或反序列化。

+1

嗯最慢?我不认为XML速度更快 - 根据我的经验,它速度更慢,而且极其耗费资源。 – 2012-03-27 09:36:26

+1

如果您使用文档模型,XML会变慢,但如果您使用事件模型,则发现速度更快。它可能是我使用的JSon编码器和解码器。 ;) – 2012-03-27 10:07:43

+2

有趣的是,也许你已经使用过类似DOM的结构的JSon库,我认为事件处理JSON会更快,当我有空休息时,我必须做一些测试。 – 2012-03-27 12:40:37

2

Protocol Buffers速度相当快,并且对Java和Python都有绑定。这是一个非常受欢迎的图书馆,在Google内部使用,因此应该进行相当好的测试和优化。

+0

谷歌的来源引用在问题是他们发现协议缓冲区很慢;你有统计协议缓冲区性能? – Will 2012-03-27 10:26:16

+0

@“快”是相对的。我发现Protocol Buffers比简单的XML更快,这对我的用例来说已经足够好了。对于其他一些用例,您可能会发现这还不够。这完全取决于你需要做什么以及你有什么期望。 – 2012-03-27 10:41:42

+0

快速在绝对“最少的时间为同一工作”种类的快速;) – Will 2012-03-27 10:49:19

0

由于您发送的数据已经定义好,非递归和非嵌套,为什么不使用简单的分隔字符串。你只需要一个不包含在你的字符串变量中的分隔符'\ n'。

"10\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\nFoo Foo Foo\nBar Bar Bar" 

然后,只需使用简单的字符串拆分方法。

string[] temp = str.split("\n"); 
ArrayList<long> longs = new ArrayList<long>(long.parseLong(temp[0])); 
string string1 = temp[temp.length-2]; 
string string2 = temp[temp.length-1]; 
for(int i = 1; i < temp.length-2 ; i++) 
    longs.add(long.parseLong(temp[i])); 

上面写于Web浏览器和未经考验的这样的语法错误可能存在。

对于基于文本;我假设以上是最快的方法。

3

您可以加快结构情况

def encode_struct(longs,str1,str2): 
    return struct.pack(">iii%dq"%len(longs),len(longs),len(str1),len(str2),*longs)+str1+str2 
  1. 尝试使用Python阵列模块和toString方法,以您的多头转换成二进制字符串。然后,您可以像对待字符串那样附加它
  2. 创建一个struct.Struct对象并使用它。我相信这是更有效的

您也可以看看:

http://docs.python.org/library/xdrlib.html#module-xdrlib

你最快的方法编码在0.1222秒1000元。这是.1222毫秒中的1个元素。这很快。我怀疑,如果不切换语言,你会做得更好。

+0

确实如此,谢谢!我有点犹豫,因为我不相信array.tostring可悲地使用可移植的,可靠的,已定义的格式 – Will 2012-03-27 18:43:24

3

我知道这是一个古老的问题,但它仍然很有趣。我最近的选择是使用Cap’n Proto,这是由曾经为Google做过protobuf的同一个人编写的。就我而言,与杰克逊的JSON编码器/解码器(服务器到服务器,两侧都是Java)相比,这导致时间和音量都减少了20%左右。

+0

另请参阅https://github.com/google/flatbuffers。非常有趣的方式来做这种事情 – frmdstryr 2017-10-21 15:50:13