关于python:为sys.stdin设置较小的缓冲区大小?

Setting smaller buffer size for sys.stdin?

我使用以下bash命令模式运行memcached:

1
memcached -vv 2>&1 | tee memkeywatch2010098.log 2>&1 | ~/bin/memtracer.py | tee memkeywatchCounts20100908.log

要尝试并跟踪不匹配的,请访问平台范围内的密钥集。

memtracer脚本在下面,可以根据需要工作,只有一个小问题。观察中间日志文件的大小,memtracer.py在memkeywatchymd.log之前不会开始获取输入。尺寸约为15-18K。在stdin中是否有更好的读取方法,或者可能有一种将缓冲区大小减少到1K以下以加快响应时间的方法?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/usr/bin/python

import sys
from collections import defaultdict

if __name__ =="__main__":


    keys = defaultdict(int)
    GET = 1
    SET = 2
    CLIENT = 1
    SERVER = 2

    #if <
    for line in sys.stdin:
        key = None
        components = line.strip().split("")
        #newConn = components[0][1:3]
        direction = CLIENT if components[0].startswith("<") else SERVER

        #if lastConn != newConn:        
        #    lastConn = newConn

        if direction == CLIENT:            
            command = SET if components[1] =="set" else GET
            key = components[2]
            if command == SET:                
                keys[key] -= 1                                                                                    
        elif direction == SERVER:
            command = components[1]
            if command =="sending":
                key = components[3]
                keys[key] += 1

        if key != None:
            print"%s:%s" % ( key, keys[key], )

您可以使用python的-u标志完全删除stdin/stdout中的缓冲:

1
2
-u     : unbuffered binary stdout and stderr (also PYTHONUNBUFFERED=x)
         see man page for details on internal buffering relating to '-u'

手册页澄清了:

1
2
3
4
5
6
7
   -u     Force stdin, stdout and stderr to  be  totally  unbuffered.   On
          systems  where  it matters, also put stdin, stdout and stderr in
          binary mode.  Note that there is internal  buffering  in  xread-
          lines(),  readlines()  and  file-object  iterators ("for line in
          sys.stdin"
) which is not influenced by  this  option.   To  work
          around  this, you will want to use"sys.stdin.readline()" inside
          a"while 1:" loop.

除此之外,不支持更改现有文件的缓冲区,但可以使用os.fdpopen使用与现有文件描述符相同的底层文件描述符创建新的文件对象,也可以使用不同的缓冲区。即。,

1
2
3
import os
import sys
newin = os.fdopen(sys.stdin.fileno(), 'r', 100)

应将newin绑定到一个文件对象的名称,该文件对象读取与标准输入相同的fd,但一次只缓冲大约100个字节(从那时起,您可以继续使用sys.stdin = newin将新的文件对象用作标准输入)。我之所以说"应该",是因为这个领域曾经在某些平台上存在许多错误和问题(提供具有完全通用性的跨平台功能非常困难)——我不确定它现在的状态,但我绝对建议在所有感兴趣的平台上进行彻底的测试,以确保一切顺利进行。(如果可以满足您的要求,那么完全删除缓冲区的-u应该可以减少所有平台上的问题)。


您只需使用sys.stdin.readline()而不是sys.stdin.__iter__()

1
2
3
4
5
6
7
import sys

while True:
    line = sys.stdin.readline()
    if not line: break # EOF

    sys.stdout.write('> ' + line.upper())

这使我可以使用Ubuntu13.04上的python 2.7.4和python 3.3.1进行行缓冲读取。


sys.stdin.__iter__仍然是行缓冲的,通过使用iter的2参数形式使sys.stdin.readline的迭代器,可以有一个行为基本相同的迭代器(在eof停止,而stdin.__iter__不会):

1
2
3
4
import sys

for line in iter(sys.stdin.readline, ''):
    sys.stdout.write('> ' + line.upper())

或者提供None作为哨兵(但请注意,然后您需要自己处理EOF条件)。


这在Python3.4.3中对我很有用:

1
2
3
4
import os
import sys

unbuffered_stdin = os.fdopen(sys.stdin.fileno(), 'rb', buffering=0)

fdopen()的文档说它只是open()的别名。

open()有一个可选的buffering参数:

buffering is an optional integer used to set the buffering policy. Pass 0 to switch buffering off (only allowed in binary mode), 1 to select line buffering (only usable in text mode), and an integer > 1 to indicate the size in bytes of a fixed-size chunk buffer.

换言之:

  • 完全未缓冲的stdin需要二进制模式,并将零作为缓冲区大小传递。
  • 行缓冲需要文本模式。
  • 任何其他缓冲区大小似乎都可以在二进制和文本模式下工作(根据文档)。

我用python 2.7做这件事的唯一方法是:

1
tty.setcbreak(sys.stdin.fileno())

从python非阻塞控制台输入。这将完全禁用缓冲并抑制回声。

编辑:关于Alex的答案,第一个建议(使用-u调用python)在我的情况下是不可能的(参见shebang限制)。

第二个命题(用较小的缓冲区复制fd:os.fdopen(sys.stdin.fileno(), 'r', 100))在我使用0或1的缓冲区时不起作用,因为它用于交互式输入,我需要立即处理每个字符。