我为以下代码得到了非常奇怪的时间:
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
为什么 float64
比 float
慢两倍?为什么 float32
比float64慢5倍?
有没有办法避免使用 np.float64
的惩罚,并让 numpy
函数返回内置 float
而不是 float64
?
我发现使用 numpy.float64
比Python的浮点慢得多, numpy.float32
甚至更慢(即使我在32位机器上) .
numpy.float32
在我的32位机器上 . 因此,每次我使用各种numpy函数(如 numpy.random.uniform
)时,我都会将结果转换为 float32
(以便以32位精度执行进一步的操作) .
有没有办法在程序或命令行中的某处设置单个变量,并使所有numpy函数返回 float32
而不是 float64
?
EDIT #1:
在算术计算中,numpy.float64比浮点慢 10 times . 它非常糟糕,甚至在计算之前转换为浮动和返回使程序运行速度提高了3倍 . 为什么?我能做些什么来解决它吗?
我想强调一下,我的时间安排不是由以下任何原因引起的:
-
函数调用
-
numpy和python float之间的转换
-
创建对象
我更新了我的代码,以便更清楚地解决问题所在 . 使用新代码,我发现使用numpy数据类型可以看到十倍的性能:
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
只是为了它的地狱,我也尝试过:
从datetime import datetime import numpy as 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
快3倍,而不是按原样使用它 . 尽管如此,转换还是要付出代价,所以总体而言,它比纯粹的python float
慢了3倍多 .
我的机器是:
-
英特尔酷睿2双核T9300(2.5GHz)
-
WinXP Professional(32位)
-
ActiveState Python 3.1.3.5
-
Numpy 1.5.1
EDIT #2:
谢谢你的答案,他们帮助我了解如何处理这个问题 .
但我仍然想知道确切的原因(可能基于源代码)为什么下面的代码使用 float64
比使用 float
慢10倍 .
EDIT #3:
我重新运行Windows 7 x64(Intel Core 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倍 . 仍然是一个显着的差异 . 我想弄清楚它来自哪里 .
END OF EDITS
8 回答
CPython floats are allocated in chunks
将numpy标量分配与
float
类型进行比较的关键问题是CPython总是为大小为N的块中的float
和int
对象分配内存 .在内部,CPython维护一个块链接列表,每个块都足够容纳N
float
对象 . 当您调用float(1)
时,CPython会检查当前块中是否有可用空间;如果没有,它会分配一个新块 . 一旦它在当前块中有空间,它只需初始化该空间并返回指向它的指针 .在我的机器上,每个块可以容纳41个
float
对象,因此第一个float(1)
调用会有一些开销,但是随着内存的分配和准备,接下来的40个运行速度要快得多 .Slow numpy.float32 vs. numpy.float64
看起来numpy在创建标量类型时可以采用2条路径:快速和慢速 . 这取决于标量类型是否具有可以推迟参数转换的Python基类 .
出于某种原因,
numpy.float32
被硬编码为采用较慢的路径(defined by the _WORK0 macro),而numpy.float64
则有机会采用更快的路径(defined by the _WORK1 macro) . 请注意,scalartypes.c.src
是在构建时生成scalartypes.c
的模板 .您可以在Cachegrind中将其可视化 . 我已经包含了屏幕截图,显示了构建
float32
与float64
的多少次调用:float64 takes the fast path
float32 takes the slow path
Updated - 慢速/快速路径采用哪种类型可能取决于操作系统是32位还是64位 . 在我的测试系统上,Ubuntu Lucid 64位,
float64
类型比float32
快10倍 .在像这样的大循环中使用Python对象操作,无论它们是
float
,np.float32
,总是很慢 . NumPy对于向量和矩阵的操作非常快,因为所有操作都是由用C语言编写的库的大部分数据执行的,而不是由Python解释器执行的 . 在解释器中运行的代码和/或使用Python对象总是很慢,并且使用非本机类型会使它更慢 . 这是可以预料的 .如果您的应用程序运行缓慢且需要对其进行优化,则应尝试将代码转换为直接使用NumPy的矢量解决方案,并且速度快,或者您可以使用Cython等工具在C中创建循环的快速实现 .
也许,这就是为什么你应该直接使用Numpy而不是使用循环 .
答案很简单:内存分配可能是其中的一部分,但最大的问题是numpy标量的算术运算是使用“ufuncs”来完成的,这对于数百个值不仅仅是1而言是快速的 . 有一些开销选择正确的函数来调用和设置循环 . 标头不需要的开销 .
将标量转换为0-d数组然后传递给相应的numpy ufunc然后为NumPy支持的许多不同标量类型中的每一个编写单独的计算方法更容易 .
意图是标量数学的优化版本将被添加到C中的类型对象 . 这可能仍然会发生,但它从未发生过,因为没有人有足够的动机去做 . 可能是因为解决方法是将numpy标量转换为具有优化算术的Python标量 .
Summary
如果算术表达式包含
numpy
和内置数字,则Python算法运行速度较慢 . 避免这种转换几乎消除了我报告的所有性能下降 .Details
请注意,在我的原始代码中:
类型
float
和numpy.float64
混合在一个表达式中 . 也许Python必须将它们全部转换为一种类型?如果运行时没有改变(而不是增加),那就表明Python确实在做什么,解释了性能拖累 .
实际上,运行时间下降了1.5倍!这怎么可能? Python可能不得不做的最糟糕的事情是这两次转换?
我根本不涉及转换,而且在不匹配的类型上碰巧是超慢的 . 阅读
numpy
源代码可能有所帮助,但这超出了我的技能 .无论如何,现在我们可以通过将转换移出循环来显然加快速度:
正如预期的那样,运行时间大幅减少:再增加2.3倍 .
公平地说,我们现在需要通过将文字常量移出循环来稍微改变
float
版本 . 这导致微小(10%)减速 .考虑到所有这些更改,代码的
np.float64
版本现在仅比等效的float
版本慢30%;这种荒谬的5倍性能打击基本消失了 .为什么我们仍然看到30%的延迟?
numpy.float64
数字占用的空间与float
相同,因此不会是原因 . 对于用户定义的类型,算术运算符的分辨率可能更长 . 当然不是主要问题 .如果您正在进行快速标量算术,那么您应该查看像gmpy而不是
numpy
这样的库(正如其他人所指出的那样,后者针对向量运算而不是标量运算进行了优化) .我也可以确认结果 . 我试图看看使用所有numpy类型会是什么样子,并且差异仍然存在 . 那么,我的测试是:
所以在这一点上,numpy类型都相互交互,但10x的差异仍然存在(2秒vs 0.2秒) .
如果我不得不猜测,我会说有两个可能的原因导致默认浮点类型更快 . 第一种可能性是python在处理某些数字操作或一般循环(例如循环展开)的情况下进行了重要的优化 . 第二种可能性是numpy类型涉及额外的抽象层(即必须从地址读取) . 为了研究每种效果,我做了一些额外的检查 .
一个区别可能是python必须采取额外步骤来解决float64类型的结果 . 与生成高效表的编译语言不同,python 2.6(可能还有3个)在解决你所做的事情上花费很大一般认为是免费的 . 即使是简单的X.a分辨率也必须在每次调用时解析点运算符 . (这就是为什么如果你有一个调用instance.function()的循环,你最好在循环外声明一个变量“function = instance.function” .
根据我的理解,当你使用python标准运算符时,这些与使用“import operator”中的运算符非常相似 . 如果用你的,*和%替换add,mul和mod,你会看到与标准运算符(两种情况)相比,静态性能下降约0.5秒 . 这意味着通过包装运算符,标准的python浮点运算速度会慢3倍 . 如果你再做一次,使用operator.add和那些变量大约增加0.7秒(超过1米的试验,分别从2秒和0.2秒开始) . 那是5倍的缓慢 . 所以基本上,如果这些问题中的每一个都发生两次,那么你基本上要慢10倍 .
所以让's assume we'重温python解释器片刻 . 情况1,我们对本机类型进行操作,让's say a+b. Under the hood, we can check the types of a and b and dispatch our addition to python'的优化代码 . 情况2,我们有另外两种类型的操作(也是b) . 在引擎盖下,我们检查他们是否_1699028_不是) . 我们继续讨论'else'案件 . else案例将我们发送给了类似的东西 . add (b) . 一个 . add 然后可以发送到numpy 's optimized code. So at this point we have had additional overhead of an extra branch, one ' . ' get slots property, and a function call. And we' ve只进入了加法操作 . 然后我们必须使用结果来创建一个新的float64(或改变现有的float64) . 同时,python本机代码可能通过专门处理其类型来欺骗,以避免这种开销 .
基于对python函数调用和范围开销的成本的上述检查,numpy很容易因为它的c数学函数而导致9x惩罚 . 我完全可以想象这个过程比简单的数学运算调用要花费很多倍 . 对于每个操作,numpy库将不得不涉及到python层以进入其C实现 .
所以在我看来,这个原因可能是因为这个原因:
这个简单的情况显示0.2秒对0.14秒的差异(显然,短路更快) . 我认为你所看到的主要是这些问题加起来 .
为了避免这种情况,我可以想到几个可能的解决方案,主要回应所说的内容 . Selinap说,第一个解决方案是尽可能地将您的评估保持在NumPy中 . 大量的损失可能是由于接口造成的 . 我会研究如何将你的工作分配到numpy或其他一些用C优化的数字库(gmpy已被提及) . 目标应该是尽可能多地将C推入C中,然后将结果返回 . 你想要从事大工作,而不是做大量的小工作 .
当然,第二种解决方案是在python中进行更多的中间和小型操作 . 显然,使用本机对象会更快 . 它们将成为所有分支语句的第一个选项,并且始终具有到C代码的最短路径 . 除非您对固定精度计算或默认运算符的其他问题有特定需求,否则我不明白为什么人们不会将直接python函数用于很多事情 .
真奇怪......我在Ubuntu 11.04 32bit,python 2.7.1,numpy 1.5.1(官方软件包)中确认结果:
我不明白为什么float32应该比float64慢5倍 .