【问题标题】:fastest packing of data in Python (and Java)Python(和Java)中最快的数据打包
【发布时间】:2012-04-10 15:53:36
【问题描述】:

(Sometimes我们的主机错了;纳秒很重要;)

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

This talk by youtube 提出了有趣的适用点:

  • 序列化格式——不管你用哪一种,都是 昂贵的。措施。不要用泡菜。不是一个好的选择。成立 协议缓冲区慢。他们编写了自己的 BSON 实现 比您可以下载的快 10-15 倍。

  • 你必须测量。 Vitess 将其协议换成了 HTTP 执行。即使它在 C 中,它也很慢。所以他们撕了 退出 HTTP 并使用 python 进行直接套接字调用,这是 8% 在全球 CPU 上更便宜。 HTTP 的封装非常昂贵。

  • 测量。在 Python 中,测量就像阅读茶叶。 Python中有很多反直觉的东西,比如 抢夺收集的成本。他们的大部分应用程序都会花费 他们的时间序列化。分析序列化非常依赖于 您要输入的内容。序列化 int 与 序列化大 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)

给予:

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 接口,否则你会加快速度!

肯定有一种方法可以更快地序列化这些数据吗?

【问题讨论】:

  • 对于python端的序列化,你可以使用cpickle,因为它比pickle快10倍以上。在服务器端,可以使用StringBuilder(如果你正在寻找优化,并且不需要并发访问)
  • 我不确定用其他东西替换 json 是否是个好主意。如果 python 端在 jython 中,那么在 java 中的序列化将是一个很好的想法。
  • 为什么不尝试一个简单的分隔字符串。 "1|2|3|4|foo|bar" 如果你能找到一个永远不会出现在你的字符串值中的分隔符,那么使用 String.Split 将是最快的'反序列化'
  • 如果您使用二进制协议,pickle/cPickle 实际上比您的测试结果显示的要好得多。只需将 .dumps(args) 更改为 .dumps(args, -1) 或 .dumps(args, cPickle.HIGHEST_PROTOCOL)。当我用这些修改重新运行你的基准测试时,cPickle 实际上能够在组合编码/解码时间上击败 msgpack。我意识到对于 Java 互操作性,pickle 不是一个选项,但如果您只使用 Python 并且可以信任您的数据(因为可以使 pickle 执行任意代码),它可能已经足够快了。
  • 如果速度很重要,那么大小也很重要 - 传递信息所需的字节越少,您在相同时间内可以传输的信息就越多。 msgpack 在您在此处提供的测试中击败了所有这些大小选项。

标签: java python performance optimization serialization


【解决方案1】:

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

这是一个库,如果我完全按照 AbstractExcerptUnsafeExcerpt 进行操作,一条典型的消息需要 50 到 200 ns 来序列化和发送或读取和反序列化。

【讨论】:

  • 嗯最慢?我不认为 XML 更快 - 根据我的经验,它更慢并且非常消耗资源。
  • 如果您使用文档模型,XML 会更慢,但如果您使用事件模型,我发现它会更快。它可能是我使用的 JSON 编码器、解码器。 ;)
  • 有趣,也许你用过运行在类 DOM 结构上的 JSon 库,我认为事件处理 JSON 会更快,我有空的时候必须做一些测试。
  • 我会对结果感兴趣。
  • 您可能会发现此链接更有趣vanillajava.blogspot.com/2015/08/…
【解决方案2】:

最终我们选择了使用msgpack。

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

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

在我们将 JSON Lib (json-simple) 换成 Jackon 的 ObjectMapper 之前,性能绝对是糟糕的。这使 RPS 提高了 35 到 300+ - 增加了 10 倍

【讨论】:

    【解决方案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 将您的 long 转换为二进制字符串。然后你可以像处理字符串一样追加它
    2. 创建一个struct.Struct 对象并使用它。我相信它更有效

    你也可以看看:

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

    您最快的方法可以在 0.1222 秒内编码 1000 个元素。那是 0.1222 毫秒内的 1 个元素。这相当快。如果不切换语言,我怀疑你会做得更好。

    【讨论】:

    • 确实如此,谢谢!我有点犹豫,感觉我不相信 array.tostring 是可移植的、可靠的、已定义的格式
    【解决方案4】:

    我知道这是一个老问题,但它仍然很有趣。我最近的选择是使用Cap’n Proto,由为 google 编写 protobuf 的同一个人编写。在我的例子中,与 Jackson 的 JSON 编码器/解码器(服务器到服务器,两边都是 Java)相比,这导致时间和体积都减少了 20% 左右。

    【讨论】:

    【解决方案5】:

    Protocol Buffers 非常快,并且具有 Java 和 Python 的绑定。它是一个非常流行的库,并且在 Google 内部使用,因此应该很好地对其进行测试和优化。

    【讨论】:

    • 问题中引用的来自 Google 的消息来源说他们发现协议缓冲区很慢;你有协议缓冲区性能的统计数据吗?
    • @Will "Fast" 是相对的。我发现 Protocol Buffers 比简单的 XML 更快,这对我的用例来说已经足够了。对于其他一些用例,您可能会发现这还不够。这完全取决于您究竟需要做什么以及您有什么期望。
    • 快,就像绝对“花最少的时间做同样的工作”一样快;)
    【解决方案6】:

    既然您发送的数据已经明确定义、非递归和非嵌套,为什么不使用简单的分隔字符串。您只需要一个不包含在字符串变量中的分隔符,可能是“\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]));
    

    注意以上内容是在网络浏览器中编写的,未经测试,因此可能存在语法错误。

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

    【讨论】:

      猜你喜欢
      • 2011-03-17
      • 1970-01-01
      • 1970-01-01
      • 2010-10-09
      • 2020-05-23
      • 1970-01-01
      • 1970-01-01
      • 2014-03-03
      • 2023-03-11
      相关资源
      最近更新 更多