Capítulo 18. Ajustes de rendimiento

Los ajustes de rendimiento son tarea de muchos esplendores. El hecho de que Python sea un lenguaje interpretado no significa que no deba preocuparse por la optimización de código. Pero tampoco debe preocuparse demasiado.

18.1. Inmersión

Hay tantas trampas en el camino a optimizar el código, que es difícil saber dónde empezar.

Empecemos por aquí: ¿está seguro de que lo necesita? ¿Es tan malo su código? ¿Merece la pena el tiempo de afinarlo? Durante el tiempo de vida de su aplicación, ¿cuánto tiempo va a pasar ese código ejecutándose, comparado al tiempo que habrá de esperar por una base de datos remota o por entrada de un usuario?

Segundo, ¿está seguro de que ya terminó de programar? Optimizar prematuramente es como adornar un pastel a medio hornear. Pasará horas o días (incluso más) optimizando el código para mejorar el rendimiento, sólo para descubrir que no hace lo que necesitaba que hiciera. Es tiempo que se va a la basura.

Esto no quiere decir que la optimización de código no merezca la pena, pero hace falta mirar al sistema completo y decidir cual es la mejor manera de usar su tiempo. Cada minuto que pierda optimizando el código es tiempo que no pasa añadiendo nuevas características, escribiendo documentación, jugando con sus hijos, o escribiendo pruebas unitarias.

Oh sí, pruebas unitarias. Casi se me pasa decir que necesitará un juego completo de pruebas unitarias antes de empezar el ajuste de rendimiento. Lo último que necesita es introducir nuevos fallos mientras juega con los algoritmos.

Con estas consideraciones en mente, veamos algunas técnicas para optimizar código en Python. El código en cuestión es una implementación del algoritmo Soundex. Soundex era un método que usaban a principios del siglo XX para organizar apellidos en categorías en el censo de los Estados Unidos. Agrupaba juntos nombres que sonaban parecido, así que incluso aunque se deletrease mal un nombre, los investigadores tendrían alguna oportunidad de encontrarlo. Soundex se sigue usando hoy día por la misma razón aunque, por supuesto, ahora usamos servidores de bases de datos computerizadas. La mayoría de los servidores de bases de datos incluyen una función Soundex.

Hay muchas variaciones sutiles sobre el algoritmo Soundex. En este capítulo usamos ésta:

  1. Deje la primera letra del nombre tal cual.
  2. Convierta el resto de letras en dígitos, de acuerdo a una tabla específica:
    • B, F, P, y V pasan a ser 1.
    • C, G, J, K, Q, S, X y Z pasan a ser 2.
    • D y T pasan a ser 3.
    • L pasa a ser 4.
    • M y N pasan a ser5 .
    • R pasa a ser 6.
    • El resto de las letras pasan a ser 9.
  3. Elimine los duplicados consecutivos.
  4. Elimine todos los 9.
  5. Si el resultado es menor de cuatro caracteres (la primera letra más tres dígitos), añada ceros al resultado hasta ese tamaño.
  6. si el resultado es mayor de cuatro caracteres, descarte todo tras el cuarto carácter.

Por ejemplo mi nombre, Pilgrim, se convierte en P942695. No tiene duplicados consecutivos así que no haré nada con eso. Ahora eliminamos los 9, dejando P4265. Eso es demasiado largo así que descartamos el carácter de exceso, dejando P426.

Otro ejemplo: Woo pasa a ser W99, que deriva en W9, que a su vez se convierte en W, que hay que rellenar con ceros dejando W000.

Éste es el primer intento de una función Soundex:

Ejemplo 18.1. soundex/stage1/soundex1a.py

Si aún no lo ha hecho, puede descargar éste ejemplo y otros usados en este libro.


import string, re

charToSoundex = {"A": "9",
                 "B": "1",
                 "C": "2",
                 "D": "3",
                 "E": "9",
                 "F": "1",
                 "G": "2",
                 "H": "9",
                 "I": "9",
                 "J": "2",
                 "K": "2",
                 "L": "4",
                 "M": "5",
                 "N": "5",
                 "O": "9",
                 "P": "1",
                 "Q": "2",
                 "R": "6",
                 "S": "2",
                 "T": "3",
                 "U": "9",
                 "V": "1",
                 "W": "9",
                 "X": "2",
                 "Y": "9",
                 "Z": "2"}

def soundex(source):
    "convert string to Soundex equivalent"

    # Soundex requirements:
    # source string must be at least 1 character
    # and must consist entirely of letters
    allChars = string.uppercase + string.lowercase
    if not re.search('^[%s]+$' % allChars, source):
        return "0000"

    # Soundex algorithm:
    # 1. make first character uppercase
    source = source[0].upper() + source[1:]
    
    # 2. translate all other characters to Soundex digits
    digits = source[0]
    for s in source[1:]:
        s = s.upper()
        digits += charToSoundex[s]

    # 3. remove consecutive duplicates
    digits2 = digits[0]
    for d in digits[1:]:
        if digits2[-1] != d:
            digits2 += d
        
    # 4. remove all "9"s
    digits3 = re.sub('9', '', digits2)
    
    # 5. pad end with "0"s to 4 characters
    while len(digits3) < 4:
        digits3 += "0"
        
    # 6. return first 4 characters
    return digits3[:4]

if __name__ == '__main__':
    from timeit import Timer
    names = ('Woo', 'Pilgrim', 'Flingjingwaller')
    for name in names:
        statement = "soundex('%s')" % name
        t = Timer(statement, "from __main__ import soundex")
        print name.ljust(15), soundex(name), min(t.repeat())

Lecturas complementarias sobre Soundex