当我不包括dtype参数时,我想我大概理解了这个机制。假设我写:
代码块1
import numpy as np
test=np.empty((1,2))
print(test)
输出量:
[[-2.00000000e+000 6.80176343e-310]]
我明白这里发生了什么。操作系统将一个(连续的)内存块分配给数组test
。该测试只取内存块单元中已经存在的任意值。好极了!
现在,让我们稍微修改一下代码:
代码块2**
import numpy as np
test=np.empty((1,2), dtype=int)
print(test)
输出[[4611686018427387904 137669224398847]]
我的问题是,操作系统是如何找到一个只包含整数的内存块的?这看起来很神奇我的猜测是,实际上操作系统找到了一个包含float 64数字的内存,但只是截断了小数点后的所有内容,使其成为整数。我猜的对吗?我还有一个问题即使我多次运行代码块1和2,输出也不会改变。真奇怪我期望输出随着每次迭代而改变。我认为操作系统会在每次迭代时将数组“test”分配给不同的内存块。看起来好像操作系统一遍又一遍地将数组“test”分配给同一个内存块。奇怪。只有当我改变形状时输出才改变。你能解释一下输出不改变的原因吗?
2条答案
按热度按时间kxeu7u2r1#
我的问题是,操作系统是如何找到一个只包含整数的内存块的?
它不一定是以前用于整数的内存。
它找到了一个内存块,不管之前有什么,它都把它们重新解释为整数。
这里有一个重新解释记忆的例子。我把0到9作为浮点数。然后,我使用ndarray.view()将它们重新解释为整数。
输出量:
当你把浮点数中构成1的位重新解释为整数时,你会得到一个完全不同的数字。
我还有一个问题即使我多次运行代码块1和2,输出也不会改变。
内存分配器可能会返回您刚刚释放的同一块内存。这是完美的尺寸,毕竟,你不再使用它了。
这里有一个例子可以证明这一点。我分配一个空数组。我来决定地址。我把它添加到一个集合中,以找出数组中存储了多少个唯一地址。然后我循环。我只保留对最近的数组
a
的引用。您可能会认为这会导致一百个不同的地址。但内存分配器比这更聪明:它可以告诉我,除了一个数组之外,所有我分配的数组都不再被引用,它可以重用这些内存。它只显示两个唯一指针。
许多使用NumPy的程序会随着时间的推移分配和释放许多NumPy数组。(例如,如果
a
和b
是数组,则a = a + b
分配并释放一个数组。)如果内存分配器不能重用已释放的内存,这将是一种浪费。另一种可能的解释是,这个相同的模式在内存中出现了多次,这是Python正在做的其他事情。没有足够的信息来说明哪一个正在发生。
nom7f22z2#
记忆不是这样运作的。
好吧,在解释型语言(如Python)中,每个数据(每个值)都伴随着一个描述符,给出了它的类型。所以int或者float已经是一个相当复杂的结构了,你不希望随机找到它们。
Numpy比Python更像C。它是Python绑定到一个主要是C库。
因此,
np.empty
所做的只是malloc
的C等价物。它找到了一个记忆的地方,而不去关心它是什么。并分配它。请注意,这个内存内容将被解释为一堆给定dtype的数据。例如,
np.empty((1,2))
查找1×2×8字节(需要存储1行2个float 64类型的数据)= 16字节的内存。malloc(16)
也是。从现在开始,这个内存被解释为float64
。几乎所有随机字节都是浮点数(如果不是所有的位组合都有意义,那将是浪费位。这意味着我们不能像用字节那样表示尽可能多的值)。
所以,不管这16个字节是什么,你都可以把它们解释为2个浮点数(如果你不能,那么它们就是NaN。所以它仍然是东西)
在您的示例中,当numpy决定将它们用于
test
数组时,内存中已经存在的16个字节恰好是所以解释为float 64,前8个字节是(0,0,0,0,0,0,192)。我们必须倒过来读。即(192,0,0,0,0,0,0)。这是二进制的
如果您选择将这64位解释为表示浮点数,则第一位为1,因此它是负数。接下来的11位是1,后面是10个0。所以,指数是1000000000,或者十进制的1024。由于浮点数64中的指数是相对于1023的(1023是指数0,1024是指数1,1025是指数2,1022是指数-1,1021是指数-2,...),所以这里的指数是1。
那么剩下的52位都是0。Float 64表示意味着在这52位之前有一个隐式1。所以尾数是1后面跟着52个0。也就是说1+0/2+0/2²+0/2 <$+...+0/2 <$² = 1。
所以1乘以2的指数1 = 1×2 = 2。-2,因为指数是负数。
同样,接下来的8个字节,代表第二个浮点数是[0,0,125,53,157,56,148,22](反向,因为又是小端)。
以二进制
符号是0
指数为0000000000 = 0。这是一个特殊情况(一个次正常数),所以指数是-1022。尾数是
0 0000 01111101 00110101 10011101 00111000 10010100 00010110
(包括次正常数的隐式0)。所以
正如预期的那样,它是6.80176343e-310
现在,如果你分配了一个int 32的2x2数组,也就是2x2 x4 =16字节,并且碰巧得到了完全相同的字节,那么你的数组将是
这是由字节表示(在小端):
所以
0, 0, 0, 0, 0, 0, 0, 192, 22, 148, 56, 157, 53, 125, 0, 0
相同的字节。
所以,这就是整件事。任何一组字节都可以被解释为
uint16
、int32
或float64
。除了“bytes”之外,没有其他类型。您可以使用
frombuffer
或tobytes
比我更容易地“玩”这种方式嗯,我被带走了。最后一个游戏和你的问题有很大的关系。但它告诉你:相同的字节,2种方式来解释它们。
这就是
np.empty
的情况。它找到一些字节,而不试图从它们的内容或类型中选择它们(同样,没有类型。它们只是字节)。然后,它决定将then解释为float 64(就像你对
test
所做的那样)或uint8
(就像我对t2
所做的那样)或任何需要的东西。