为什么我的内存足迹在这种贪婪的 tsp 方法中爆炸了?

why is my memory footprint blowing up in this greedy approach to tsp?

我有一个作业要使用贪婪的方法来满足 TSP。问题有33708个城市。因为我从之前的任务中获得了很多有用的工具,所以我决定重用该方法并预先计算距离。

所以这仅仅超过 50 亿个条目(33708 选择 2),每个条目都适合 float32。同样,x 和 y 坐标是数字 $|n| < 10000 $,小数点后不超过 4 位。

我的 python 同样是:

def get_distance(left, right):
  """ return the euclidean distance between tuples left and right, which are coordinates"""
  return ((left[0] - right[0]) ** 2 + (left[1] - right[1]) ** 2) ** 0.5

# precompute all distances
distances = {}
for i in range(len(cities)):
  for j in range(i + 1, len(cities)):
    d = get_distance(cities[i], cities[j])
    distances[frozenset((i, j)))] = d

我预计这会占用 (3 * 32b) * 568m ≈ 6.7 GB 的内存。但事实上,在我的 jupyter notebook 中观看实时运行时,它似乎甚至超过了 35GB。 (442s 和计数)我不得不杀死它,因为我已经进入我的交换 space 并且它慢了很多。有谁知道为什么它这么大?

更新:再次尝试使用 tuple(sorted((i,j))) -- 但已经达到 110s,它是 15GB 并且还在计算中

尺码

>>> import sys
>>> a = frozenset((1,2))
>>> sys.getsizeof(a)
216
>>> sys.getsizeof(tuple(sorted((1,2))))
56
>>> sys.getsizeof(1)
28

python有没有float32和int16之类的东西?? -- 答:numpy 有它们

更新尝试:

from numpy import float32, int16
from itertools import combinations
import sys

def get_distance(left, right):
  """ return the euclidean distance between tuples left and right, which are coordinates"""
  return float32(((left[0] - right[0]) ** 2 + (left[1] - right[1]) ** 2) ** 0.5)

# precompute all distances
distances = {}
for i, j in combinations(range(len(cities)), 2):
    distances[tuple(sorted((int16(i), int16(j))))] = get_distance(cities[i], cities[j])

print(sys.getsizeof(distances))

观察到的尺寸:

请注意,即使我们接近 5000 万个条目,即 10000 个选择 2(数据总大小的 10%),增长率也不会随数据缩放:

我决定停止对完整城市列表的尝试,因为我的 OS 内存快照增长到远远超过 30GB,我打算交换。这意味着,即使最终对象有那么大,笔记本所需的内存量仍然要大得多。

Python 对象由于动态类型和引用计数而产生开销。绝对最小对象 object() 的大小为 16 字节(在 64 位机器上)。 8 字节引用计数,8 字节类型指针。 没有 python 物体可以比那个小。 floatint 稍大,至少 24 字节。 list 至少是一个指针数组,它增加了一个额外的 8 字节。因此,十亿个整数列表的小内存占用可能是 32 * 500_000_000 ~= 16Gb。 setsdicts 甚至比那个更大,因为它们每个元素存储的不仅仅是一个指针。

使用numpy(也许stdlib array模块已经足够了)

(注意:numpy的float32类型也不能小于16字节)