如何提升Python 3中套接字性能?

3 投票
2 回答
4592 浏览
提问于 2025-04-18 09:54

初始帖子

我有一个运行时间很长的程序,里面大约97%的性能都被ftp.retrlines和ftp.retrbinary调用创建的socket对象占用了。我已经使用了进程和线程来让程序并行运行。还有什么其他方法可以进一步提高速度吗?

示例代码:

# Get file list
ftpfilelist = []
ftp.retrlines('NLST %s' % ftp_directory, ftpfilelist.append)
... filter file list, this part takes almost no time ...
# Download a file
with open(path, 'wb') as fout:
    ftp.retrbinary('RETR %s' % ftp_path, fout.write)

cProfiler的输出:

5890792 function calls (5888775 primitive calls) in 548.883 seconds

Ordered by: internal time
List reduced from 843 to 50 due to restriction <50>

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
  9166  249.154    0.027  249.154    0.027 {method 'recv_into' of '_socket.socket' objects}
 99573  230.489    0.002  230.489    0.002 {method 'recv' of '_socket.socket' objects}
  1767   53.113    0.030   53.129    0.030 {method 'connect' of '_socket.socket' objects}
 98808    2.839    0.000    2.839    0.000 {method 'write' of '_io.BufferedWriter' objects}

后续跟进

使用gevent分支的结果(https://github.com/fantix/gevent),支持python 3.4.1:

7645675 function calls (7153156 primitive calls) in 301.813 seconds

Ordered by: internal time
List reduced from 948 to 50 due to restriction <50>

ncalls       tottime  percall  cumtime  percall filename:lineno(function)
107541/4418  281.228    0.003  296.499    0.067 gevent/hub.py:354(wait)
99885/59883    4.466    0.000  405.922    0.007 gevent/_socket3.py:248(recv)
99097          2.244    0.000    2.244    0.000 {method 'write' of '_io.BufferedWriter' objects}
111125/2796    1.036    0.000    0.017    0.000 gevent/hub.py:345(switch)
107543/2788    1.000    0.000    0.039    0.000 gevent/hub.py:575(get)

使用concurrent.futures.ThreadPool的结果:

5319963 function calls (5318875 primitive calls) in 359.541 seconds

Ordered by: internal time
List reduced from 872 to 50 due to restriction <50>

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    31  349.876   11.286  349.876   11.286 {method 'acquire' of '_thread.lock' objects}
  2652    3.293    0.001    3.293    0.001 {method 'recv' of '_socket.socket' objects}
310270    0.790    0.000    0.790    0.000 {method 'timetuple' of 'datetime.date' objects}
    25    0.661    0.026    0.661    0.026 {method 'recv_into' of '_socket.socket' objects}

结论: 对于我的使用情况,gevent的性能提升大约为20%!

2 个回答

0

看起来cProfile是在计算函数花费的总时间,比如用户空间的时间和在内核中等待的系统时间。这意味着像retrbinaryretrlines这样的函数会包括从网络获取数据所需的时间,而你的ftp服务器提供数据的速度越慢,这些函数花费的时间就会越多。

我建议你对你的性能分析结果做个简单的检查,可以用time(1)来对比,或者使用os.times()。你可能会发现,进程大部分时间都在等待数据(系统时间),所以其实没有太多可以优化的地方。

3

可以看看gevent这个库。它可以对你正在使用的任何库(比如FTP库)进行“猴子补丁”,这样可以通过使用协作线程来提高网络性能。

简单来说,使用线程的程序在处理大量输入输出(I/O)时效率不高,因为调度器不知道某个线程是否在等待网络操作。这就导致当前的线程可能被调度了,但其实在浪费时间等待I/O,而其他线程本来可以在做其他工作。

而使用gevent后,一旦你的线程(叫做greenlet)遇到需要等待的网络调用,它会自动切换到另一个greenlet。通过这种机制,你的线程/greenlets可以被充分利用。

这里有一个很好的关于这个库的介绍: http://www.gevent.org/intro.html#example

撰写回答