数组元素的Numpy内存分配

2024-05-09 18:57:59 发布

您现在位置:Python中文网/ 问答频道 /正文

所以最近我一直在做一个优化项目,我想使用numpy数组而不是python内置列表。它将是一个在两个轴上都具有固定长度的二维阵列。我还想最大限度地使用现金,使代码尽可能快。但是,当使用id(var)函数时,会出现意外结果:

代码:

 a = numpy.ascontiguousarray([1,2,3,4,5,6,7,8,9], dtype=numpy.int32)
 for var in a:
    print(hex(id(var)))

返回:

x1aaba10d8f0
0x1aaba1f33d0
0x1aaba10d8f0
0x1aaba1f33d0
0x1aaba10d8f0
0x1aaba1f33d0
0x1aaba10d8f0
0x1aaba1f33d0
0x1aaba10d8f0

这对我来说是非常奇怪的,这意味着两个变量位于同一个内存块中(这是一件事吗?)。不管怎样,是我没有正确地理解它吗

作为一个附带问题,是否可以用成本较低的方法完成构建二维阵列的原始任务?Numpy阵列附带了许多我不需要的函数。我只需要两样东西:

  1. 为了能够正常使用[:-1]语法进行反转
  2. 检查一个==另一个是否有效

提前感谢您的帮助:-)


Tags: 项目函数代码numpyid列表var数组
2条回答

id(var)并不像您想象的那样工作。实际上,id(var)返回指定对象var的唯一ID,但var不是a的单元格var是一个Python对象,它引用了a的单元格。请注意a不包含这样的对象,因为它效率太低(并且数据不会按要求连续)。看到重复ID的原因是以前的var对象已被回收

您真正想要的阵列类型不清楚,目的也不清楚。但谈到连续(或连续)和缓存,表明您不清楚Python是如何工作的

首先,Python一直都是面向对象的。整数、字符串、列表都是某个类的对象,具有相关的方法和属性。对于内置类,我们对存储几乎没有发言权

让我们列出一个小清单:

In [89]: alist = [1,2,3,1000,1001,1000,'foobar']
In [90]: alist
Out[90]: [1, 2, 3, 1000, 1001, 1000, 'foobar']

列表有一个数据缓冲区,用于存储指向内存中其他位置的对象的引用(如果愿意,可以使用指针)。id可能会给出一些关于where的概念,它不应该被理解为c语言意义上的“指针”

对于此列表:

In [91]: [id(i) for i in alist]
Out[91]: 
[9784896,
 9784928,
 9784960,
 140300786887792,
 140300786888080,
 140300786887792,
 140300786115632]

1,2,3的id值很小,因为Python在开始时初始化了小整数(最多256个)。因此,所有使用都将具有唯一的id

In [92]: id(2)
Out[92]: 9784928

在列表创建中1000似乎是唯一的,但在该上下文之外并非如此

In [93]: id(1001)
Out[93]: 140300786888592

看起来字符串也被缓存了——但这只是解释器的选择,我们不应该指望它

In [94]: id('foobar')
Out[94]: 140300786115632

反向列表是一个新列表,具有自己的指针数组。但参考文献相同:

In [95]: rlist = alist[::-1]
In [96]: rlist
Out[96]: ['foobar', 1000, 1001, 1000, 3, 2, 1]
In [97]: rlist[5],id(rlist[5])
Out[97]: (2, 9784928)

[::-1]这样的索引操作应该只取决于列表中的项目数。它不取决于值实际指向的位置。其他副本也一样。即使是附加到数组中,也是相对独立于时间的(它在数据缓冲区中保持增长空间)。实际处理列表中的对象可能取决于它们存储在内存中的位置,但我们对此没有什么可说的

“2d”列表实际上是包含列表元素的列表;嵌套列表。子列表存储在内存中的其他位置,就像字符串和数字一样。从这个意义上说,嵌套列表是不连续的

那么阵列呢

In [101]: x = np.arange(12)
In [102]: x
Out[102]: array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])
In [104]: x.__array_interface__
Out[104]: 
{'data': (57148880, False),
 'strides': None,             # default (8,)
 'descr': [('', '<i8')],
 'typestr': '<i8',
 'shape': (12,),
 'version': 3}
In [105]: x.nbytes     # 12*8 bytes
Out[105]: 96

x是一个ndarray对象,具有shapestridesdtype等属性。和一个数据缓冲区。在本例中,是96字节长的c数组,在“57148880. We can't use that number, but I find it useful when comparing this 数组接口dict across arrays. A视图`尤其具有相同或相关的值

In [106]: x.reshape(3,4)
Out[106]: 
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
In [107]: x.reshape(3,4).__array_interface__['data']
Out[107]: (57148880, False)
In [108]: x.reshape(3,4)[1,:].__array_interface__['data']
Out[108]: (57148912, False)     # 32 bytes later

数组数据缓冲区具有实际值,而不是引用。这里使用intdtype,每个8字节被解释为一个“int64”值

你的id迭代有效地要求一个列表[x[i] for i in range(n)]。数组的元素必须是“未绑定的”,并且是一个新对象,类型为np.int64。虽然不是数组,但它确实有许多与1元素数组相同的属性

In [110]: x[4].__array_interface__
Out[110]: 
{'data': (57106480, False),
 ...
 'shape': (),....}

data值与x值无关

只要在现有数组上使用numpy方法,速度就很好,通常比同等的列表方法快10倍。但是如果从列表开始,则创建数组需要时间。而且像列表一样处理数组的速度很慢

x的反面呢

In [111]: x[::-1].__array_interface__
Out[111]: 
{'data': (57148968, False),
 'strides': (-8,),
 'descr': [('', '<i8')],
 'typestr': '<i8',
 'shape': (12,),
 'version': 3}

这是一个新的数组,但是有一个不同的strides(-8,),并且data指向缓冲区的末尾880+96-8

相关问题 更多 >