2010-09-08 15 views
21

estoy corriendo MemCached con el siguiente patrón de comandos bash:Estableciendo un tamaño de búfer más pequeño para sys.stdin?

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

para tratar de localizar a inigualable llega a juegos para la plataforma de teclas de ancho.

El script memtracer está a continuación y funciona como se desee, con un pequeño inconveniente. Al observar el tamaño de archivo de registro intermedio, memtracer.py no comienza a recibir entrada hasta que memkeywatchYMD.log tenga un tamaño de aproximadamente 15-18K. ¿Hay una mejor manera de leer en stdin o quizás una forma de reducir el tamaño del buffer a menos de 1k para tiempos de respuesta más rápidos?

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

Respuesta

26

Puede eliminar completamente el búfer de la entrada estándar/salida estándar utilizando -u bandera de pitón:

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

y la página del manual aclara:

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

Más allá de esto, la alteración del búfer para una no se admite el archivo existente, pero puede crear un nuevo objeto de archivo con el mismo descriptor de archivo subyacente a s uno existente, y posiblemente diferente almacenamiento en búfer, usando os.fdopen. Es decir,

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

debe se unen newin al nombre de un objeto de archivo que se lee igual FD como entrada estándar, pero amortiguada por sólo unos 100 bytes a la vez (y que podría continuar con sys.stdin = newin utilizar el nuevo objeto de archivo como entrada estándar desde allí en adelante). Digo "debería" porque esta área usó para tener una cantidad de errores y problemas en algunas plataformas (es bastante difícil proporcionar multiplataforma con total generalidad) - No estoy seguro de cuál es su estado ahora, pero Definitivamente recomendaría realizar pruebas exhaustivas en todas las plataformas de interés para garantizar que todo vaya bien. (-u, eliminar completamente el almacenamiento en búfer, debería funcionar con menos problemas en todas las plataformas, si eso puede cumplir con sus requisitos).

+0

gracias, la bandera -u para un entorno Linux fue la ganadora. Anteriormente había intentado usar os.fdopen y me encontré con el mismo problema de almacenamiento en el búfer, incluso si configuré el tamaño del búfer en 10. – David

+5

Desafortunadamente, Python 3 todavía obstinadamente abre 'stdin' en el modo de texto en búfer. Solo 'stdout' y' stderr' se ven afectados por el interruptor '-u' ahora. –

+0

¿Alguna solución alternativa para Python3? ¿Quizás una biblioteca/opción impulsada por eventos? –

18

Simplemente puede utilizar en lugar de sys.stdin.readline()sys.stdin.__iter__():

import sys 

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

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

Esto me da búfer de línea-lee usando Python 2.7.4 y Python 3.3.1 en Ubuntu 13.04.

+2

Esto no es realmente relevante para la pregunta, ¿quiso hacer esto como un comentario? – David

+2

Según entendí, la pregunta era "¿Hay una mejor manera de leer en stdin" [para evitar problemas con el búfer de entrada cuando se usa una secuencia de comandos de Python en una tubería], y mi respuesta (tres años tarde) es "Sí". , use 'readline' en lugar de' __iter__' ". Pero tal vez mi respuesta dependa de la plataforma, y ​​todavía tienes problemas con el búfer si pruebas el código anterior. –

+0

Ahkey, lo entiendo. Me refería a MUCHO tamaños de búfer más pequeños (como 80 bytes o menos) para el almacenamiento intermedio stdin. Para 2.7 no puedes efectuar esos tamaños de buffer sin la bandera -U que Alex menciona en su respuesta. – David

7

El sys.stdin.__iter__ dejar de ser línea-tamponada, uno puede tener un iterador que se comporta en su mayoría de forma idéntica (se detiene en EOF, mientras que stdin.__iter__ no) mediante el uso de the 2-argument form of iter para hacer un iterador de sys.stdin.readline:

import sys 

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

O proporcione None como centinela (pero tenga en cuenta que debe manejar la condición EOF por su cuenta).

+1

Parece que hubiera sido mejor como un comentario a la respuesta de Soren. Alex Martelli y Soren han proporcionado respuestas, mientras que esto es más una mejora en la entrada de Soren. – David

+0

Lo que propone aquí aquí es la mejor solución que he visto para este horrible problema; Estoy por barrer todo mi código python y reemplazar "for line in sys.stdin" con él. Veo que en realidad está en la lista en la página de referencia a la que se refiere. Lo que todavía no está claro para mí es ... ¿por qué en la tierra "para la línea en sys.stdin" se comporta de manera diferente de "para la línea en iter (sys.stdin.readline, ''):"? Por lo que puedo ver, son semánticamente idénticos, excepto que el comportamiento de la versión anterior es lo que a mí me parece un error desagradable, un comportamiento que nadie podría querer. Si alguien tiene un contraejemplo, me encantaría verlo. –

+0

@DonHatch al iterar en stdin Estoy de acuerdo en que el comportamiento es extraño y similar a un error, pero cuando el archivo no está leyendo en tiempo real 8k a la vez mejorará el rendimiento. –

2

Esto funcionó para mí en Python 3.4.3:

import os 
import sys 

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

El documentation for fdopen() dice que es sólo un alias para open().

open() tiene un buffering parámetro opcional:

buffering es un número entero opcional utilizado para establecer la política de almacenamiento en búfer. Pase 0 para desactivar el almacenamiento en búfer (solo permitido en modo binario), 1 para seleccionar almacenamiento en línea (solo utilizable en modo texto) y un número entero> 1 para indicar el tamaño en bytes de un almacenamiento en bloque de tamaño fijo.

En otras palabras:

  • totalmente sin búfer stdin requiere modo binario y que pasa a cero a medida que el tamaño del búfer.
  • Line-buffering requiere texto modo.
  • Cualquier otro tamaño de búfer parece funcionar en los modos de binarios y (según la documentación).
0

La única manera de que pudiera hacerlo con Python 2.7 fue:

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

de Python nonblocking console input. Esto deshabilita por completo el almacenamiento en búfer y también suprime el eco.

EDITAR: En cuanto a la respuesta de Alex, la primera proposición (invocando python con -u) no es posible en mi caso (ver shebang limitation).

La segunda proposición (duplicar fd con memoria intermedia más pequeña: os.fdopen(sys.stdin.fileno(), 'r', 100)) no funciona cuando utilizo una memoria intermedia de 0 o 1, ya que es para una entrada interactiva y necesito que cada carácter presionado se procese inmediatamente.

+0

Extraño, la respuesta de Alex funcionó para mí en ese momento. Me pregunto si una actualización backport cambió/rompió algo – David

Cuestiones relacionadas