Je veux exposer la réponse simple avec diverses notes de performance. np.linalg.norm fera peut-être plus que ce dont vous avez besoin:
dist = numpy.linalg.norm(a-b)
Premièrement - cette fonction est conçue pour travailler sur une liste et renvoyer toutes les valeurs, par exemple pour comparer la distance de pA
à l'ensemble de points sP
:
sP = set(points)
pA = point
distances = np.linalg.norm(sP - pA, ord=2, axis=1.) # 'distances' is a list
Rappelez-vous plusieurs choses:
- Les appels de fonction Python sont chers.
- [Normal] Python ne met pas en cache les recherches de nom.
Donc
def distance(pointA, pointB):
dist = np.linalg.norm(pointA - pointB)
return dist
n'est pas aussi innocent qu'il n'y paraît.
>>> dis.dis(distance)
2 0 LOAD_GLOBAL 0 (np)
2 LOAD_ATTR 1 (linalg)
4 LOAD_ATTR 2 (norm)
6 LOAD_FAST 0 (pointA)
8 LOAD_FAST 1 (pointB)
10 BINARY_SUBTRACT
12 CALL_FUNCTION 1
14 STORE_FAST 2 (dist)
3 16 LOAD_FAST 2 (dist)
18 RETURN_VALUE
Premièrement - chaque fois que nous l'appelons, nous devons faire une recherche globale pour "np", une recherche de portée pour "linalg" et une recherche de portée pour "norm", et la surcharge de simplement appeler la fonction peut correspondre à des dizaines de python instructions.
Enfin, nous avons gaspillé deux opérations pour stocker le résultat et le recharger pour le retour ...
Première passe à l'amélioration: accélérer la recherche, sauter le magasin
def distance(pointA, pointB, _norm=np.linalg.norm):
return _norm(pointA - pointB)
Nous obtenons le beaucoup plus rationalisé:
>>> dis.dis(distance)
2 0 LOAD_FAST 2 (_norm)
2 LOAD_FAST 0 (pointA)
4 LOAD_FAST 1 (pointB)
6 BINARY_SUBTRACT
8 CALL_FUNCTION 1
10 RETURN_VALUE
Cependant, la surcharge de l'appel de fonction représente encore un peu de travail. Et vous voudrez faire des repères pour déterminer si vous feriez mieux de faire les calculs vous-même:
def distance(pointA, pointB):
return (
((pointA.x - pointB.x) ** 2) +
((pointA.y - pointB.y) ** 2) +
((pointA.z - pointB.z) ** 2)
) ** 0.5 # fast sqrt
Sur certaines plateformes, **0.5
est plus rapide que math.sqrt
. Votre kilométrage peut varier.
**** Notes de performance avancées.
Pourquoi calculez-vous la distance? Si le seul but est de l'afficher,
print("The target is %.2fm away" % (distance(a, b)))
avancer. Mais si vous comparez des distances, effectuez des vérifications de portée, etc., j'aimerais ajouter quelques observations utiles sur les performances.
Prenons deux cas: tri par distance ou élimination d'une liste des éléments qui répondent à une contrainte de plage.
# Ultra naive implementations. Hold onto your hat.
def sort_things_by_distance(origin, things):
return things.sort(key=lambda thing: distance(origin, thing))
def in_range(origin, range, things):
things_in_range = []
for thing in things:
if distance(origin, thing) <= range:
things_in_range.append(thing)
La première chose dont nous devons nous souvenir est que nous utilisons Pythagore pour calculer la distance ( dist = sqrt(x^2 + y^2 + z^2)
), donc nous faisons beaucoup d' sqrt
appels. Math 101:
dist = root ( x^2 + y^2 + z^2 )
:.
dist^2 = x^2 + y^2 + z^2
and
sq(N) < sq(M) iff M > N
and
sq(N) > sq(M) iff N > M
and
sq(N) = sq(M) iff N == M
En bref: jusqu'à ce que nous ayons réellement besoin de la distance dans une unité de X plutôt que X ^ 2, nous pouvons éliminer la partie la plus difficile des calculs.
# Still naive, but much faster.
def distance_sq(left, right):
""" Returns the square of the distance between left and right. """
return (
((left.x - right.x) ** 2) +
((left.y - right.y) ** 2) +
((left.z - right.z) ** 2)
)
def sort_things_by_distance(origin, things):
return things.sort(key=lambda thing: distance_sq(origin, thing))
def in_range(origin, range, things):
things_in_range = []
# Remember that sqrt(N)**2 == N, so if we square
# range, we don't need to root the distances.
range_sq = range**2
for thing in things:
if distance_sq(origin, thing) <= range_sq:
things_in_range.append(thing)
Génial, les deux fonctions ne font plus de racines carrées coûteuses. Ce sera beaucoup plus rapide. Nous pouvons également améliorer in_range en le convertissant en générateur:
def in_range(origin, range, things):
range_sq = range**2
yield from (thing for thing in things
if distance_sq(origin, thing) <= range_sq)
Cela a surtout des avantages si vous faites quelque chose comme:
if any(in_range(origin, max_dist, things)):
...
Mais si la prochaine chose que vous allez faire nécessite une distance,
for nearby in in_range(origin, walking_distance, hotdog_stands):
print("%s %.2fm" % (nearby.name, distance(origin, nearby)))
pensez à donner des tuples:
def in_range_with_dist_sq(origin, range, things):
range_sq = range**2
for thing in things:
dist_sq = distance_sq(origin, thing)
if dist_sq <= range_sq: yield (thing, dist_sq)
Cela peut être particulièrement utile si vous pouvez enchaîner des vérifications de plage (`` trouver des choses qui sont proches de X et à Nm de Y '', car vous n'avez pas à calculer à nouveau la distance).
Mais qu'en est-il si nous recherchons une très grande liste things
et que nous prévoyons que beaucoup d'entre eux ne méritent pas d'être pris en considération?
Il y a en fait une optimisation très simple:
def in_range_all_the_things(origin, range, things):
range_sq = range**2
for thing in things:
dist_sq = (origin.x - thing.x) ** 2
if dist_sq <= range_sq:
dist_sq += (origin.y - thing.y) ** 2
if dist_sq <= range_sq:
dist_sq += (origin.z - thing.z) ** 2
if dist_sq <= range_sq:
yield thing
Son utilité dépendra de la taille des «choses».
def in_range_all_the_things(origin, range, things):
range_sq = range**2
if len(things) >= 4096:
for thing in things:
dist_sq = (origin.x - thing.x) ** 2
if dist_sq <= range_sq:
dist_sq += (origin.y - thing.y) ** 2
if dist_sq <= range_sq:
dist_sq += (origin.z - thing.z) ** 2
if dist_sq <= range_sq:
yield thing
elif len(things) > 32:
for things in things:
dist_sq = (origin.x - thing.x) ** 2
if dist_sq <= range_sq:
dist_sq += (origin.y - thing.y) ** 2 + (origin.z - thing.z) ** 2
if dist_sq <= range_sq:
yield thing
else:
... just calculate distance and range-check it ...
Et encore une fois, envisagez de fournir le dist_sq. Notre exemple de hot-dog devient alors:
# Chaining generators
info = in_range_with_dist_sq(origin, walking_distance, hotdog_stands)
info = (stand, dist_sq**0.5 for stand, dist_sq in info)
for stand, dist in info:
print("%s %.2fm" % (stand, dist))