首页 文章

numpy float:比算术运算中的内置慢10倍?

提问于
浏览
47

我为以下代码得到了非常奇怪的时间:

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的浮点慢得多, 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 回答

  • 9

    CPython floats are allocated in chunks

    将numpy标量分配与 float 类型进行比较的关键问题是CPython总是为大小为N的块中的 floatint 对象分配内存 .

    在内部,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中将其可视化 . 我已经包含了屏幕截图,显示了构建 float32float64 的多少次调用:

    float64 takes the fast path

    float64 takes the fast path

    float32 takes the slow path

    float32 takes the slow path

    Updated - 慢速/快速路径采用哪种类型可能取决于操作系统是32位还是64位 . 在我的测试系统上,Ubuntu Lucid 64位, float64 类型比 float32 快10倍 .

  • 7

    在像这样的大循环中使用Python对象操作,无论它们是 floatnp.float32 ,总是很慢 . NumPy对于向量和矩阵的操作非常快,因为所有操作都是由用C语言编写的库的大部分数据执行的,而不是由Python解释器执行的 . 在解释器中运行的代码和/或使用Python对象总是很慢,并且使用非本机类型会使它更慢 . 这是可以预料的 .

    如果您的应用程序运行缓慢且需要对其进行优化,则应尝试将代码转换为直接使用NumPy的矢量解决方案,并且速度快,或者您可以使用Cython等工具在C中创建循环的快速实现 .

  • 21

    也许,这就是为什么你应该直接使用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
    
  • 44

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

    将标量转换为0-d数组然后传递给相应的numpy ufunc然后为NumPy支持的许多不同标量类型中的每一个编写单独的计算方法更容易 .

    意图是标量数学的优化版本将被添加到C中的类型对象 . 这可能仍然会发生,但它从未发生过,因为没有人有足够的动机去做 . 可能是因为解决方法是将numpy标量转换为具有优化算术的Python标量 .

  • -1

    Summary

    如果算术表达式包含 numpy 和内置数字,则Python算法运行速度较慢 . 避免这种转换几乎消除了我报告的所有性能下降 .

    Details

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

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

    类型 floatnumpy.float64 混合在一个表达式中 . 也许Python必须将它们全部转换为一种类型?

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

    如果运行时没有改变(而不是增加),那就表明Python确实在做什么,解释了性能拖累 .

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

    我根本不涉及转换,而且在不匹配的类型上碰巧是超慢的 . 阅读 numpy 源代码可能有所帮助,但这超出了我的技能 .

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

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

    正如预期的那样,运行时间大幅减少:再增加2.3倍 .

    公平地说,我们现在需要通过将文字常量移出循环来稍微改变 float 版本 . 这导致微小(10%)减速 .

    考虑到所有这些更改,代码的 np.float64 版本现在仅比等效的 float 版本慢30%;这种荒谬的5倍性能打击基本消失了 .

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

  • 1

    如果您正在进行快速标量算术,那么您应该查看像gmpy而不是 numpy 这样的库(正如其他人所指出的那样,后者针对向量运算而不是标量运算进行了优化) .

  • 11

    我也可以确认结果 . 我试图看看使用所有numpy类型会是什么样子,并且差异仍然存在 . 那么,我的测试是:

    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
    

    所以在这一点上,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实现 .

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

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

    这个简单的情况显示0.2秒对0.14秒的差异(显然,短路更快) . 我认为你所看到的主要是这些问题加起来 .

    为了避免这种情况,我可以想到几个可能的解决方案,主要回应所说的内容 . Selinap说,第一个解决方案是尽可能地将您的评估保持在NumPy中 . 大量的损失可能是由于接口造成的 . 我会研究如何将你的工作分配到numpy或其他一些用C优化的数字库(gmpy已被提及) . 目标应该是尽可能多地将C推入C中,然后将结果返回 . 你想要从事大工作,而不是做大量的小工作 .

    当然,第二种解决方案是在python中进行更多的中间和小型操作 . 显然,使用本机对象会更快 . 它们将成为所有分支语句的第一个选项,并且始终具有到C代码的最短路径 . 除非您对固定精度计算或默认运算符的其他问题有特定需求,否则我不明白为什么人们不会将直接python函数用于很多事情 .

  • 1

    真奇怪......我在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倍 .

相关问题