Performance de l'implémentation de Floyd-Warshall
Closed this issue · 2 comments
Bonjour
Vous proposez dans votre livre l'algorithme classique de Floyd-Warshall pour ASSP. Pas de souci concernant l'algo, mais curieusement les performances ne sont pas du tout safisfaisantes. Voici votre code (j'ai retiré la docstring et n'ai gardé que le calcul de la matrice des distances) :
def tryalgo(weight):
V = range(len(weight))
for k in V:
for u in V:
for v in V:
weight[u][v] = min(weight[u][v], weight[u][k] + weight[k][v])
Ce code en C++ est très efficace mais pas en Python. Il y a selon moi deux problèmes, provenant de la dernière ligne :
- en Python, l'opérateur
[]
est couteux (__getitem__
), or il est appliqué de nombreuses fois, tant à gauche qu'à droite - le minimum est calculé à chaque tour de boucle, et va souvent faire une affectation équivalente à
weight[u][v] = weight[u][v]
.
Donc, je pense qu'il faut ré-écrire le code pour :
- ne pas calculer le minimum avec
min
(et donc limiter l'évaluation du membre de droite), - factoriser le plus possible les appels à
__getitem__
pour éviter les recalculs.
En faisant cela, le temps d'exécution est divisé par un facteur allant de 3 à 4 sur ma machine (oui, oui, je n'y croyais pas) ! Voici quelques benchmarks:
from time import perf_counter
import networkx as nx
from copy import deepcopy
def random_edges(n, density, max_weight=100):
from random import randrange
M = n * (n - 1) // 2
m = int(density * M)
edges = set()
wedges = []
while len(edges) < m:
L = (randrange(n), randrange(n))
if L[0] != L[1] and L not in edges:
w = float(randrange(max_weight))
wedges.append(L + (w, ))
edges.add(L)
return wedges
def wedges2matrix(wedges, n, loop=True):
M = [[float('inf')] * n for _ in range(n)]
for a, b, w in wedges:
M[a][b] = w
if loop:
for a in range(n):
M[a][a] = 0
return M
def tryalgo(weight):
V = range(len(weight))
for k in V:
for u in V:
for v in V:
weight[u][v] = min(weight[u][v], weight[u][k] + weight[k][v])
def po(M):
n = len(M)
for k in range(n):
rowk = M[k]
colk = [M[i][k] for i in range(n)]
for i in range(n):
a = colk[i]
Mi = M[i]
for j in range(n):
if a + rowk[j] < Mi[j]:
Mi[j] = a + rowk[j]
def test(n=800, density=0.15):
wedges = random_edges(n, density)
print("%-10s%d" % ("Vertices:", n))
print("%-10s%d" % ("Edges:", len(wedges)))
print("%-10s%.2f" % ("Density:", density))
print("--------------------")
return wedges2matrix(wedges, n)
M = test(100)
MM = deepcopy(M)
start = perf_counter()
tryalgo(M)
tryalgo_time = perf_counter() - start
print("Tryalgo ASSP \t: %.2f" % tryalgo_time)
start = perf_counter()
po(MM)
po_time = perf_counter() - start
print("PO ASSP \t: %.2f" % po_time)
print("--------------------")
print("Checking:", M == MM)
print("Ratio: %.2f" % (tryalgo_time / po_time))
qui affiche
Vertices: 100
Edges: 742
Density: 0.15
--------------------
Tryalgo ASSP : 0.20
PO ASSP : 0.05
--------------------
Checking: True
Ratio: 3.74
Voilà, sinon, j'aime beaucoup votre livre et je fais confiance à vos algos qui me servent de références pour mes propres implémentations !
Cordialement
PO
Bonjour Christoph et merci de ta réponse
Concernant la lisibilité, bien sûr que votre algo doit être conservé, il faut garder à Floyd-Warshall sa simplicité !
J'ai signalé la lenteur car vos algos de graphes tournent plutôt vite et j'ai trouvé surprenant qu'un détail d'implémentation ait un tel effet sur les performances.
Pour la partie graphes que j'ai lue (chapitres 6 à 10 mais lecture très partielle), pas relevé d'erreur (quelques fautes d'orthographe de-ci, de-là). Par contre j'ai trouvé que certains algos étaient difficiles à comprendre et une description parfois plus exemplifiée pourrait faciliter la compréhension.
Bonne continuation !