按字节拆分字典

Splitting Dictionary on Bytes

我有一些 python 代码:

  1. 从不同端点提取各种指标
  2. 将它们与一些标准化的 key/values
  3. 合并到一个通用词典中
  4. 将字典上传到另一个工具进行分析

虽然这通常有效,但当字典变得太大时会出现问题,它会导致多方面的性能问题。

我见过使用 itertools 根据键的范围进行拆分,根据键的数量进行均匀拆分的示例。但是,我想尝试根据字节大小拆分它,因为一些指标比其他指标大得多。

是否可以根据字节大小将字典动态拆分为字典列表?

假设键和值都是可以以有意义的方式调用 sys.getsizeof 的合理类型,以及所有不同的对象,您可以使用该信息将字典分成相等的块。

如果您希望最大块是总大小的除数,请先计算总大小。如果你的最大尺寸是外部固定的,你可以跳过这一步:

total_size = sum(getsizeof(k) + getsizeof(v) for k, v in my_dict.items())

现在您可以迭代字典,假设大小大致随机分布,在超过 max_size 阈值之前剪切一个新字典:

from sys import getsizeof

def split_dict(d, max_size):
    result = []
    current_size = 0
    current_dict = {}
    while d:
        k, v = d.popitem()
        increment = getsizeof(k) + getsizeof(v)
        if increment + current_size > max_size:
            result.append(current_dict)
            if current_size:
                current_dict = {k: v}
                current_size = increment
            else:
                current_dict[k] = v  # going to list
                current_dict = {}
                current_size = 0
        else:
            current_dict[k] = v
            current_size += increment
    if current_dict:
        result.append(current_dict)
    return result

请记住 dict.popitem 是破坏性的:您实际上是从 my_dict 中删除所有内容以填充较小的版本。

这是一个高度简化的示例:

>>> from string import ascii_letters
>>> d = {s: i for i, s in enumerate(ascii_letters)}
>>> total_size = sum(getsizeof(k) + getsizeof(v) for k, v in d.items())
>>> split_dict(d, total_size // 5)
[{'Z': 51, 'Y': 50, 'X': 49, 'W': 48, 'V': 47, 'U': 46, 'T': 45, 'S': 44, 'R': 43, 'Q': 42},
 {'P': 41, 'O': 40, 'N': 39, 'M': 38, 'L': 37, 'K': 36, 'J': 35, 'I': 34, 'H': 33, 'G': 32},
 {'F': 31, 'E': 30, 'D': 29, 'C': 28, 'B': 27, 'A': 26, 'z': 25, 'y': 24, 'x': 23, 'w': 22},
 {'v': 21, 'u': 20, 't': 19, 's': 18, 'r': 17, 'q': 16, 'p': 15, 'o': 14, 'n': 13, 'm': 12},
 {'l': 11, 'k': 10, 'j': 9, 'i': 8, 'h': 7, 'g': 6, 'f': 5, 'e': 4, 'd': 3, 'c': 2},
 {'b': 1, 'a': 0}]

如您所见,拆分在分布方面不一定是最佳的,但它确保没有块大于 max_size,除非一个条目需要比这更多的字节。

更新异常值

如果您有任意大的嵌套值,您仍然可以在顶层拆分,但是,您必须用更强大的东西替换 getsizeof(v)。例如:

from collections.abc import Mapping, Iterable

def xgetsizeof(x):
    if isinstance(x, Mapping):
        return getsizeof(x) + sum(xgetsizeof(k) + xgetsizeof(v) for k, v in x.items())
    if isinstance(x, Iterable) and not isintance(x, str):
        return getsizeof(x) + sum(xgetizeof(e) for e in x)
    return getsizeof(x)

现在您还可以通过一次调用计算 total_size

total_size = xgetsizeof(d)

请注意,这比您之前看到的值要大。较早的结果是

xgetsizeof(d) - getsizeof(d)

要使解决方案真正稳健,您需要添加实例跟踪以避免循环引用和重复计算。

我继续为我的库编写了这样一个函数 haggis, called haggis.objects.getsizeof。它的行为与上面的 xgetsizeof 很相似,但更加稳健。