【问题标题】:Setting smaller buffer size for sys.stdin?为 sys.stdin 设置较小的缓冲区大小?
【发布时间】:2017-05-29 23:40:31
【问题描述】:

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

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

尝试在整个平台范围内追踪不匹配的 get 设置。

memtracer 脚本如下,可以按需要运行,但有一个小问题。观察中间日志文件大小,memtracer.py 直到 memkeywatchYMD.log 才开始获取输入 大小约为15-18K。有没有更好的读取标准输入的方法,或者将缓冲区大小减少到 1k 以下以加快响应时间?

#!/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 stdin buffering


    【解决方案1】:

    您可以使用 python 的 -u 标志从标准输入/标准输出中完全删除缓冲:

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

    手册页说明:

       -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.
    

    除此之外,不支持更改现有文件的缓冲,但您可以使用与现有文件描述符相同的基础文件描述符创建一个新文件对象,并且可能使用不同的缓冲,使用 @ 987654321@。即,

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

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

    【讨论】:

    • 谢谢,Linux 环境的 -u 标志是赢家。我之前尝试过使用 os.fdopen 并遇到了同样的缓冲问题,即使我将缓冲区大小设置为 10。
    • 不幸的是,Python 3 仍然顽固地以缓冲文本模式打开 stdin。现在只有stdoutstderr 受到-u 开关的影响。
    • Python3 的任何变通方法?也许是事件驱动的库/选项?
    • 这在 Python 3.4.3 中对我有用:os.fdopen(sys.stdin.fileno(), 'rb', buffering=0)
    • @DenilsonSáMaia:无需自己重新打开它。 sys.stdin真的是三层;一个io.TextIOWrapper(将bytes解码为str)包装一个io.BufferedReader(用于缓冲bytes)包装一个io.FileIO(提交系统调用的实际事物)。它们都可以作为属性使用; sys.stdin.buffer 得到 BufferedReader 没有文本解码,sys.stding.buffer.raw 得到 FileIO 没有缓冲。
    【解决方案2】:

    您可以简单地使用sys.stdin.readline() 而不是sys.stdin.__iter__()

    import sys
    
    while True:
        line = sys.stdin.readline()
        if not line: break # EOF
    
        sys.stdout.write('> ' + line.upper())
    

    这让我在 Ubuntu 13.04 上使用 Python 2.7.4 和 Python 3.3.1 进行行缓冲读取。

    【讨论】:

    • 这与问题无关,您是否打算将其作为评论。
    • 据我了解,问题是“是否有更好的读取标准输入的方法”[以避免在管道中使用 Python 脚本时出现输入缓冲区问题],以及我的回答(迟到三年它可能是)是“是的,使用readline 而不是__iter__”。但也许我的答案是平台相关的,如果你尝试上面的代码,你仍然会遇到缓冲区问题?
    • 啊,我明白了。我的意思是用于标准输入缓冲的缓冲区大小要小得多(例如 80 字节或更少)。对于 2.7,如果没有 Alex 在回答中提到的 -U 标志,您将无法影响这些缓冲区大小。
    • 有趣的 Alex 没有注意到这一点,github.com/certik/python-2.7/blob/… 你说得对,readline 可能更快,因为它增量使用 getc 而 file_internext 缓冲 8192,如源代码中定义的那样。
    • 这很重要——我看到程序由于缓冲标准输入而没有足够的交互性(而不是立即做出反应)。我以前不知道这一点。
    【解决方案3】:

    sys.stdin.__iter__ 仍然是行缓冲的,通过使用the 2-argument form of iter 生成sys.stdin.readline 的迭代器,可以拥有一个行为基本相同的迭代器(停止在EOF,而stdin.__iter__ 不会):

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

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

    【讨论】:

    • 这似乎作为对 soren 答案的评论会更好。 Alex Martelli 和 Soren 提供了答案,而这更像是对 Soren 的意见的改进。
    • 您在这里提出的建议是我见过的解决这个可怕问题的最佳解决方案;我即将浏览我所有的python代码并用它替换“for line in sys.stdin”。我看到它实际上列在您提到的参考页面中。我仍然不清楚的是......为什么“for line in sys.stdin”的行为与“for line in iter(sys.stdin.readline, ''):”不同?据我所知,它们在语义上是相同的,只是前一个版本的行为在我看来就像一个讨厌的错误,没有人想要的行为。如果有人有反例,我很乐意看到。
    • @DonHatch 在 stdin 上迭代时我同意这种行为很奇怪且类似错误,但是当文件不是 stdin 时,一次读取 8k 会提高性能。
    • @SamJacobson 为什么有问题的输入流是否是标准输入很重要? (也许您的意思是指出终端、文件和管道之间的一些差异?但这些差异与它是否是标准输入无关。)当您说一次读取 8k 会提高性能时 - 与什么相比提高性能?我认为我没有提出或提倡任何在输入可用 8k 时一次读取少于 8k 的行为。
    • @SamJacobson 顺便说一句,我刚才提交了bugs.python.org/issue26290:“fileinput 和'for line in sys.stdin'对输入缓冲做了奇怪的嘲弄”。
    【解决方案4】:

    这在 Python 3.4.3 中对我有用:

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

    documentation for fdopen() 表示它只是open() 的别名。

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

    buffering 是一个可选的整数,用于设置缓冲策略。传递 0 以关闭缓冲(仅在二进制模式下允许),1 以选择行缓冲(仅在文本模式下可用),并且整数 > 1 以指示固定大小的块缓冲区的大小(以字节为单位)。

    换句话说:

    • 完全无缓冲标准输入需要二进制模式并传递零作为缓冲区大小。
    • 行缓冲需要文本模式。
    • 任何其他缓冲区大小似乎都适用于 binarytext 模式(根据文档)。

    【讨论】:

      【解决方案5】:

      您的问题可能不在于 Python,而在于 Linux shell 在使用管道链接命令时注入的缓冲。当这是问题时,输入不是按行缓冲,而是按 4K 块缓冲。

      要停止此缓冲,请在命令链前面加上 expect 包中的 unbuffer 命令,例如:

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

      unbuffer 命令在管道中间使用时需要-p 选项。

      【讨论】:

        【解决方案6】:

        我可以用 python 2.7 做到这一点的唯一方法是:

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

        来自Python nonblocking console input。这将完全禁用缓冲并抑制回声。

        编辑:关于亚历克斯的回答,第一个命题(用-u 调用python)在我的情况下是不可能的(见shebang limitation)。

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

        【讨论】:

        • 很奇怪,当时亚历克斯的回答对我有用。想知道后端更新是否改变/破坏了某些东西
        • tty.setcbreak 不是关于 python 缓冲,而是内核 tty 层缓冲输入。因此这不适用于管道。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-07-10
        • 1970-01-01
        • 1970-01-01
        • 2012-08-10
        • 1970-01-01
        相关资源
        最近更新 更多