2015-05-10 63 views
5

所以,我在Python 3.4中做了一个游戏。在游戏中,我需要跟踪地图。它是从(0,0)开始并以各个方向继续,以过滤随机方式生成的连接房间的地图(只有下一个位置的正确匹配用于随机列表选择)。python内存使用量字典和变量大型数据集

我有几个类型的房间,其中有一个名字,和门的列表:

RoomType = namedtuple('Room','Type,EntranceLst') 
typeA = RoomType("A",["Bottom"]) 
... 

对于地图的那一刻我保持位置的字典和房间的类型:

currentRoomType = typeA 
currentRoomPos = (0,0) 
navMap = {currentRoomPos: currentRoomType} 

我有循环产生9.000.000房间,测试内存使用情况。 当我运行它时,我获得了大约600和800Mb。 我想知道是否有一种方法来优化。

我试着用的,而不是做

navMap = {currentRoomPos: currentRoomType} 

我会做

navMap = {currentRoomPos: "A"} 

,但这并没有在使用一个真正的变化。

现在我想知道我是否可以 - 也应该 - 保留所有类型的列表,并为每种类型保留它发生的位置。但我不知道它是否会与python管理变量的方式有所不同。

这几乎是一个思想实验,但如果有任何有用的东西来自它,我可能会实现它。

+0

我想这个职位只有2-d。对 ? –

+0

字典是散列表,因此它们提供了快速查找,代价是内存使用的大量开销。你确定你需要一个散列表吗?我无法判断自己,因为你的代码没有展示出你可能想用你的地图做的所有可能的事情。 –

+0

@TasosVogiatzoglou:确实,他们是。可能我会在稍后添加梯子,但目前,这是单一的水平。 – ShadowFlame

回答

4

您可以使用sys.getsizeof(object)来获取Python对象的大小。但是,在调用容器上的sys.getsizeof时必须小心:它只给出容器的大小,而不是内容 - 请参阅this配方,以获取容器总大小(包括内容)的解释。在这种情况下,我们不需要太深入:我们可以手动添加容器的大小和内容的大小。

问题的类型的尺寸为:

# room type size 
>>> sys.getsizeof(RoomType("A",["Bottom"])) + sys.getsizeof("A") + sys.getsizeof(["Bottom"]) + sys.getsizeof("Bottom") 
233 

# position size 
>>> sys.getsizeof((0,0)) + 2*sys.getsizeof(0) 
120 

# One character size 
>>> sys.getsizeof("A") 
38 

让我们来看看不同的选择,假设你有N个房间:从position -> room_type

  1. 字典。这涉及将N*(size(position) + size(room_type)) = 353 N字节保存在内存中。
  2. position -> 1-character string字典。这涉及在内存中保留N*158字节。
  3. 字典从type -> set of positions。这涉及保留N*120字节加上存储字典密钥的小开销。

就内存使用情况而言,第三个选项显然更好。但是,通常情况下,您有CPU内存权衡。值得简要思考一下你可能做的查询的计算复杂性。为了找到给它的位置的房间,与每个三种选择的类型,你要不断:

  1. 查找在字典中的位置。这是一个O(1)查找,所以你总是有相同的运行时间(近似),与房间数量无关(对于大量房间)。
  2. 相同
  3. 看看每种类型,并为每种类型询问该位置是否在该类型的位置集合中。这是一个O(ntypes)查找,也就是说,它花费的时间与您拥有的类型数量成正比。请注意,如果您已经选择了列表而不是集合来存储给定类型的房间,那么这将会增加到O(nrooms * ntypes),这会损害您的表现。

与往常一样,优化时,考虑优化对内存使用率和CPU时间的影响很重要。这两者往往不一致。

作为一种替代方案,如果地图足够矩形,则可以考虑将类型保留在二维numpy字符数组中。我相信这会更有效率。在一个numpy的阵列中的每个字符是一个单字节,所以存储器使用将要少得多,并且CPU时间仍然是O(1)从室的位置查找来输入:

# Generate random 20 x 10 rectangular map 
>>> map = np.repeat('a', 100).reshape(20, 10) 
>>> map.nbytes 
200 # ie. 1 byte per character. 

一些另外小规模优化:

将房间类型编码为int而不是字符串。 Ints的大小为24字节,而一个字符的字符串的大小为38.

将位置编码为单个整数而不是元组。例如:

# Random position 
xpos = 5 
ypos = 92 

# Encode the position as a single int, using high-order bits for x and low-order bits for y 
pos = 5*1000 + ypos 

# Recover the x and y values of the position.  
xpos = pos/1000 
ypos = pos % 1000 

注意,这杀死可读性,所以才值得做,如果你想挤进业绩的最后一位。在实践中,您可能希望使用2的幂而不是10的幂作为分隔符(但10的幂有助于调试和可读性)。请注意,这会将每个位置的字节数从120增加到24.如果确实按照此路线行事,请考虑使用__slots__定义位置类,以告知Python如何分配内存,并将xposypos属性添加到类中。您不想用pos/1000pos % 1000陈述乱丢代码。

+0

感谢您的好评。我不能保证矩形,因为玩家在随机生成的迷宫中决定自己想要去哪里。 – ShadowFlame

+0

另外,如果地图是动态生成的,则数组不太合适:添加行或列可能非常缓慢。 –