Pregunta

Estoy importando varias partes de un volcado de datos en formato de texto a MySQL, el problema es que antes de los datos interesantes hay muchas cosas no interesantes por delante. Escribí este bucle para obtener los datos necesarios:

def readloop(DBFILE):
    txtdb=open(DBFILE, 'r')

sline = ""

# loop till 1st "customernum:" is found
while sline.startswith("customernum:  ") is False: 
    sline = txtdb.readline()

while sline.startswith("customernum:  "):
    data = []
    data.append(sline)
    sline = txtdb.readline()
    while sline.startswith("customernum:  ") is False:
        data.append(sline)
        sline = txtdb.readline()
        if len(sline) == 0:
            break
    customernum = getitem(data, "customernum:  ")
    street = getitem(data, "street:  ")
    country = getitem(data, "country:  ")
    zip = getitem(data, "zip:  ")

El archivo de texto es bastante grande, por lo que el bucle hasta la primera entrada deseada lleva mucho tiempo. ¿Alguien tiene una idea de si esto podría hacerse más rápido (o si todo el camino lo arreglé no es la mejor idea)?

¡Muchas gracias de antemano!

¿Fue útil?

Solución

La idea general para la optimización es proceder "por bloques grandes" (ignorando la estructura de la línea) para localizar la primera línea de interés, luego pasar al procesamiento por línea para el resto). Es algo quisquilloso y propenso a errores (off-by-one y similares), por lo que realmente necesita pruebas, pero la idea general es la siguiente ...:

import itertools

def readloop(DBFILE):
  txtdb=open(DBFILE, 'r')
  tag = "customernum:  "
  BIGBLOCK = 1024 * 1024
  # locate first occurrence of tag at line-start
  # (assumes the VERY FIRST line doesn't start that way,
  # else you need a special-case and slight refactoring)
  blob = ''
  while True:
    blob = blob + txtdb.read(BIGBLOCK)
    if not blob:
      # tag not present at all -- warn about that, then
      return
    where = blob.find('\n' + tag)
    if where != -1:  # found it!
      blob = blob[where+1:] + txtdb.readline()
      break
    blob = blob[-len(tag):]
  # now make a by-line iterator over the part of interest
  thelines = itertools.chain(blob.splitlines(1), txtdb)
  sline = next(thelines, '')
  while sline.startswith(tag):
    data = []
    data.append(sline)
    sline = next(thelines, '')
    while not sline.startswith(tag):
      data.append(sline)
      sline = next(thelines, '')
      if not sline:
        break
    customernum = getitem(data, "customernum:  ")
    street = getitem(data, "street:  ")
    country = getitem(data, "country:  ")
    zip = getitem(data, "zip:  ")

Aquí, he tratado de mantener intacta la mayor parte de su estructura, haciendo solo mejoras menores más allá de la "gran idea". de esta refactorización.

Otros consejos

Por favor no escriba este código:

while condition is False:

Las condiciones booleanas son booleanas para llorar en voz alta, por lo que se pueden probar (o negar y probar) directamente:

while not condition:

Su segundo ciclo while no se escribe como "while while condition is True:", tengo curiosidad de saber por qué sintió la necesidad de probar que "False". en el primero.

Al sacar el módulo dis, pensé en analizarlo un poco más. En mi experiencia de pyparsing, las llamadas a funciones son asesinos de rendimiento total, por lo que sería bueno evitar las llamadas a funciones si es posible. Aquí está su prueba original:

>>> test = lambda t : t.startswith('customernum') is False
>>> dis.dis(test)
  1           0 LOAD_FAST                0 (t)
              3 LOAD_ATTR                0 (startswith)
              6 LOAD_CONST               0 ('customernum')
              9 CALL_FUNCTION            1
             12 LOAD_GLOBAL              1 (False)
             15 COMPARE_OP               8 (is)
             18 RETURN_VALUE

Aquí suceden dos cosas costosas, CALL_FUNCTION y LOAD_GLOBAL . Puede reducir LOAD_GLOBAL definiendo un nombre local para False:

>>> test = lambda t,False=False : t.startswith('customernum') is False
>>> dis.dis(test)
  1           0 LOAD_FAST                0 (t)
              3 LOAD_ATTR                0 (startswith)
              6 LOAD_CONST               0 ('customernum')
              9 CALL_FUNCTION            1
             12 LOAD_FAST                1 (False)
             15 COMPARE_OP               8 (is)
             18 RETURN_VALUE

¿Pero qué pasa si simplemente dejamos caer la prueba 'es' completamente ?:

>>> test = lambda t : not t.startswith('customernum')
>>> dis.dis(test)
  1           0 LOAD_FAST                0 (t)
              3 LOAD_ATTR                0 (startswith)
              6 LOAD_CONST               0 ('customernum')
              9 CALL_FUNCTION            1
             12 UNARY_NOT
             13 RETURN_VALUE

Hemos colapsado un LOAD_xxx y COMPARE_OP con un simple UNARY_NOT . " es falso " ciertamente no está ayudando al rendimiento a causar ninguna.

Ahora, ¿qué pasa si podemos hacer una eliminación general de una línea sin hacer ninguna llamada a la función? Si el primer carácter de la línea no es una 'c', no hay forma de que comience con ('customernum'). Probemos eso:

>>> test = lambda t : t[0] != 'c' and not t.startswith('customernum')
>>> dis.dis(test)
  1           0 LOAD_FAST                0 (t)
              3 LOAD_CONST               0 (0)
              6 BINARY_SUBSCR
              7 LOAD_CONST               1 ('c')
             10 COMPARE_OP               3 (!=)
             13 JUMP_IF_FALSE           14 (to 30)
             16 POP_TOP
             17 LOAD_FAST                0 (t)
             20 LOAD_ATTR                0 (startswith)
             23 LOAD_CONST               2 ('customernum')
             26 CALL_FUNCTION            1
             29 UNARY_NOT
        >>   30 RETURN_VALUE

(Tenga en cuenta que el uso de [0] para obtener el primer carácter de una cadena no crea un segmento; de hecho, esto es muy rápido)

Ahora, suponiendo que no haya una gran cantidad de líneas que comiencen con 'c', el filtro de corte aproximado puede eliminar una línea usando todas las instrucciones bastante rápidas. De hecho, al probar " t [0]! = 'C' " en lugar de " no t [0] == 'c' " nos guardamos una instrucción UNARY_NOT extraña.

Entonces, usando este aprendizaje sobre la optimización de atajos y sugiero cambiar este código:

while sline.startswith("customernum:  ") is False:
    sline = txtdb.readline()

while sline.startswith("customernum:  "):
    ... do the rest of the customer data stuff...

A esto:

for sline in txtdb:
    if sline[0] == 'c' and \ 
       sline.startswith("customernum:  "):
        ... do the rest of the customer data stuff...

Tenga en cuenta que también he eliminado la llamada a la función .readline (), y solo itero sobre el archivo usando " for sline en txtdb " ;.

Me doy cuenta de que Alex ha proporcionado un cuerpo de código completamente diferente para encontrar esa primera línea 'customernum', pero trataría de optimizar dentro de los límites generales de su algoritmo, antes de sacar grandes pero oscuras pistolas de lectura en bloque.

Supongo que está escribiendo este script de importación y se vuelve aburrido esperar durante la prueba, por lo que los datos permanecen igual todo el tiempo.

Puede ejecutar el script una vez para detectar las posiciones reales en el archivo al que desea saltar, con print txtdb.tell () . Anótelos y reemplace el código de búsqueda con txtdb.seek (pos) . Básicamente eso es construir un índice para el archivo ;-)

Otra forma más convencional sería leer datos en fragmentos más grandes, algunos MB a la vez, no solo los pocos bytes en una línea.

Cuéntanos más sobre el archivo.

¿Puedes usar file.seek para hacer una búsqueda binaria? Busque hasta la mitad, lea algunas líneas, determine si está antes o después de la parte que necesita, vuelva a aparecer. Eso convertirá su búsqueda de O (n) en O (logn).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top