从iterab设置numpy数组值的有效方法

2024-06-24 12:20:03 发布

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

我已经分配了一个给定大小的numpy大数组。例如

my_array = numpy.empty(10000, numpy.float)

数组的值可以由(模拟示例)生成

^{pr2}$

设置数组值的这一步要做很多次。例如,for k in range(0,1000)。除了开始时numpy.empty()所做的分配之外,我不想做任何其他的分配。在

我考虑过

my_array = numpy.array([k*val**2 for val in range(0,10000)])

但这看起来至少会有列表[k * val ** 2 for val in range(0, 10000)]的分配。对吗?在

我还看到了numpy.fromiter,但这似乎是为了构造数组。在

my_array = numpy.fromiter((k*val**2 for val in range(0,10000)), numpy.float, 10000)

这里真的还有一个分配吗?在


为了查看numpy.fromiter是否分配了一个数组,我尝试了以下操作

import numpy as np

iterable1 = (x*x for x in range(5))
iterable2 = (x*x + 1.0 for x in range(5))
my_array = np.fromiter(iterable1, np.float)
print(my_array)
print(hex(id(my_array)))

my_array = np.fromiter(iterable2, np.float)
print(my_array)
print(hex(id(my_array)))

在输出I中,打印的两个地址是不同的。这不意味着np.fromiter分配了一个新数组,然后分配给了my_array?在


Tags: innumpyformynprangeval数组
3条回答

^{}不执行任何进一步的分配。它只是从iterable创建一个数组。这就是函数的全部本质。它还接受一个count参数,该参数允许fromiter预先分配输出数组,而不是根据需要调整其大小。在

另外,如果您想一次更改所有项目,则不需要使用np.empty。在

毕竟,如果您是通过对一系列数字执行一些数学运算来构造新数组,那么您也可以对Numpy数组执行这些操作。在

下面是一个例子:

In [4]: a = np.arange(10)

In [6]: a**2 + 10
Out[6]: array([10, 11, 14, 19, 26, 35, 46, 59, 74, 91])

首先确保您了解变量赋值的作用:

 my_array = numpy.empty(10000, numpy.float)
 my_array = numpy.fromiter(...)

第二个作业代替第一个作业。my_array最初引用的对象是空闲的,并被垃圾回收。这只是基本的Python变量处理。要保持原始数组(可变对象),必须更改其值

^{pr2}$

但是生成<new values>的进程很可能会创建一个临时缓冲区(或两个或三个)。然后将这些值复制到目标。甚至x += 1也进行缓冲计算。几乎没有现成的裸体手术。在

一般来说,试图猜测numpy的内存分配是行不通的。效率只能通过时间测试来衡量,而不是通过猜测幕后发生了什么。在

除非需要迭代地填充“预分配”,否则不要费心:

In [284]: my_array = np.empty(10, int)
In [285]: for i in range(my_array.shape[0]):
     ...:     my_array[i] = 2*i+3
In [286]: my_array
Out[286]: array([ 3,  5,  7,  9, 11, 13, 15, 17, 19, 21])

这是一种糟糕的创建数组的方法,相比之下:

In [288]: np.arange(10)*2+3
Out[288]: array([ 3,  5,  7,  9, 11, 13, 15, 17, 19, 21])

fromiter方法更漂亮,但不是更快。在

In [290]: np.fromiter((i*2+3 for i in range(10)),int)
Out[290]: array([ 3,  5,  7,  9, 11, 13, 15, 17, 19, 21])

一些时间安排:

In [292]: timeit np.fromiter((i*2+3 for i in range(10000)),int)
100 loops, best of 3: 4.76 ms per loop
# giving a count drops the time to 4.28 ms

In [293]: timeit np.arange(10000)*2+3
The slowest run took 8.73 times longer than the fastest. This could mean that an intermediate result is being cached.
10000 loops, best of 3: 47.4 µs per loop

In [294]: %%timeit 
     ...: my_array=np.empty(10000,int)
     ...: for i in range(my_array.shape[0]):
     ...:     my_array[i] = 2*i+3
     ...:     
100 loops, best of 3: 4.72 ms per loop

In [303]: timeit np.array([i*2+3 for i in range(10000)],int)
100 loops, best of 3: 4.48 ms per loop

fromiter所需时间与显式循环一样长,而纯numpy解要快几个数量级。从时间上看,使用列表理解的np.array与使用生成器的fromiter之间几乎没有差别。在

从预先存在的列表创建数组需要大约1/3的时间。在

In [311]: %%timeit alist=[i*2+3 for i in range(10000)]
     ...: x=np.array(alist, int)
     ...: 
1000 loops, best of 3: 1.63 ms per loop

将列表分配给现有的empty数组并不快。在

In [315]: %%timeit alist=[i*2+3 for i in range(10000)]
     ...: arr = np.empty(10000,int)
     ...: arr[:] = alist
1000 loops, best of 3: 1.65 ms per loop
In [316]: %%timeit alist=[i*2+3 for i in range(10000)]; arr=np.empty(10000,int)
     ...: arr[:] = alist
1000 loops, best of 3: 1.63 ms per loop

有些numpy函数接受out参数。以这种方式重用数组可以节省一些时间。np.cross是一个利用这一点的函数(代码是Python的,可读)。在

从标量函数创建值的另一种“矢量化”方法:

In [310]: %%timeit f=np.frompyfunc(lambda i: i*2+3,1,1)
     ...: f(range(10000))
     ...: 
100 loops, best of 3: 8.31 ms per loop

根据评论中的解释,问题似乎如下:

  • 一个大的阵列需要经常更新,并且尽可能的高效
  • 更新源不仅是其他numpy数组,而且是任意Python对象(可以动态生成)。在

第二个问题是:只要您的值来自Python,将它们放入numpy数组将不会真正有效。这是因为您必须循环处理解释代码中的每个值。在

I was expecting to find the expression for ind, elem in enumerate(iterable): my_array[ind] = elem already packaged in a built in function. Do you know if the Python interpreter compiles that expression as a whole?

CPython的虚拟机与C++模型有很大不同;具体来说,编译器不能将表达式内嵌或将其解释为整体,以使其显著地更有效。即使它支持在C中执行这一特定操作的字节码指令,它仍然需要调用生成器的next方法,该方法在执行Python字节码之后,将每个值生成为堆分配的Python对象。在这两种情况下,每次迭代都会涉及到解释的代码,而您确实希望避免这种情况。在

解决问题的有效方法是从头开始设计,不要离开numpy。正如其他人在评论中所解释的那样,与在Python中逐个处理数据的实际成本相比,分配的成本(如果有效的话,由numpy完成)是微不足道的。我将设计如下:

  • 将尽可能多的代码转换为以本机方式使用numpy数组,从头开始;将返回numpy数组作为接口的一部分,而不必担心分配成本。在numpy内部尽可能多地执行循环,因此它们是用本机代码完成的。永远不要在Python中迭代大型数组的所有值。在
  • 如果不能使用numpy,请使用numpy.fromiter尽早将迭代器转换为numpy数组。在
  • 使用my_array[:] = new_array[:]或{}将新值引入数组。(前者在显微镜下需要更多的时间,但是当my_array在数据模型中的许多地方被共享时,它就更有意义了。)
  • 基准您感兴趣的操作。不要假设“复制是慢的”——它可能会证明C++中的操作“慢”比在C++中高效的Python再现更快。在

如果在完成上述操作之后,numpy不支持某些操作,并且测量结果表明它效率极低,那么可以使用Python/C API创建一个扩展模块,该模块高效地执行计算并以C中创建的numpy数组的形式返回结果

相关问题 更多 >