Sugerencias sobre cómo acelerar un cálculo de distancia.

Considere la siguiente clase:

class SquareErrorDistance(object): def __init__(self, dataSample): variance = var(list(dataSample)) if variance == 0: self._norm = 1.0 else: self._norm = 1.0 / (2 * variance) def __call__(self, u, v): # u and v are floats return (u - v) ** 2 * self._norm 

Lo uso para calcular la distancia entre dos elementos de un vector. Básicamente, creo una instancia de esa clase para cada dimensión del vector que usa esta medida de distancia (hay dimensiones que usan otras medidas de distancia). El perfil revela que la función __call__ de esta clase representa el 90% del tiempo de ejecución de mi implementación de knn (quién lo hubiera pensado). No creo que haya ninguna forma de Python puro para acelerar esto, pero ¿quizás si lo implemento en C?

Si ejecuto un progtwig de C simple que solo calcula distancias para valores aleatorios usando la fórmula anterior, es órdenes de magnitud más rápido que Python. Así que traté de usar ctypes y llamé a una función C que realiza el cálculo, pero aparentemente la conversión de los parámetros y valores de retorno es demasiado costosa, porque el código resultante es mucho más lento.

Por supuesto, podría implementar todo el knn en C y solo llamar a eso, pero el problema es que, como describí, uso diferentes funciones de distancia para alguna dimensión de los vectores, y traducirlos a C sería demasiado trabajo.

¿Cuáles son mis alternativas? ¿La escritura de la función C utilizando la API C de Python eliminará la sobrecarga? ¿Hay alguna otra forma de acelerar este cálculo?

El siguiente código de cython (me doy cuenta de que la primera línea de __init__ es diferente, lo reemplacé con cosas aleatorias porque no conozco var y porque no importa de todos modos, usted dijo que __call__ es el cuello de botella):

 cdef class SquareErrorDistance: cdef double _norm def __init__(self, dataSample): variance = round(sum(dataSample)/len(dataSample)) if variance == 0: self._norm = 1.0 else: self._norm = 1.0 / (2 * variance) def __call__(self, double u, double v): # u and v are floats return (u - v) ** 2 * self._norm 

Comstackdo a través de un simple setup.py (solo el ejemplo de los documentos con el nombre del archivo modificado), se desempeña casi 20 veces mejor que el python puro equivalente en un simple timeit referencia de tiempo timeit . Tenga en cuenta que los únicos cambios fueron cdef s para el campo _norm y los parámetros __call__ . Considero esto bastante impresionante.

Probablemente esto no ayude mucho, pero puede reescribirlo usando funciones anidadas:

 def SquareErrorDistance(dataSample): variance = var(list(dataSample)) if variance == 0: def f(u, v): x = u - v return x * x else: norm = 1.0 / (2 * variance) def f(u, v): x = u - v return x * x * norm return f