¿Las listas de comprensión y las funciones funcionales son más rápidas que “para bucles”?

En términos de rendimiento en Python, ¿es una lista-comprensión, o funciones como map (), filter () y reduce () más rápido que un bucle for? ¿Por qué, técnicamente, “se ejecutan en una velocidad de C”, mientras que “el bucle for se ejecuta en la velocidad de la máquina virtual de python”?

Supongamos que en un juego que estoy desarrollando necesito dibujar mapas complejos e inmensos usando bucles. Esta pregunta sería definitivamente relevante, ya que si la comprensión de la lista, por ejemplo, es más rápida, sería una opción mucho mejor para evitar retrasos (a pesar de la complejidad visual del código).

Si verifica la información en python.org , puede ver este resumen:

 Version Time (seconds) Basic loop 3.47 Eliminate dots 2.45 Local variable & no dots 1.79 Using map function 0.54 

Pero realmente debería leer el artículo anterior en detalles para comprender la causa de la diferencia de rendimiento.

También le sugiero que programe su código usando timeit . Al final del día, puede haber una situación en la que, por ejemplo, es posible que deba interrumpir el ciclo cuando se cumple una condición. Potencialmente, podría ser más rápido que averiguar el resultado llamando al map .

Usted pregunta específicamente sobre map (), filter () y reduce (), pero supongo que quiere saber acerca de la progtwigción funcional en general. Habiendo probado esto por mí mismo en el problema de calcular distancias entre todos los puntos dentro de un conjunto de puntos, la progtwigción funcional (usando la función de mapa de enlace del módulo de itertools incorporado) resultó ser un poco más lenta que los bucles for (que toma 1.25 veces más tiempo) , de hecho). Aquí está el código de muestra que utilicé:

 import itertools, time, math, random class Point: def __init__(self,x,y): self.x, self.y = x, y point_set = (Point(0, 0), Point(0, 1), Point(0, 2), Point(0, 3)) n_points = 100 pick_val = lambda : 10 * random.random() - 5 large_set = [Point(pick_val(), pick_val()) for _ in range(n_points)] # the distance function f_dist = lambda x0, x1, y0, y1: math.sqrt((x0 - x1) ** 2 + (y0 - y1) ** 2) # go through each point, get its distance from all remaining points f_pos = lambda p1, p2: (p1.x, p2.x, p1.y, p2.y) extract_dists = lambda x: itertools.starmap(f_dist, itertools.starmap(f_pos, itertools.combinations(x, 2))) print('Distances:', list(extract_dists(point_set))) t0_f = time.time() list(extract_dists(large_set)) dt_f = time.time() - t0_f 

¿Es la versión funcional más rápida que la versión de procedimiento?

 def extract_dists_procedural(pts): n_pts = len(pts) l = [] for k_p1 in range(n_pts - 1): for k_p2 in range(k_p1, n_pts): l.append((pts[k_p1].x - pts[k_p2].x) ** 2 + (pts[k_p1].y - pts[k_p2].y) ** 2) return l t0_p = time.time() list(extract_dists_procedural(large_set)) # using list() on the assumption that # it eats up as much time as in the functional version dt_p = time.time() - t0_p f_vs_p = dt_p / dt_f if f_vs_p >= 1.0: print('Time benefit of functional progamming:', f_vs_p, 'times as fast for', n_points, 'points') else: print('Time penalty of functional programming:', 1 / f_vs_p, 'times as slow for', n_points, 'points') 

Escribí un simple script que prueba la velocidad y esto es lo que descubrí. En realidad para bucle fue el más rápido en mi caso. Eso realmente me sorprendió, echa un vistazo abajo (estaba calculando la sum de los cuadrados).

 from functools import reduce import datetime def time_it(func, numbers, *args): start_t = datetime.datetime.now() for i in range(numbers): func(args[0]) print (datetime.datetime.now()-start_t) def square_sum1(numbers): return reduce(lambda sum, next: sum+next**2, numbers, 0) def square_sum2(numbers): a = 0 for i in numbers: i = i**2 a += i return a def square_sum3(numbers): sqrt = lambda x: x**2 return sum(map(sqrt, numbers)) def square_sum4(numbers): return(sum([int(i)**2 for i in numbers])) time_it(square_sum1, 100000, [1, 2, 5, 3, 1, 2, 5, 3]) time_it(square_sum2, 100000, [1, 2, 5, 3, 1, 2, 5, 3]) time_it(square_sum3, 100000, [1, 2, 5, 3, 1, 2, 5, 3]) time_it(square_sum4, 100000, [1, 2, 5, 3, 1, 2, 5, 3]) 

0:00:00.302000 #Reduce 0:00:00.144000 #For loop 0:00:00.318000 #Map 0:00:00.390000 #List comprehension

Agregando un giro a la respuesta de Alphii , en realidad el bucle for sería el segundo mejor y aproximadamente 6 veces más lento que el map

 from functools import reduce import datetime def time_it(func, numbers, *args): start_t = datetime.datetime.now() for i in range(numbers): func(args[0]) print (datetime.datetime.now()-start_t) def square_sum1(numbers): return reduce(lambda sum, next: sum+next**2, numbers, 0) def square_sum2(numbers): a = 0 for i in numbers: a += i**2 return a def square_sum3(numbers): a = 0 map(lambda x: a+x**2, numbers) return a def square_sum4(numbers): a = 0 return [a+i**2 for i in numbers] time_it(square_sum1, 100000, [1, 2, 5, 3, 1, 2, 5, 3]) time_it(square_sum2, 100000, [1, 2, 5, 3, 1, 2, 5, 3]) time_it(square_sum3, 100000, [1, 2, 5, 3, 1, 2, 5, 3]) time_it(square_sum4, 100000, [1, 2, 5, 3, 1, 2, 5, 3]) 

Los principales cambios han sido eliminar las llamadas de sum lenta, así como el int() probablemente innecesario en el último caso. Poner el bucle for y el mapa en los mismos términos hace que sea un hecho, en realidad. Recuerde que las lambdas son conceptos funcionales y que, en teoría, no deberían tener efectos secundarios, pero, bueno, pueden tener efectos secundarios como la adición de a. Resultados en este caso con Python 3.6.1, Ubuntu 14.04, CPU Intel® Core (TM) i7-4770 a 3.40GHz

 0:00:00.257703 0:00:00.184898 0:00:00.031718 0:00:00.212699