当前位置:  开发笔记 > 编程语言 > 正文

为什么Python对于简单的for循环来说太慢了?

如何解决《为什么Python对于简单的for循环来说太慢了?》经验,为你挑选了3个好方法。

我们正在使用Python 制作一些kNNSVD实现.其他人选择了Java.我们的执行时间非常不同.我使用cProfile来查看我在哪里犯错但实际上一切都很好.是的,我numpy也用.但我想问一个简单的问题.

total = 0.0
for i in range(9999): # xrange is slower according 
    for j in range(1, 9999):            #to my test but more memory-friendly.
        total += (i / j)
print total

这段代码在我的电脑上占用了31.40秒.

此代码的Java版本在同一台计算机上占用1秒或更短时间.我想,类型检查是这段代码的主要问题.但我应该为我的项目做很多这样的操作,我认为9999*9999不是那么大的数字.

我想我犯了错​​误,因为我知道Python被很多科学项目所使用.但是为什么这段代码这么慢,我怎么能处理比这更大的问题呢?

我应该使用JIT编译器Psyco吗?

编辑

我还说这个循环问题只是一个例子.代码并不像这样简单,可能很难将改进/代码示例付诸实践.

另一个问题是,我可以实现大量的数据挖掘和机器学习算法与numpyscipy,如果我正确地使用它?



1> 小智..:

我想我犯了错​​误,因为我知道Python被很多科学项目所使用.

他们大量使用SciPy(NumPy是最突出的组件,但我听说围绕NumPy的API开发的生态系统更为重要),这大大加快了这些项目所需的各种操作.你做错了什么:你不是用C 编写你的关键代码.一般来说,Python非常适合开发,但是扩展良好的扩展模块本身就是一个至关重要的优化(至少当你处理数字时) .Python是一种非常糟糕的语言,用于实现紧密的内部循环.

默认(当时最受欢迎和广泛支持的)实现是一个简单的字节码解释器.即使是最简单的操作,如整数除法,也可能需要数百个CPU周期,多个内存访问(类型检查是一个流行的例子),几个C函数调用等,而不是几个(甚至单个,在整数的情况下)分裂)指令.此外,该语言设计有许多抽象,增加了开销.如果你使用xrange,你的循环会在堆上分配9999个对象 - 如果你使用range(9999*9999整数减去256*256左右,对于缓存的小整数)则更多.此外,该xrange版本在每次迭代时调用一个方法来推进 - range如果序列上的迭代没有被特别优化,那么版本也是如此.它仍然需要一个完整的字节码调度,这本身就非常复杂(当然,与整数除法相比).

看看什么是JIT会很有趣(我推荐PyPy而不是Psyco,后者不再是主动开发的,而且范围非常有限 - 尽管如此,它可能适用于这个简单的例子).经过一小部分迭代之后,它应该产生一个最优的机器代码循环,增加一些防护 - 简单的整数比较,如果它们失败则跳跃 - 以保持正确性,以防你在该列表中得到一个字符串.Java可以做到同样的事情,只是更早(它不必先跟踪)和更少的防护(至少如果你使用ints).这就是它快得多的原因.



2> Joe Kington..:

因为你提到科学代码,看看numpy.您正在做的事情可能已经完成(或者更确切地说,它使用LAPACK来处理像SVD这样的事情).当你听说python被用于科学代码时,人们可能并不是指你在你的例子中使用它.

作为一个简单的例子:

(如果你使用python3,你的例子会使用浮点除法.我的例子假设你使用python2.x,因此整数除法.如果不是,请指定i = np.arange(9999, dtype=np.float)等)

import numpy as np
i = np.arange(9999)
j = np.arange(1, 9999)
print np.divide.outer(i,j).sum()

给出时间的一些想法...(我将在这里使用浮点除法,而不是像你的例子中的整数除法):

import numpy as np

def f1(num):
    total = 0.0
    for i in range(num): 
        for j in range(1, num):
            total += (float(i) / j)
    return total

def f2(num):
    i = np.arange(num, dtype=np.float)
    j = np.arange(1, num, dtype=np.float)
    return np.divide.outer(i, j).sum()

def f3(num):
    """Less memory-hungry (and faster) version of f2."""
    total = 0.0
    j = np.arange(1, num, dtype=np.float)
    for i in xrange(num):
        total += (i / j).sum()
    return total

如果我们比较时间:

In [30]: %timeit f1(9999)
1 loops, best of 3: 27.2 s per loop

In [31]: %timeit f2(9999)
1 loops, best of 3: 1.46 s per loop

In [32]: %timeit f3(9999)
1 loops, best of 3: 915 ms per loop


@naxa - 抱歉,我之前没有注意到你的评论!"f2"对于大数字来说较慢的原因是它构建了一个"num"x"num"2D数组,这会占用大量内存.`f3`只构建一个`num`-length 1D数组,并重复制作它的副本以进行除法.大数组的内存分配实际上是这里的瓶颈,因此`f3`更快,因为在内存中为小数组找到连续的空间更容易.
@JimRaynor - 可能还有一本书,但我不知道.Scipy-lectures是整个生态系统的绝佳概述:http://scipy-lectures.github.io/但是,它并不是你想到的.另一个不错的选择是关注[Jamie's](http://stackoverflow.com/search?q=user:110026%20%5Bnumpy%5D),[unutbu's](http://stackoverflow.com/search?q=user %3A190597 + [numpy]),[Sven's](http://stackoverflow.com/search?q=user:279627+ [numpy])或[DSM](http://stackoverflow.com/search?q=user :487339+ [numpy])(等)numpy答案.

3> Harald Schil..:

Python的好处是,与Java(你只有这种反射机制)相比,它有更多的灵活性(例如,类是对象)

这里没有提到的是Cython.它允许引入类型变量并将您的示例转换为C/C++.然后它快得多.我也改变了循环中的界限......

from __future__ import division

cdef double total = 0.00
cdef int i, j
for i in range(9999):
    for j in range(1, 10000+i):
        total += (i / j)

from time import time
t = time()
print("total = %d" % total)
print("time = %f[s]" % (time() - t))

其次是

$ cython loops.pyx
$ gcc -I/usr/include/python2.7 -shared -pthread -fPIC -fwrapv -Wall -fno-strict-aliasing -O3 -o loops.so loops.c
$ python -c "import loops"

total = 514219068
time = 0.000047[s]


看起来您在打印`time()-t`之前就已经定义了`t = time()`。您需要在循环之前启动计时器。还是我对文件的编译方式有误解?
推荐阅读
mobiledu2402851377
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有