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

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

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

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

Скрипт memtracer приведен ниже и работает по желанию, с одной незначительной проблемой. Наблюдая за размером промежуточного файла журнала, memtracer.py не начинает получать ввод, пока размер файла memkeywatchYMD.log не достигнет 15-18K. Есть ли лучший способ чтения в stdin или, возможно, способ сократить размер буфера до 1 КБ для более быстрого времени отклика?

#!/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], )

5 ответов

Решение

Вы можете полностью удалить буферизацию из stdin/stdout, используя 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.

Помимо этого, изменение буферизации для существующего файла не поддерживается, но вы можете создать новый файловый объект с тем же базовым дескриптором файла, что и существующий, и, возможно, с другой буферизацией, используя os.fdopen. То есть,

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__():

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.

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

import sys

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

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

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

import os
import sys

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

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

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

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

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

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

Возможно, ваши проблемы связаны не с 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 вариант при использовании в середине трубопровода.

Единственный способ, которым я мог сделать это с Python 2.7, был:

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

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

РЕДАКТИРОВАТЬ: Относительно ответа Алекса, первое предложение (вызов Python с -u) в моем случае это невозможно (см. ограничение Шебанга).

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

Другие вопросы по тегам