Pergunta

eu estou importando várias partes de um Databasedump em formato de texto em MySQL, o problema é que antes do dado interessante há muito material em frente não interessante. Eu escrevi este loop para obter os dados necessários:

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:  ")

O Textfile é muito grande, por isso só looping até a entrada primeiro queria leva muito tempo. Qualquer um tem uma idéia se isso poderia ser feito mais rapidamente (ou se toda a maneira que eu fixo isso não é a melhor idéia)?

Muito obrigado antecipadamente!

Foi útil?

Solução

A idéia geral para a otimização é para continuar "por grandes blocos" (principalmente-ignorando estrutura de linha) para localizar a primeira linha de interesse, em seguida, passar para o processamento por linha para o resto). É um pouco mimado e propenso a erros (off-by-one e similares) para que ele realmente precisa de testes, mas a idéia geral é o seguinte ...:

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:  ")

Aqui, eu tentei manter o máximo de sua estrutura intacta quanto possível, fazendo melhorias apenas pequenas além da "grande ideia" desta refatoração.

Outras dicas

Por favor, não escrever este código:

while condition is False:

condições booleanas são boolean para chorar em voz alta, para que eles possam ser testados (ou negado e testado) diretamente:

while not condition:

O segundo loop while não está escrito como "enquanto condição for verdadeira:"., Estou curioso por que você sentiu a necessidade de teste "é falso" na primeira

Retirando o dis módulo, eu pensei em dissecar isso um pouco mais. Na minha experiência pyparsing, chamadas de função são totais assassinos de desempenho, por isso seria bom para evitar chamadas de função, se possível. Aqui é o seu teste 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

Duas coisas caras acontecer aqui, CALL_FUNCTION e LOAD_GLOBAL. Você poderia cortar em LOAD_GLOBAL pela definição de um nome local para Falso:

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

Mas o que se basta soltar o teste 'é' 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

Nós já entrou em colapso um LOAD_xxx e COMPARE_OP com um UNARY_NOT simples. "É falsa" certamente não está ajudando o desempenho causa qualquer.

Agora, o que se pode fazer alguma eliminação bruta de uma linha sem fazer quaisquer chamadas de função em tudo. Se o primeiro caractere da linha não é um 'c', não há como ele vai StartsWith ( 'customernum'). Vamos tentar isso:

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

(Note que usando [0] para obter o primeiro caractere de uma string não criar uma fatia -. Este é de fato muito rápido)

Agora, assumindo que não há um grande número de linhas que começam com 'c', o filtro bruto de corte pode eliminar uma linha usando todas as instruções bastante rápido. Na verdade, por meio de testes "t [0]! = 'C'" em vez de "não t [0] == 'c'" nós salvar uma instrução UNARY_NOT estranho.

Então, usando esse aprendizado sobre otimização de atalho e eu sugiro mudar este código:

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

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

Para isto:

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

Note que eu também tenha removido a chamada de função .readline (), e apenas iterar sobre o arquivo usando "para sline em txtdb".

Eu percebo Alex tem proporcionado um corpo diferente do código inteiramente para encontrar aquela primeira linha 'customernum', mas gostaria de tentar otimizar dentro dos limites gerais de seu algoritmo, antes de retirar a leitura do bloco armas grandes, mas obscuros.

Eu acho que você está escrevendo o script de importação e fica chato que esperar durante os testes, então as estadias dados da mesma o tempo todo.

Você pode executar o script uma vez para detectar as posições reais no arquivo que você deseja saltar para, com print txtdb.tell(). Escrever para baixo aqueles e substituir o código de busca com txtdb.seek( pos ). Basicamente desse builing um índice para o arquivo; -)

Outra maneira mais convetional seria ler dados em blocos maiores, alguns MB de cada vez, e não apenas os poucos bytes em uma linha.

Conte-nos mais sobre o arquivo.

Você pode usar file.seek fazer uma busca binária? Vai para a marca de metade, ler algumas linhas, determinar se você é antes ou depois da parte que você precisa, recurse. Isso vai transformar o seu O (n) procurar em O (log n).

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top