最近在看代码库rlkit时,发现一句有意思的代码和注释(如下所示),大意是从列表中随机选择一个元素时使用np.random.randint比np.random.choice更加高效,相关的解释是np.random.choice会进行一些不必要的复制操作,使得效率相较于randint低一些。
possible_future_obs_idxs
我做了相关的实验进行对比,发现使用np.random.random的实现比np.random.randint更快,并且是否使用参数size=1对结果的影响也很大,文末进行了总结。
1. random, randint, choice
生成长度为1e6的随机array,从中随机选择1个数和1000个数,比较两种情况下运行速度,测试代码如下:
import
进行1e6次的实验结果(单位:s):
更直观的使用ipython的%timeit的结果:
import
以上实验结果在不同环境中跑会有些许误差,但是应该能得到以下结论:
- 随机选择一个数时,random最快,速度是randint的2倍;
- 随机选择一个数时,不指定size=1的参数更快,以random为例,速度差异能到5倍左右;
- 随机选择n个数时,指定size=n,randint最快。
以下是我的猜测和解释,如有问题欢迎指出:
- choice会有内存申请和复制array的操作,通常是最慢的;
- 指定size会有内存申请的操作,并且会转化为ndarray类型,因此产生一个随机数时不指定size=1更快;
- random的输出是python的float类型,指定size后是ndarray类型,乘以N时,float类型和N都直接进行python的float类型运算,而ndarray乘以N多进行了数据转化和传递操作(转化为numpy的类型并传入底层进行运算,可以参考这个问题https://www.zhihu.com/question/24789359/answer/55643155),randint内部实现其实是一样的,但是乘以N的操作本身是在numpy的类型中进行的,减少了两次数据转化和传递,速度更快。
>>>
为了让大家体会到数据格式转化的耗时,补充一个实验,注意上面是np.random.random()乘以的是python的常数,均为python内置数据类型的运算,而如果乘以的是numpy数据类型,结果可能不一样:
import
因为涉及到数据格式转化和向底层代码的数据传递,简单的python内置运算可能比numpy更快,当运算较多时,numpy并行的优势才能显示出来。
2. 案例优化
我们以rlkit的这段代码为例进行优化,该段代码的目的是从不等长的列表组里对每个列表随机选择1个数据。这个案例特殊的地方在于列表组里有N个不等长的子列表,还要考虑循环处理子列表的时间,使用numpy并行进行向量对应位相乘速度更快,选择random_idx的过程能够加速80倍左右。
import
3. Take Away
- numpy的运算会涉及到数据格式转化和向底层代码的数据传递,当运算比较少时python内置运算可能比numpy更快,所以要注意运算量的类型,numpy的优势在于大规模的并行计算;
- 随机选择一个数时,尽量避免设置size=1,不设置size的运行速度从快到慢为:random > randint > choice;
- 随机选择n个数时,由于randint(size=n)内置了random(size=n) * N的操作,比外部实现的random(size=n) * N少了两次数据转化而更快,速度从快到慢为:randint > random > choice。