与 CPython 相比,PyPy 大内存使用量

Lan*_*AOH 7 python io pypy cpython

我用python解决了SPOJ的大输入测试问题,遇到了一个很奇怪的现象。我 使用 PyPy 和 Python 2提交了相同的代码。结果如下所示:

spoj 大输入测试

正如预期的那样,与 CPython 相比,使用 PyPy 的代码运行速度要快得多。但与此同时,内存使用量增加了惊人的 7 倍!我在网上进行了搜索,但找不到任何证据表明 PyPy 的内存使用量远远超过 CPython。有人可以解释一下内存使用量的巨大差异吗?

我也考虑过这可能是因为我的代码。因此,我在下面发布了我的代码:

import io, sys, atexit, os
sys.stdout = io.BytesIO()
atexit.register(lambda: sys.__stdout__.write(sys.stdout.getvalue()))
sys.stdin = io.BytesIO(sys.stdin.read())
raw_input = lambda: sys.stdin.readline().rstrip()

line = list(map(int,raw_input().split()))
num, k = line
ans = 0

for i in xrange(0,num):
    if int(raw_input())%k == 0:
        ans += 1;

print(ans) 
Run Code Online (Sandbox Code Playgroud)

有人可以建议我吗?

ead*_*ead 7

首先,我无法重现结果。不知道 SPOJ 使用哪些版本/设置。对于以下实验,使用了 PyPy 5.8.0 和 CPython 2.7.12。

作为测试用例,使用了大约大小的最大可能输入文件110MB

#create_data.py
print 10**6, 33
for i in xrange(10**6):
  print 10**9

>> python create_data.py > input.in
Run Code Online (Sandbox Code Playgroud)

现在运行/usr/bin/time -v XXX solution.py < input.py产量:

Interpreter     MaximalResidentSize 
PyPy:                 278 Mb
CPython:              222 Mb
Run Code Online (Sandbox Code Playgroud)

PyPy 需要多一点内存。CPython 和 PyPy 使用不同的垃圾收集器策略,我认为 PyPy 的权衡是更快但使用更多内存。PyPy 的人有一篇很棒的文章,介绍了他们的垃圾收集器及其与 CPython 的比较。


其次,我不相信来自 SPJO 站点的数字。system.stdin.read()将整个文件读入内存。python 文档甚至

要读取文件的内容,请调用 f.read(size),它读取一定数量的数据并将其作为字符串返回。size 是一个可选的数字参数。当 size 省略或为负时,将读取并返回文件的全部内容;如果文件是您机器内存的两倍大,那是您的问题

假设最坏的情况包含在他们的测试用例中,内存使用量应该至少是您使用的文件大小 (110 MB) std.stdin.read(),甚至是文件大小的两倍,因为您正在处理数据。


实际上,我不确定,整个麻烦是否值得 - 使用raw_input()可能足够快 - 我只是相信 python 会做正确的事情。CPython 通常缓冲stdoutstdin(如果它们被重定向到文件,则完全缓冲,或者为控制台行缓冲),您必须使用命令行选项-u其关闭

但是,如果您真的想确定,您可以使用 的文件对象迭代器sys.stdin,因为正如 CPython 手册页所述:

-u 强制标准输入、标准输出和标准错误完全无缓冲。在重要的系统上,还将 stdin、stdout 和 stderr 置于二进制模式。请注意,xread中有内部缓冲吗? 不受此选项影响的lines()、readlines() 和文件对象迭代器(“for line in sys.stdin”)。要解决此问题,您需要在“while 1:”循环中使用“sys.stdin.readline()”。

这意味着您的程序可能如下所示:

import sys
num, k = map(int,raw_input().split())
ans = 0    
for line in sys.stdin:
    if int(line)%k == 0:
        ans += 1
print(ans)
Run Code Online (Sandbox Code Playgroud)

这有一个很大的优势,即此变体仅使用大约 7MB 内存。

另一个教训是,sys.stdin.readline()如果您害怕有人在无缓冲模式下运行您的程序,则不应使用。


一些进一步的实验(我的 cpu 被调低了)

                   CPython        CPython -u         PyPy         PyPy -u
original        28sec/221MB      25sec/221MB       3sec/278MB    3sec/278MB
raw_input()     29sec/7MB        110sec/7MB        7sec/75MB    100sec/63MB
readline()     38sec/7MB        130sec/7MB        5sec/75MB    100sec/63MB
readlines()    20sec/560MB      20sec/560MB       4sec/1.4GB    4sec/1.4G
file-iterator    17sec/7MB       17sec/7MB         4sec/68MB    100sec/62MB 
Run Code Online (Sandbox Code Playgroud)

有一些要点:

  1. raw_input()并且sys.stdin.read_line()有相同的表现
  2. raw_input()是缓冲的,但是这个缓冲区似乎与文件对象迭代器的缓冲区有点不同raw_input(),至少对于这个文件来说,它的表现要好一些。
  3. 内存开销sys.stdin.readlines()似乎相当高,至少只要行很短。
  4. 文件对象迭代器在 CPython 和 PyPy 中具有不同的行为,如果使用选项-u:对于 PyPy-u也会关闭文件对象迭代器的缓冲(可能是错误?)。