Установка меньшего размера буфера для sys.stdin?

Я запускаю memcached со следующим шаблоном команды bash:

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

чтобы попытаться отследить непревзойденный доступ к наборам для широкой платформы ключей.

Memtracer script ниже и работает по желанию с одной незначительной проблемой. Наблюдая за размером промежуточного файла журнала, memtracer.py не начинает получать ввод, пока memkeywatchYMD.log составляет около 15-18K. Есть ли лучший способ прочитать в stdin или, возможно, способ сократить размер буфера до менее 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], )

Ответы

Ответ 1

Вы можете полностью удалить буферизацию из stdin/stdout с помощью флага python -u:

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

и поясняет man-страницу:

   -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.fdopen. То есть.

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

должен привязать newin к имени файлового объекта, который считывает тот же FD, что и стандартный ввод, но буферизуется только по 100 байт за раз (и вы можете продолжить с sys.stdin = newin для использования нового файлового объекта как стандартный ввод оттуда). Я говорю "должен", потому что в этой области было несколько ошибок и проблем на некоторых платформах (это довольно сложная функциональность, обеспечивающая кросс-платформу с полной общностью). Я не уверен, каково ее состояние сейчас, d обязательно рекомендую провести тщательное тестирование на всех платформах, чтобы гарантировать, что все идет гладко. (-u, полностью удаляя буферизацию, должно работать с меньшим количеством проблем на всех платформах, если это может соответствовать вашим требованиям).

Ответ 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())

Это дает мне чтение в виде строки с использованием Python 2.7.4 и Python 3.3.1 на Ubuntu 13.04.

Ответ 3

sys.stdin.__iter__, все еще буферизируемый по строке, может иметь итератор, который ведет себя в основном идентично (останавливается при EOF, тогда как stdin.__iter__ не будет), используя 2- аргумент iter, чтобы сделать итератор sys.stdin.readline:

import sys

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

Или укажите None как часовую (но учтите, что тогда вам нужно самостоятельно обрабатывать условие EOF).

Ответ 4

Это работало для меня в Python 3.4.3:

import os
import sys

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

Документация для fdopen() говорит, что это просто псевдоним для open().

open() имеет необязательный параметр buffering:

Буферизация

- необязательное целое число, используемое для установки политики буферизации. Передайте 0, чтобы отключить буферизацию (разрешено только в двоичном режиме), 1 выбрать буферизацию строки (только для использования в текстовом режиме) и целое число > 1, чтобы указать размер в байтах буфера блоков фиксированного размера.

Другими словами:

  • Полностью небуферизованный stdin требует двоичного режима и пропускает нуль в качестве размера буфера.
  • Линейная буферизация требует текста.
  • Любой другой размер буфера, похоже, работает как в двоичном, так и в текстовом режимах (согласно документации).

Ответ 5

Единственный способ сделать это с помощью python 2.7:

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

from Неблокирующий консольный вход Python. Это полностью отключает буферизацию, а также подавляет эхо.

EDIT: Что касается ответа Alex, первое предложение (вызов python с -u) в моем случае невозможно (см. ограничение shebang).

Второе предложение (дублирование fd с меньшим буфером: os.fdopen(sys.stdin.fileno(), 'r', 100)) не работает, когда я использую буфер 0 или 1, как и для интерактивного ввода, и мне нужно, чтобы каждый символ был нажат для немедленной обработки.

Ответ 6

Возможно, ваши проблемы связаны не с Python, а с буферизацией, которую вводит оболочка Linux при объединении команд в каналы. В этом случае входные данные буферизуются не строкой, а блоком 4K.

Чтобы остановить эту буферизацию, перед цепочкой команд введите команду unbuffer из пакета expect, например:

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

Команде unbuffer требуется опция -p при использовании в середине конвейера.