小能豆

为什么我的函数比python在IDLE中的打印函数更快?

py

不久前我写了这个函数:

def faster_print(*args, sep=" ", end="\n", file=stdout):
    file.write(sep.join(map(str, args))+end)

并且我测试了一下:

from sys import stdout
from time import perf_counter

def faster_print(*args, sep=" ", end="\n", file=stdout):
    file.write(sep.join(map(str, args))+end)

def time(function, *args, **kwargs):
    start = perf_counter()
    function(*args, **kwargs)
    return perf_counter()-start

def using_normal_print(number):
    for i in range(number):
        print("Hello world.", 5, 5.0, ..., str)

def using_faster_print(number):
    for i in range(number):
        faster_print("Hello world.", 5, 5.0, ..., str)

normal_time = time(using_normal_print, number=100)
faster_time = time(using_faster_print, number=100)

print("Normal print:", normal_time)
print("My print function", faster_time)

事实证明,只有在 IDLE 中速度更快,而在 cmd 中则不然。我知道 IDLE 会为 和 创建自己的对象sys.stdoutsys.stdinsys.stderr我不明白为什么它只会减慢 python 的内置print函数。这个答案说内置print函数是用 c 编写的。这难道不会使它更快吗,因为我的函数需要从 python 字节码编译成机器代码?

我正在使用 Python 3.7.9 和 IDLE 版本 3.7.9


阅读 15

收藏
2024-11-04

共1个答案

小能豆

TheLizard,感谢您报告并修改您的实验。作为 IDLE 维护者,我担心 IDLE 的速度。我注意到打印到屏幕有时比在 Python 终端/控制台 REPL 中慢得多。在后者中,Python 在与屏幕窗口相同的进程中执行,screen.write 直接写入屏幕缓冲区。另一方面,IDLE 在单独的进程中执行用户代码。在该进程中,替换 sys.stdout 通过套接字将输出发送到 IDLE GUI 进程,然后调用 tkinter text.insert,后者调用写入屏幕缓冲区的 tcl/tk 函数。但到目前为止,我还没有进行适当的调查。

我在 Win 10 机器上的 3.10.0a5 中运行了您的代码。在 REPL 中,正常和快速打印耗时 0.05 秒。在 IDLE 中,它们分别耗时约 1.1 秒和 0.3 秒。上面的开销解释了 6 (.3/.05) 的倍数。但约 3.7 (1.1/.3) 的额外倍数呢?

为了测试 kaya3 的第二个假设,我定义s = 'a'*75并替换了您的打印参数s。在 REPL 中,时间仍然是 .05 和 .05。在 IDLE 中,它们大约是 .41 和 .31。我得出的结论是,内部打印功能开销很小,但 3.7 中的大部分是额外的套接字到屏幕开销。当打印正在写入缓冲区时,没有理由预先连接小字符串,因为多个 stdout.writes 本质上是在进行连接,无论是连接到屏幕缓冲区还是磁盘缓冲区。

为了进一步测试这一点,我修改了测试,编写了 3 个 40 行的块。 REPL 时间保持不变。在 IDLE 中,它们平均约为 .058 和 .05,与 REPL 中一样快。

结论:我应该记录一下,如果一个人在 IDLE 中定期运行的代码中进行打印,并且关心速度,那么他应该先将想要显示的所有内容预先组装成一个字符串,然后只打印该字符串。IDLE 这样做是为了回溯,这就是为什么它们会“一次性”显示。

2024-11-04