Estoy tratando de usar el generador de perfiles de Python para acelerar mi código. He podido identificar la función específica donde se pasa casi todo el tiempo, pero no puedo entender en qué parte de esa función se está gastando el tiempo.

A continuación tengo la salida del perfil, que muestra que "appendBallot" es el principal culpable y consume casi 116 segundos. Más abajo, tengo el código para "appendBallot".

No puedo deducir de la salida del perfil, qué parte de "appendBallot" necesito optimizar ya que la siguiente entrada de tiempo más alta es menos de un segundo. Estoy seguro de que muchos de ustedes podrían decirme solo por mi código, pero me gustaría entender cómo obtener esa información de la salida del perfil. Cualquier ayuda sería muy apreciada.

Salida de perfil:

  ncalls  tottime  percall  cumtime  percall filename:lineno(function)
       1    0.000    0.000  116.168  116.168 <string>:1(<module>)
       1    0.001    0.001  116.168  116.168 {execfile}
       1    0.003    0.003  116.167  116.167 foo.py:1(<module>)
       1    0.000    0.000  116.139  116.139 ballots.py:330(loadKnown)
       1    0.000    0.000  116.109  116.109 plugins.py:148(load)
       1    0.196    0.196  116.108  116.108 BltBallotLoader.py:37(loadFile)
  100000  114.937    0.001  115.912    0.001 ballots.py:133(appendBallot)
  100000    0.480    0.000    0.790    0.000 ballots.py:117(newBallot)
  316668    0.227    0.000    0.310    0.000 ballots.py:107(getNumCandidates)
417310/417273    0.111    0.000    0.111    0.000 {len}
  200510    0.071    0.000    0.071    0.000 {method 'append' of 'list' objects}
   99996    0.045    0.000    0.045    0.000 {method 'add' of 'set' objects}
  100000    0.042    0.000    0.042    0.000 {method 'has_key' of 'dict' objects}
       1    0.000    0.000    0.030    0.030 plugins.py:202(getLoaderPluginClasses)
       1    0.000    0.000    0.030    0.030 plugins.py:179(getPluginClasses)
       1    0.000    0.000    0.030    0.030 plugins.py:205(getLoaderPluginClass)
       3    0.016    0.005    0.029    0.010 {__import__}
       1    0.022    0.022    0.025    0.025 ballots.py:1(<module>)
       1    0.010    0.010    0.013    0.013 BltBallotLoader.py:1(<module>)
       7    0.000    0.000    0.003    0.000 re.py:227(_compile)

Código:

  def appendBallot(self, ballot, ballotID=None):
    "Append a ballot to this Ballots object."

    # String representation of ballot for determining whether ballot is unique
    ballotString = str(list(ballot))

    # Ballot as the appropriate array to conserve memory
    ballot = self.newBallot(ballot)

    # Assign a ballot ID if one has not been given
    if ballotID is None:
      ballotID = len(self.ballotIDs)
    assert(ballotID not in self.ballotIDs)
    self.ballotIDs.append(ballotID)

    # Check to see if we have seen this ballot before
    if self.uniqueBallotsLookup.has_key(ballotString):
      i = self.uniqueBallotsLookup[ballotString]
      self.uniqueBallotIDs[i].add(ballotID)
    else:
      i = len(self.uniqueBallots)
      self.uniqueBallotsLookup[ballotString] = i
      self.uniqueBallots.append(ballot)
      self.uniqueBallotIDs.append(set([ballotID]))
    self.ballotOrder.append(i)
7
gaefan 24 sep. 2009 a las 07:50

6 respuestas

La mejor respuesta

Los perfiladores pueden ser así. El método que uso es this. Llega directamente al corazón del problema en muy poco tiempo.

5
Community 23 may. 2017 a las 11:54

Tiene dos problemas en esta pequeña porción de código:

# Assign a ballot ID if one has not been given
if ballotID is None:
    ballotID = len(self.ballotIDs)
assert(ballotID not in self.ballotIDs)
self.ballotIDs.append(ballotID)

En primer lugar, parece que self.ballotIDs es una lista, por lo que la declaración de aserción provocará un comportamiento cuadrático. Como no proporcionó ninguna documentación para sus estructuras de datos, no es posible ser prescriptivo, pero si el orden de aparición no importa, puede usar un conjunto en lugar de una lista.

En segundo lugar, la lógica (en ausencia de documentación sobre de qué se trata un ballotID y lo que significa un argumento de ballotID no-None) parece estar seriamente molesta:

obj.appendBallot(ballota, 2) # self.ballotIDs -> [2]
obj.appendBallot(ballotb)    # self.ballotIDs -> [2, 1]
obj.appendBallot(ballotc)    # wants to add 2 but triggers assertion

Otros comentarios:

En lugar de adict.has_key(key), use key in adict: es más rápido y se ve mejor.

Es posible que desee considerar revisar sus estructuras de datos ... parecen ser un poco barrocas; puede haber un poco de tiempo de CPU involucrado en su construcción.

2
John Machin 24 sep. 2009 a las 15:06

He utilizado este decorador en mi código, y me ayudó. yo con mi trabajo de tuning pyparsing.

4
Chris Seymour 7 jul. 2014 a las 08:37

Apoyaré a Fragsworth diciendo que querrás dividir tu función en otras más pequeñas.

Dicho esto, estás leyendo la salida correctamente: el tiempo de espera es el que debes observar.

Ahora, donde es probable que sea su desaceleración:

Dado que parece haber 100000 llamadas para agregarBallot, y no hay ningún bucle obvio, sugeriría que esté en su afirmación. Porque estás ejecutando:

assert(ballotID not in self.ballotIDs)

Esto realmente actuará como un bucle. Por lo tanto, la primera vez que llame a esta función, iterará a través de una matriz (probablemente vacía) y luego afirmará si se encontró el valor. La vez número 100000 iterará por toda la matriz.

Y en realidad hay un posible error aquí: si se elimina una boleta, entonces la próxima boleta agregada tendría la misma identificación que la última agregada (a menos que esa fuera la eliminada). Creo que sería mejor usar un contador simple. De esa manera, puede aumentarlo cada vez que agrega una boleta. Alternativamente, puede usar un UUID para obtener identificadores únicos.

Alternativamente, si está buscando algún nivel de persistencia, use un ORM y consiga que haga la generación de ID y una verificación única para usted.

5
Matthew Schinckel 25 sep. 2009 a las 05:57

He echado un vistazo a su código, y parece que realiza muchas llamadas a funciones y búsquedas de atributos como parte de su 'comprobación' o de anticipación antes de saltar. También tiene una gran cantidad de código dedicado para rastrear la misma condición, es decir, muchos bits de código que buscan crear ID 'únicos'.

En lugar de tratar de asignar algún tipo de cadena única a cada boleta, ¿no podría simplemente usar el ID de boleta (un número entero)?

Ahora podría tener un diccionario (uniqueBallotIDs) mapeando ballotID y el objeto de boleta real.

El proceso podría ser algo como esto:

def appendBallot(self, ballot, ballotID=None):
   if ballotID is None:
       ballotID = self._getuniqueid() # maybe just has a counter? up to you.
   # check to see if we have seen this ballot before.
   if not self._isunique(ballotID):
       # code for non-unique ballot ids.
   else:
       # code for unique ballot ids.

   self.ballotOrder.append(i)

Es posible que pueda manejar algunas de sus preocupaciones sobre el diccionario que falta una clave determinada mediante el uso de un defaultdict (desde el módulo de colecciones). documentos de la colección

Editar para completar, incluiré una muestra de uso del defaultdict:

>>> from collections import defaultdict            

>>> ballotIDmap = defaultdict(list)
>>> ballotID, ballot = 1, object() # some nominal ballotID and object.
>>> # I will now try to save my ballotID.
>>> ballotIDmap[ballotID].append(ballot)
>>> ballotIDmap.items()
[(1, [<object object at 0x009BB950>])]
5
Simon Edwards 24 sep. 2009 a las 05:36

Sí, me encontré con ese mismo problema también.

La única forma en que sé evitar esto es envolver su función grande en varias llamadas a funciones más pequeñas. Esto permitirá que el generador de perfiles tenga en cuenta cada una de las llamadas a funciones más pequeñas.

Lo suficientemente interesante, el proceso de hacer esto (para mí, de todos modos) hizo obvio dónde estaban las ineficiencias, por lo que ni siquiera tuve que ejecutar el generador de perfiles.

7
Fragsworth 24 sep. 2009 a las 03:58