python read()从标准输出比逐行读取慢得多(slurping?)

python read()从标准输出比逐行读取慢得多(slurping?),python,performance,subprocess,readline,Python,Performance,Subprocess,Readline,我有一个python子进程调用,它运行一个可执行文件并将输出通过管道传输到我的子进程stdout 在stdout数据相对较小(约2k行)的情况下,逐行读取和作为块读取(stdout.read())之间的性能相当……stdout.read()稍快一些 一旦数据变得更大(比如30k+行),逐行读取的性能就会显著提高 这是我的比较脚本: proc=subprocess.Popen(executable,stdout=subprocess.PIPE) tic=time.clock() for line

我有一个python子进程调用,它运行一个可执行文件并将输出通过管道传输到我的子进程stdout

在stdout数据相对较小(约2k行)的情况下,逐行读取和作为块读取(stdout.read())之间的性能相当……stdout.read()稍快一些

一旦数据变得更大(比如30k+行),逐行读取的性能就会显著提高

这是我的比较脚本:

proc=subprocess.Popen(executable,stdout=subprocess.PIPE)
tic=time.clock()
for line in (iter(proc.stdout.readline,b'')):
    tmp.append(line)
print("line by line = %.2f"%(time.clock()-tic))

proc=subprocess.Popen(executable,stdout=subprocess.PIPE)
tic=time.clock()
fullFile=proc.stdout.read()
print("slurped = %.2f"%(time.clock()-tic))
这些是读取约96k行(或50mb磁盘内存)的结果:

我不清楚为什么性能差异如此之大。我的期望是read()版本应该比逐行存储结果更快。当然,我希望在实际情况下,在读取过程中可以进行显著的每行处理,从而获得更快的逐行结果


有人能告诉我read()性能成本吗?

尝试在Popen调用中添加一个bufsize选项,看看它是否有区别:

proc=subprocess.Popen(executable, bufsize=-1, stdout=subprocess.PIPE)
Popen包含一个选项,用于设置读取输入的缓冲区大小。bufsize默认为0,这意味着无缓冲输入。任何其他值表示大约该大小的缓冲区。负值意味着使用系统默认值,这意味着完全缓冲输入

本说明包括:

注意:如果您遇到性能问题,建议您 尝试通过将bufsize设置为-1或大值来启用缓冲 足够的正值(如4096)


这不仅仅是Python,没有缓冲的按字符读取总是比按行或大块读取慢

考虑以下两个简单的C程序:

[readchars.c]

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>

int main(void) {
        FILE* fh = fopen("largefile.txt", "r");
        if (fh == NULL) {
                perror("Failed to open file largefile.txt");
                exit(1);
        }

        int c;
        c = fgetc(fh);
        while (c != EOF) {
                c = fgetc(fh);
        }

        return 0;
}

我根本不懂那种行为

import subprocess
import time


executable = ["cat", "data"]

proc=subprocess.Popen(executable,stdout=subprocess.PIPE)
tic=time.clock()
tmp = []
for line in (iter(proc.stdout.readline,b'')):
    tmp.append(line)
print("line by line = %.2f"%(time.clock()-tic))

proc=subprocess.Popen(executable,stdout=subprocess.PIPE)
tic=time.clock()
fullFile=proc.stdout.read()
print("slurped = %.2f"%(time.clock()-tic))
数据是文本

pts/0$ ll data
-rw-r--r-- 1 javier users 18M feb 21 20:53 data

pts/0$ wc -l data
169866 data
结果:

pts/0$ python3 a.py 
line by line = 0.08
slurped = 0.01
Python 2比Python 3慢得多

pts/0$ python2 a.py 
line by line = 4.45
slurped = 0.01

也许这取决于子进程?

我在bufsize上的结果参差不齐,我运行了一个记录回复的连续ping脚本,我需要它不间断地运行,这将每隔几天挂起一次,我的解决方案是编写一个单独的脚本来监视任务列表,并杀死任何需要10秒以上时间的ping任务。见下文

import subprocess
import time

CREATE_NO_WINDOW = 0x08000000
previous_id = ''

while 0!=1:
    command = subprocess.Popen(['tasklist'], stdout=subprocess.PIPE, 
              shell=False, creationflags = CREATE_NO_WINDOW)
    reply = str(command.communicate()[0]).split('Ko')
    for item in reply:
        if 'PING.EXE' in item:
            print(item.split(' ')[0][4:]+' '+item.split(' ')[22])
        if item.split(' ')[22] != previous_id:
            previous_id = item.split(' ')[22]
            print('New ping detected, system is healthy')
        else:
            print('Same ping active for 10 seconds, killing')
            command = subprocess.Popen(['taskkill','/f','/im','PING.EXE'], stdout=subprocess.PIPE, shell=False, creationflags = CREATE_NO_WINDOW)
            err_log=open('errors.txt','w')
    time.sleep(10)

这是并行运行的,两个进程同时挂起的可能性很小。您所需要做的就是捕获由于主脚本中管道丢失而导致的任何错误。

子流程执行时是否总是花费相同的时间?(例如,缓存对重复运行没有影响等)在重复运行中没有观察到显著的收益。无法使用
seq 30000
()进行复制。我想我们需要一个SSCCE()。我怀疑内存压力。你比较过这两种情况下的内存使用模式吗?@NPE:这是一个windows系统……如果python执行与linux python有什么不同的话。另外,我不确定seq 30000是否会强制我看到的行为,因为我认为这是一个数据量问题,而不是行数问题。我的每一行都有大约400个字符。带缓冲区的结果:逐行=2.41 slurped no buffer=30.90 slurped w/buffer=31.78FWIW这为我的特定用例带来了8倍的加速,谢谢!确实,很抱歉。
bufsize
在Python2和Python3中有不同的默认值。如果你明确地设置它;在这两个版本中,您应该会得到类似的结果。使用
timeit.default\u timer
而不是
time.clock()
以实现便携性。很高兴知道。谢谢
pts/0$ python3 a.py 
line by line = 0.08
slurped = 0.01
pts/0$ python2 a.py 
line by line = 4.45
slurped = 0.01
import subprocess
import time

CREATE_NO_WINDOW = 0x08000000
previous_id = ''

while 0!=1:
    command = subprocess.Popen(['tasklist'], stdout=subprocess.PIPE, 
              shell=False, creationflags = CREATE_NO_WINDOW)
    reply = str(command.communicate()[0]).split('Ko')
    for item in reply:
        if 'PING.EXE' in item:
            print(item.split(' ')[0][4:]+' '+item.split(' ')[22])
        if item.split(' ')[22] != previous_id:
            previous_id = item.split(' ')[22]
            print('New ping detected, system is healthy')
        else:
            print('Same ping active for 10 seconds, killing')
            command = subprocess.Popen(['taskkill','/f','/im','PING.EXE'], stdout=subprocess.PIPE, shell=False, creationflags = CREATE_NO_WINDOW)
            err_log=open('errors.txt','w')
    time.sleep(10)