【问题标题】:Fastest pythonic way of parsing dictionary where values are bytes stringfied json object解析字典的最快pythonic方法,其中值是字节字符串化的json对象
【发布时间】:2018-12-11 20:34:52
【问题描述】:

所以我有一个字典,它是我从 Redis 获取的哈希对象,类似于以下字典:

source_data = {
   b'key-1': b'{"age":33,"gender":"Male"}', 
   b'key-2': b'{"age":20,"gender":"Female"}'
}

我的目标是从该字典中提取所有值,并将它们作为 Python 字典列表,如下所示:

final_data = [
   {
      'age': 33,
      'gender': 'Male'
   },

   {
      'age': 20,
      'gender': 'Female'
   }
]

我尝试使用 json 解析进行列表理解:

import json
final_data = [json.loads(a) for a in source_data.values()]

它可以工作,但对于大型数据集,它需要太多时间。

我切换到使用这个 3rd 方 json 模块 ujson 根据这个 benchmark 更快,但我没有注意到任何改进。

我尝试使用多线程:

pool = Pool()
final_data = pool.map(ujson.loads, source_data.values(), chunksize=500)

pool.close()
pool.join()

我玩了一点chunksize,结果还是一样,还是太费时间了。

如果有人可以建议其他解决方案或改进以前的尝试,那将非常有帮助,如果我可以避免使用循环,那将是理想的。

【问题讨论】:

  • 也许值得尝试 pypy?
  • 需要多长时间,您的数据源有多大?
  • @TomDalton 目前无法尝试 pypy。
  • 多处理在这里更有可能阻碍而不是帮助。你想反序列化一个字符串,但是一旦子进程完成了他们必须将对象序列化为字符串,将字符串发送到父进程,然后再将字符串反序列化为对象......唯一的区别是多处理不使用 JSON 作为其数据交换格式。
  • 如果需要 35 秒,我高度怀疑这里还有其他一些我们忽略的瓶颈。 json.loads 应该能够在微秒内处理这样的小数据。你是在一个批次中从 redis 中获取所有数据吗?或者您是按顺序从远程 redis 服务器请求条目? (只是推测,但这种延迟对于网络 i/o 来说更为典型)

标签: python json python-3.x dictionary ujson


【解决方案1】:

假设这些值确实是有效的 JSON,那么构建一个 single JSON 对象进行解码可能会更快。我认为将值加入单个字符串应该是安全的。

>>> new_json = b'[%s]' % (b','.join(source_data.values(),)
>>> new_json
b'[{"age":33,"gender":"Male"},{"age":20,"gender":"Female"}]'
>>> json.loads(new_json)
[{'age': 33, 'gender': 'Male'}, {'age': 20, 'gender': 'Female'}]

这将调用json.loads 2000+ 次的开销替换为对b','.join 的单个调用和单个字符串格式化操作的较小开销。

【讨论】:

  • 我已经试过了,但是你看到的值是字节字符串。
  • 抱歉,懒得在 Python 2 中测试了。更新应该在 Python 3 中工作。
  • 感谢您的尝试,它可以工作,但解析 2000 多个条目仍需要 34 多秒。
  • 如果仅仅解码几千个条目需要几秒钟的时间,这里肯定有其他事情发生。您能否发布示例数据的链接,以便我们自己复制? minimal reproducible example
  • 这突然似乎非常快,大约 4000 个条目,在 0.12 秒内解析它们
【解决方案2】:

作为参考,我尝试复制这种情况:

import json, timeit, random
source_data = { 'key-{}'.format(n).encode('ascii'): 
                '{{"age":{},"gender":"{}"}}'.format(
                    random.randint(18,75), 
                    random.choice(("Male", "Female"))
                 ).encode('ascii') 
               for n in range(45000) }
timeit.timeit("{ k: json.loads(v) for (k,v) in source_data.items() }", 
    number=1, globals={'json': json, 'source_data': source_data})

这在不到一秒钟的时间内完成。那些超过 30 秒的时间一定来自我没有看到的东西。

我最接近的猜测是您将数据放在某种代理容器中,其中每个键提取都变成了远程调用,例如使用hscan 而不是hgetall。使用count 提示hscan 应该可以在两者之间进行权衡。

正确的分析应该可以揭示延迟的来源。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-09-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多