按字节拆分字典
Splitting Dictionary on Bytes
我有一些 python 代码:
- 从不同端点提取各种指标
- 将它们与一些标准化的 key/values
合并到一个通用词典中
- 将字典上传到另一个工具进行分析
虽然这通常有效,但当字典变得太大时会出现问题,它会导致多方面的性能问题。
我见过使用 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
很相似,但更加稳健。
我有一些 python 代码:
- 从不同端点提取各种指标
- 将它们与一些标准化的 key/values 合并到一个通用词典中
- 将字典上传到另一个工具进行分析
虽然这通常有效,但当字典变得太大时会出现问题,它会导致多方面的性能问题。
我见过使用 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
很相似,但更加稳健。