numpy float:在算术运算中比内build速度慢10倍?

下面的代码我正在得到非常奇怪的计时:

import numpy as np s = 0 for i in range(10000000): s += np.float64(1) # replace with np.float32 and built-in float 
  • 内置浮动:4.9秒
  • float64:10.5 s
  • float32:45.0 s

为什么float64float更慢两倍? 为什么float32比float64慢5倍?

有没有办法避免使用np.float64的惩罚,并有numpy函数返回内置的float而不是float64

我发现使用numpy.float64比Python的float慢得多,而numpy.float32甚至更慢(即使我在32位机器上)。

numpy.float32在我的32位机器上。 因此,每次我使用numpy.random.uniform等各种numpy函数,我将结果转换为float32 (以便进一步的操作将以32位精度执行)。

有什么办法可以在程序或命令行中设置一个单独的variables,并使所有numpy函数返回float32而不是float64

编辑#1:

numpy.float64比算术计算中的浮点数慢10倍 。 这是非常糟糕的,甚至转换为浮动和返回之前的计算使程序运行速度提高3倍。 为什么? 有什么我可以做的,以解决它?

我想强调,我的时间不是由于以下任何一种情况:

  • 函数调用
  • numpy和python float之间的转换
  • 对象的创build

我更新了代码,使问题更加清楚。 使用新的代码,使用numpy数据types看起来会使性能下降十倍:

 from datetime import datetime import numpy as np START_TIME = datetime.now() # one of the following lines is uncommented before execution #s = np.float64(1) #s = np.float32(1) #s = 1.0 for i in range(10000000): s = (s + 8) * s % 2399232 print(s) print('Runtime:', datetime.now() - START_TIME) 

时间是:

  • float64:34.56s
  • float32:35.11s
  • float:3.53s

只是为了地狱,我也试过:

从date时间导入date时间导入numpy为np

 START_TIME = datetime.now() s = np.float64(1) for i in range(10000000): s = float(s) s = (s + 8) * s % 2399232 s = np.float64(s) print(s) print('Runtime:', datetime.now() - START_TIME) 

执行时间是13.28秒; 实际上,将float64转换为float和back的速度比使用它快3倍。 尽pipe如此,转换还是会花钱,所以总体来说,它比纯python float要慢3倍多。

我的机器是:

  • 英特尔酷睿2双核T9300(2.5GHz)
  • WinXP专业版(32位)
  • ActiveState Python 3.1.3.5
  • Numpy 1.5.1

编辑#2:

谢谢你的答案,他们帮助我理解如何处理这个问题。

但是我还是想知道确切的原因(也许是基于源代码)为什么下面的代码用float64运行速度比用float慢10倍。

编辑#3:

我重新运行Windows 7 x64(英特尔酷睿i7 930 @ 3.8GHz)下的代码。

同样,代码是:

 from datetime import datetime import numpy as np START_TIME = datetime.now() # one of the following lines is uncommented before execution #s = np.float64(1) #s = np.float32(1) #s = 1.0 for i in range(10000000): s = (s + 8) * s % 2399232 print(s) print('Runtime:', datetime.now() - START_TIME) 

时间是:

  • float64:16.1s
  • float32:16.1s
  • float:3.2s

现在,两个np浮点数(64或32)都比内置float慢5倍。 仍然是一个重要的区别。 我试图找出它来自哪里。

编辑结束

CPython浮点数分块分配

将numpy标量分配与floattypes进行比较的关键问题是,CPython总是为大小为N的块分配floatint对象的内存。

在内部,CPython维护一个块的链表,每个块的大小足以容纳N个float对象。 当你调用float(1) CPython检查当前块是否有可用空间; 如果不是,则分配一个新的块。 一旦它在当前块中有空格,它只是初始化该空间并返回一个指针。

在我的机器上,每个块可以容纳41个float对象,所以第一个float(1)调用会有一些开销,但是随着内存被分配和准备好,接下来的40个会更快地运行。

慢numpy.float32与numpy.float64

看起来numpy在创build标量types时有2个path可用:快速和慢速。 这取决于标量types是否具有可以推迟参数转换的Python基类。

出于某种原因, numpy.float32被硬编码以获取较慢的path(由_WORK0macros定义) ,而numpy.float64有机会采用较快的path(由_WORK1macros定义) 。 请注意, scalartypes.c.src是一个在构build时生成scalartypes.c的模板。

你可以在Cachegrind中看到这个。 我已经包含了屏幕截图,显示了构造一个float32float64多less次调用:

float64采取快速path

float64采取快速路径

float32采取慢path

float32采取慢路径

更新 – 哪种types需要慢/快path可能取决于操作系统是32位还是64位。 在我的testing系统Ubuntu Lucid 64位上, float64types比float32快10倍。

在像这样的np.float32循环中使用Python对象操作,无论它们是floatnp.float32 ,总是很慢。 NumPy对于向量和matrix的运算是快速的,因为所有的操作都是用C语言编写的库的一部分,而不是由Python解释器执行。 在解释器中运行的代码和/或使用Python对象的代码总是很慢,而使用非本地types会使代码更慢。 这是可以预料的。

如果你的应用程序很慢,你需要优化它,你应该尝试把你的代码转换成直接使用NumPy的向量解决scheme,而且速度很快,或者你可以使用Cython这样的工具来创build一个快速的C语言实现。

也许,这就是为什么你应该直接使用Numpy而不是使用循环。

 s1 = np.ones(10000000, dtype=np.float) s2 = np.ones(10000000, dtype=np.float32) s3 = np.ones(10000000, dtype=np.float64) np.sum(s1) <-- 17.3 ms np.sum(s2) <-- 15.8 ms np.sum(s3) <-- 17.3 ms 

答案是非常简单的:内存分配可能是其中的一部分,但最大的问题是,numpy标量的算术运算是使用“ufuncs”来完成的,这个函数的速度不仅仅是1,而是数百个值。有一些开销在select正确的函数来调用和设置循环。 标量不需要的开销。

将标量转换为0-d数组然后传递给相应的numpy ufunc比较容易,然后为NumPy支持的许多不同标量types分别编写单独的计算方法。

其目的是将标量math的优化版本添加到C中的types对象中。这种情况仍然可能发生,但是从未发生,因为没有人有足够的动机去做。 可能是因为解决方法是将numpy标量转换为Python标量,这些标量优化了算术。

概要

如果一个算术expression式既包含numpy也包含内置数字,Pythonalgorithm运行速度较慢。 避免这种转换几乎消除了我所报告的所有性能下降。

细节

请注意,在我的原始代码中:

 s = np.float64(1) for i in range(10000000): s = (s + 8) * s % 2399232 

typesfloatnumpy.float64被混合在一个expression式中。 也许Python必须将它们全部转换为一种types?

 s = np.float64(1) for i in range(10000000): s = (s + np.float64(8)) * s % np.float64(2399232) 

如果运行时没有改变(而不是增加),那么这就是Python正在做的事情,解释了性能拖延。

实际上,运行时间下降了1.5倍! 这怎么可能? Python可能不得不做的最糟糕的事情是这两个转换?

我真的不知道。 也许Python必须dynamic地检查需要转换的内容,这需要花费一些时间,并被告知要执行的精确转换是否会使其更快。 也许,一些完全不同的机制被用于算术(根本不涉及转换),并且在不匹配的types上恰好是超慢的。 阅读numpy源代码可能会有所帮助,但这超出了我的技能。

无论如何,现在我们显然可以通过将转换移出循环来加快速度:

 q = np.float64(8) r = np.float64(2399232) for i in range(10000000): s = (s + q) * s % r 

正如所料,运行时间大幅减less了2.3倍。

公平地说,我们现在需要稍微改变float ,通过将文字常量移出循环。 这导致了一个很小的(10%)放缓。

考虑到所有这些变化,代码的np.float64版本现在只比同等的float版本慢30% 可笑的5倍的performance已经大大消失了。

为什么我们仍然看到30%的延迟? numpy.float64数字占用与float相同的空间量,所以不会是原因。 对于用户定义的types,算术运算符的分辨率可能需要更长的时间。 当然不是一个主要的问题。

如果你使用的是快速标量算术,那么你应该把图像看作gmpy而不是numpy (正如其他人已经注意到的那样,后者对vector运算而不是标量运算优化)。

我也可以确认结果。 我试图看看使用所有numpytypes会是什么样子,并且差异依然存在。 那么,我的testing是:

 def testStandard(length=100000): s = 1.0 addend = 8.0 modulo = 2399232.0 startTime = datetime.now() for i in xrange(length): s = (s + addend) * s % modulo return datetime.now() - startTime def testNumpy(length=100000): s = np.float64(1.0) addend = np.float64(8.0) modulo = np.float64(2399232.0) startTime = datetime.now() for i in xrange(length): s = (s + addend) * s % modulo return datetime.now() - startTime 

所以在这一点上,numpytypes都是相互作用的,但10倍的差异持续存在(2秒比0.2秒)。

如果我不得不猜测,我会说有两个可能的原因,为什么默认的浮点types更快。 第一种可能性是python在处理某些数字操作或一般循环(例如循环展开)时进行重要的优化。 第二种可能性是numpytypes涉及额外的抽象层(即不得不从地址读取)。 为了研究每一个的影响,我做了一些额外的检查。

一个区别可能是python不得不采取额外的步骤来解决float64types的结果。 与生成高效表的编译语言不同,python 2.6(也可能是3)对于解决你通常认为是免费的事情有很大的代价。 即使是一个简单的Xa分辨率,每当它被调用的时候,都必须parsing点运算符。 (这就是为什么如果你有一个循环调用instance.function()你最好有一个variables“function = instance.function”在循环外声明)。

根据我的理解,当你使用Python标准操作符时,这些与使用“import operator”中的那些非常相似。 如果你用+,*和%replaceadd,mul和mod,你会看到静态性能比标准运算符(两种情况)大约0.5秒。 这意味着通过包装运算符,标准的python float操作速度要慢3倍。 如果你再做一次,使用operator.add和这些变体大约增加0.7秒(超过1米试验,分别从2秒和0.2秒开始)。 这是5倍慢。 所以基本上,如果每个问题都发生两次,那么基本上就是慢了10倍。

那么让我们假设我们是python解释器。 案例1,我们对本地types进行操作,比如说a + b。 在引擎盖下,我们可以检查a和b的types,并派发我们的除了python的优化代码。 情况2,我们有另外两种types的操作(也是a + b)。 在引擎盖下,我们检查它们是否是本地types(它们不是)。 我们继续讨论“其他”案例。 其他情况下,我们就像一个。 (b)。 一个。 然后添加可以做一个调度numpy的优化代码。 所以在这一点上,我们有额外的分支额外的开销,一个''。 获取slots属性和一个函数调用。 而我们只进入了加法操作。 然后我们必须使用结果创build一个新的float64(或者改变一个现有的float64)。 同时,python本地代码可能会通过专门处理它的types来避免这种开销。

基于以上对python函数调用的昂贵性和范围开销的检查,numpy很容易导致9x的惩罚只是来自它的cmath函数。 我完全可以想象这个过程比简单的math操作调用要花费很多时间。 对于每一个操作,numpy库将不得不通过python层来获得它的C实现。

所以在我看来,这个原因可能是由于这个原因:

 length = 10000000 class A(): X = 10 startTime = datetime.now() for i in xrange(length): x = AX print "Long Way", datetime.now() - startTime startTime = datetime.now() y = AX for i in xrange(length): x = y print "Short Way", datetime.now() - startTime 

这个简单的例子显示了0.2秒和0.14秒之间的差别(很明显,速度更快)。 我认为你所看到的主要是这些问题的总和。

为了避免这一点,我可以想到一些可能的解决scheme,主要是回应已经说过的话。 如Selinap所说,第一个解决scheme是尽可能将评估内容保留在NumPy中。 大量的损失可能是由于接口。 我会研究如何派遣你的工作成numpy或其他一些数字库优化的C(gmpy已被提及)。 目标应该是尽可能多地将C推到C,然后将结果返回。 你想投入大的工作,而不是大量的小工作。

第二种解决scheme当然是在Python中进行更多的中间和小型操作。 显然,使用本地对象将会更快。 它们将成为所有分支语句的第一个选项,并且始终具有到C代码的最短path。 除非你有特定的需求来进行精确的计算或者使用默认的运算符来处理其他问题,否则我不明白为什么在很多情况下不能使用直接的python函数。

真的很奇怪…我确认在Ubuntu 11.04 32bit,python 2.7.1,numpy 1.5.1(官方软件包)中的结果:

 import numpy as np def testfloat(): s = 0 for i in range(10000000): s+= float(1) def testfloat32(): s = 0 for i in range(10000000): s+= np.float32(1) def testfloat64(): s = 0 for i in range(10000000): s+= np.float64(1) %time testfloat() CPU times: user 4.66 s, sys: 0.06 s, total: 4.73 s Wall time: 4.74 s %time testfloat64() CPU times: user 11.43 s, sys: 0.07 s, total: 11.50 s Wall time: 11.57 s %time testfloat32() CPU times: user 47.99 s, sys: 0.09 s, total: 48.08 s Wall time: 48.23 s 

我不明白为什么float32应该比float64慢5倍。