তালিকা-বোধগম্য এবং কার্যকরী ফাংশনগুলি কি "লুপগুলির জন্য" এর চেয়ে দ্রুত?


155

পাইথন কর্মক্ষমতা নিরিখে একটি তালিকা-ধী, অথবা ফাংশন পছন্দ map(), filter()এবং reduce()দ্রুত লুপ জন্য একটি চেয়ে? কেন, প্রযুক্তিগতভাবে, তারা সি গতিতে চালিত হয় , যখন লুপের জন্য পাইথন ভার্চুয়াল মেশিনের গতিতে চালিত হয় ?

মনে করুন যে আমি যে গেমটি বিকাশ করছি তাতে আমাকে লুপগুলি ব্যবহার করে জটিল এবং বিশাল মানচিত্র আঁকতে হবে। এই প্রশ্নটি অবশ্যই প্রাসঙ্গিক হবে, কারণ যদি একটি তালিকা-উপলব্ধি, উদাহরণস্বরূপ, দ্রুততর হয় তবে ল্যাগগুলি এড়ানোর জন্য এটি একটি আরও ভাল বিকল্প হবে (কোডটির ভিজ্যুয়াল জটিলতা সত্ত্বেও)।

উত্তর:


146

নিম্নলিখিত অভিজ্ঞতার উপর ভিত্তি করে মোটামুটি গাইডলাইন এবং শিক্ষিত অনুমান। timeitহার্ড নম্বর পেতে আপনার কংক্রিট ব্যবহারের ক্ষেত্রে বা প্রোফাইল করা উচিত এবং সেই সংখ্যাগুলি মাঝে মাঝে নীচের সাথে একমত হতে পারেন।

একটি তালিকা বোধগম্যতা যথাযথ সমতুল্য forলুপের তুলনায় একটি সামান্য বিট দ্রুত হয় (এটি আসলে একটি তালিকা তৈরি করে) সম্ভবত এটি সম্ভবত appendপ্রতিটি পুনরাবৃত্তিতে তালিকা এবং তার পদ্ধতিটি অনুসন্ধান করতে হবে না । তবে, একটি তালিকা উপলব্ধি এখনও একটি বাইটোকোড-স্তরীয় লুপ করে:

>>> dis.dis(<the code object for `[x for x in range(10)]`>)
 1           0 BUILD_LIST               0
             3 LOAD_FAST                0 (.0)
       >>    6 FOR_ITER                12 (to 21)
             9 STORE_FAST               1 (x)
            12 LOAD_FAST                1 (x)
            15 LIST_APPEND              2
            18 JUMP_ABSOLUTE            6
       >>   21 RETURN_VALUE

একটি তালিকা তৈরি করে না এমন লুপের জায়গায় একটি তালিকা বোধগম্যতা ব্যবহার করা , অর্থহীনভাবে অর্থহীন মানগুলির একটি তালিকা সংগ্রহ করা এবং তারপরে তালিকাটি ফেলে দেওয়া প্রায়শই ধীর হয় er তৈরি এবং প্রসারিত করার ওভারহেডের কারণে । তালিকা বোধগম্যতা এমন জাদু নয় যা ভাল পুরানো লুপের চেয়ে সহজাতভাবে দ্রুত।

ক্রিয়ামূলক তালিকা প্রক্রিয়াকরণ ফাংশন হিসাবে: এই সি লেখা হয় এবং সম্ভবত পাইথন লেখা সমতুল্য ফাংশন সুখ্যাতি যদিও, তারা না অগত্যা দ্রুততম বিকল্প। ফাংশনটি সি তেও লেখা থাকলে কিছু গতি বাড়ানো আশা করা যায়। তবে বেশিরভাগ ক্ষেত্রেই এlambda (বা অন্যান্য পাইথন ফাংশন) বার বার পাইথন স্ট্যাক ফ্রেম স্থাপনের ওভারহেড কোনও সঞ্চয়ই খায়। কেবল একই কাজটি ইন-লাইনে করা, ফাংশন কল ছাড়াই (উদাহরণস্বরূপ mapবা এর পরিবর্তে একটি তালিকা উপলব্ধি filter) প্রায়শই দ্রুত হয়।

মনে করুন যে আমি যে গেমটি বিকাশ করছি তাতে আমাকে লুপগুলি ব্যবহার করে জটিল এবং বিশাল মানচিত্র আঁকতে হবে। এই প্রশ্নটি অবশ্যই প্রাসঙ্গিক হবে, কারণ যদি একটি তালিকা-উপলব্ধি, উদাহরণস্বরূপ, দ্রুততর হয় তবে ল্যাগগুলি এড়ানোর জন্য এটি একটি আরও ভাল বিকল্প হবে (কোডটির ভিজ্যুয়াল জটিলতা সত্ত্বেও)।

সম্ভাবনাগুলি হ'ল, যদি ভাল অ-"অনুকূলিত" পাইথনে লিখিতভাবে এই জাতীয় কোডটি ইতিমধ্যে পর্যাপ্ত দ্রুত না হয়, পাইথন স্তরের মাইক্রো অপ্টিমাইজেশনের পরিমাণের পরিমাণ এটি যথেষ্ট দ্রুত তৈরি করতে চলেছে না এবং আপনার সিডে নেমে যাওয়ার চিন্তা করা উচিত extensive মাইক্রো অপ্টিমাইজেশানগুলি প্রায়শই পাইথন কোডকে যথেষ্ট গতিময় করে তুলতে পারে, এটির একটি সীমাবদ্ধতা (পরম পদে) রয়েছে in তদুপরি, আপনি সেই সিলিংটি আঘাত করার আগে, বুলেটটি কামড়ানোর জন্য এবং কিছু সি লিখার জন্য এটি আরও ব্যয়সাধ্য দক্ষ হয়ে ওঠে (15% স্পিডআপ বনাম 300% একই প্রচেষ্টা দিয়ে গতি))


25

আপনি যদি পাইথন.আর.গে তথ্য পরীক্ষা করেন তবে আপনি এই সংক্ষিপ্তসারটি দেখতে পাবেন:

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

তবে আপনার সত্যই হওয়া উচিত বিস্তারিত উপরে নিবন্ধ পড়া কর্মক্ষমতা পার্থক্য কারণ বুঝতে।

আমি টাইমিট ব্যবহার করে আপনার কোডটি সময় করা উচিত বলে দৃ strongly়ভাবে পরামর্শ দিই । দিনের শেষে, এমন পরিস্থিতি তৈরি হতে পারে যেখানে উদাহরণস্বরূপ, forযখন কোনও শর্ত পূরণ হয় তখন আপনাকে লুপ থেকে বেরিয়ে আসতে হবে । এটি কল করে ফলাফলটি খুঁজে পাওয়ার চেয়ে দ্রুততর হতে পারে map


17
যদিও পৃষ্ঠাটি একটি ভাল পঠিত এবং আংশিকভাবে সম্পর্কিত, কেবলমাত্র এই সংখ্যাগুলি উদ্ধৃত করা কার্যকর নয়, সম্ভবত বিভ্রান্তিকরও।

1
এটি আপনার সময় কীসের ইঙ্গিত দেয় না। লুপ / ​​লিস্টকম্প / মানচিত্রে যা আছে তার উপর নির্ভর করে আপেক্ষিক পারফরম্যান্স ব্যাপকভাবে পরিবর্তিত হবে।
ব্যবহারকারী 2357112

@ ডেলান আমি সম্মত পারফরম্যান্সের পার্থক্য বোঝার জন্য ডকুমেন্টেশন পড়ার জন্য ওপিকে অনুরোধ করার জন্য আমি আমার উত্তরটি পরিবর্তন করেছি।
অ্যান্টনি কং

@ ব্যবহারকারী 2357112 আপনাকে প্রসঙ্গের জন্য লিঙ্কিত উইকি পৃষ্ঠাটি পড়তে হবে। আমি ওপির রেফারেন্সের জন্য এটি পোস্ট করেছি।
অ্যান্টনি কং

13

আপনি সম্পর্কে বিশেষভাবে জিজ্ঞাসা করুন map(), filter()এবং reduce(), তবে আমি ধরে নিই আপনি সাধারণভাবে ফাংশনাল প্রোগ্রামিং সম্পর্কে জানতে চান। বিন্দুগুলির একটি সেটের মধ্যে সমস্ত পয়েন্টের মধ্যে দূরত্বের কম্পিউটিংয়ের সমস্যাটি সম্পর্কে নিজেই এটি পরীক্ষা করে দেখার পরে, ফাংশনাল প্রোগ্রামিং ( starmapঅন্তর্নির্মিত itertoolsমডিউল থেকে ফাংশনটি ব্যবহার করে ) ফর লুপগুলির তুলনায় কিছুটা ধীরে ধীরে পরিণত হয় (১.২25 বার যতক্ষণ সময় লাগে) আসলে)। আমি ব্যবহৃত নমুনা কোডটি এখানে:

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

কার্যনির্বাহী সংস্করণ প্রক্রিয়াগত সংস্করণের চেয়ে দ্রুত?

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')

2
এই প্রশ্নের উত্তর দেওয়ার জন্য বরং এক বিশৃঙ্খল উপায় বলে মনে হচ্ছে। আপনি এটি নিখুঁত করতে পারেন যাতে এটি আরও ভাল বোঝা যায়?
অ্যারন হল

2
অ্যারোনহল @ আমি আসলে এন্ড্রিআইপএমসিএন এর উত্তরকে বরং আকর্ষণীয় বলেই খুঁজে পেয়েছি কারণ এটি একটি তুচ্ছ উদাহরণ। কোড আমরা খেলতে পারি।
অ্যান্টনি কং

@ অ্যারনহল, আপনি কি আমাকে পাঠ্য অনুচ্ছেদে সম্পাদনা করতে চান যাতে এটি আরও স্পষ্ট এবং সোজা বলে মনে হয়, বা আপনি কোডটি সম্পাদনা করতে চান?
andreipmbcn

9

আমি একটি সাধারণ স্ক্রিপ্ট লিখেছিলাম যা গতি পরীক্ষা করে এবং আমি এটিই খুঁজে পেয়েছি। আসলে লুপের ক্ষেত্রে আমার ক্ষেত্রে সবচেয়ে দ্রুত ছিল। যা আমাকে সত্যিই অবাক করে দিয়েছিল, নমুনা পরীক্ষা করছিল (বর্গাকার সমষ্টি গণনা করছিল)।

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

অজগর সহ 3.6.1 পার্থক্য এত বড় নয়; হ্রাস করুন এবং মানচিত্রটি 0.24 এ নেমে যান এবং বোধগম্যাকে 0.29 এ তালিকাবদ্ধ করুন। 0.18 এ বেশি higher
jjmerelo

দূর intমধ্যে square_sum4এটি বেশ একটু দ্রুত এবং লুপ চেয়ে একটু ধীরে হয়ে যায়।
jjmerelo

5

আলফাই উত্তরে একটি টুইস্ট যুক্ত করা , আসলে লুপের জন্য দ্বিতীয়টি সেরা এবং এর চেয়ে প্রায় 6 গুণ বেশি ধীর হবে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])

মূল পরিবর্তনগুলি হ'ল ধীর sumকলগুলি দূর করার পাশাপাশি int()শেষ ক্ষেত্রে সম্ভবত অপ্রয়োজনীয় । লুপ এবং মানচিত্রটিকে একই পদে রাখলে এটি বাস্তবে বেশ বাস্তব হয়ে যায়। মনে রাখবেন যে lambdas কার্মিক ধারণা এবং তাত্ত্বিক পার্শ্ব প্রতিক্রিয়া আছে না হয় বরং, ভাল, তারা করতে পারেন যোগ মত পার্শ্ব প্রতিক্রিয়া আছে a। পাইথন 3.6.1, উবুন্টু 14.04, ইনটেল (আর) কোর (টিএম) i7-4770 সিপিইউ @ 3.40GHz এর সাথে এই ক্ষেত্রে ফলাফল

0:00:00.257703 #Reduce
0:00:00.184898 #For loop
0:00:00.031718 #Map
0:00:00.212699 #List comprehension

2
স্কয়ার_সুম 3 এবং স্কয়ার_সুম 4 ভুল। তারা যোগফল দেবে না। @Alisca চেনের নীচের উত্তরগুলি আসলে সঠিক।
শিখারদুয়া

5

আমি @ আলিসার কোডটি সংশোধন করেছি এবং cProfileকেন তালিকা বোধগতি দ্রুত হয় তা দেখানোর জন্য ব্যবহৃত :

from functools import reduce
import datetime

def reduce_(numbers):
    return reduce(lambda sum, next: sum + next * next, numbers, 0)

def for_loop(numbers):
    a = []
    for i in numbers:
        a.append(i*2)
    a = sum(a)
    return a

def map_(numbers):
    sqrt = lambda x: x*x
    return sum(map(sqrt, numbers))

def list_comp(numbers):
    return(sum([i*i for i in numbers]))

funcs = [
        reduce_,
        for_loop,
        map_,
        list_comp
        ]

if __name__ == "__main__":
    # [1, 2, 5, 3, 1, 2, 5, 3]
    import cProfile
    for f in funcs:
        print('=' * 25)
        print("Profiling:", f.__name__)
        print('=' * 25)
        pr = cProfile.Profile()
        for i in range(10**6):
            pr.runcall(f, [1, 2, 5, 3, 1, 2, 5, 3])
        pr.create_stats()
        pr.print_stats()

ফলাফল এখানে:

=========================
Profiling: reduce_
=========================
         11000000 function calls in 1.501 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
  1000000    0.162    0.000    1.473    0.000 profiling.py:4(reduce_)
  8000000    0.461    0.000    0.461    0.000 profiling.py:5(<lambda>)
  1000000    0.850    0.000    1.311    0.000 {built-in method _functools.reduce}
  1000000    0.028    0.000    0.028    0.000 {method 'disable' of '_lsprof.Profiler' objects}


=========================
Profiling: for_loop
=========================
         11000000 function calls in 1.372 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
  1000000    0.879    0.000    1.344    0.000 profiling.py:7(for_loop)
  1000000    0.145    0.000    0.145    0.000 {built-in method builtins.sum}
  8000000    0.320    0.000    0.320    0.000 {method 'append' of 'list' objects}
  1000000    0.027    0.000    0.027    0.000 {method 'disable' of '_lsprof.Profiler' objects}


=========================
Profiling: map_
=========================
         11000000 function calls in 1.470 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
  1000000    0.264    0.000    1.442    0.000 profiling.py:14(map_)
  8000000    0.387    0.000    0.387    0.000 profiling.py:15(<lambda>)
  1000000    0.791    0.000    1.178    0.000 {built-in method builtins.sum}
  1000000    0.028    0.000    0.028    0.000 {method 'disable' of '_lsprof.Profiler' objects}


=========================
Profiling: list_comp
=========================
         4000000 function calls in 0.737 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
  1000000    0.318    0.000    0.709    0.000 profiling.py:18(list_comp)
  1000000    0.261    0.000    0.261    0.000 profiling.py:19(<listcomp>)
  1000000    0.131    0.000    0.131    0.000 {built-in method builtins.sum}
  1000000    0.027    0.000    0.027    0.000 {method 'disable' of '_lsprof.Profiler' objects}

এই প্রোগ্রামটিতে:

  • reduceএবং mapসাধারণভাবে বেশ ধীর। কেবল sumএটিই নয়, mapফিরে আসা পুনরাবৃত্তকারীগুলিতে ব্যবহার করা ধীরে ধীরেsum তালিকার
  • for_loop অ্যাপেন্ড ব্যবহার করে, যা কিছুটা হলেও ধীরে ধীরে
  • তালিকা-বোধগম্যতা কেবল তালিকা তৈরির জন্য কমপক্ষে সময় ব্যয় করে না sum, বিপরীতে , এটি আরও দ্রুততর করে তোলেmap

3

আমি @ অ্যালপিআইয়ের কয়েকটি কোড সংশোধন করতে সক্ষম হয়েছি এবং আবিষ্কার করেছি যে লুপের চেয়ে তালিকা বোধগম্যতা একটু দ্রুত। এটি এর কারণে হতে পারে int(), এটি তালিকা বোঝার এবং লুপের মধ্যে উপযুক্ত নয়।

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*next, numbers, 0)

def square_sum2(numbers):
    a = []
    for i in numbers:
        a.append(i*2)
    a = sum(a)
    return a

def square_sum3(numbers):
    sqrt = lambda x: x*x
    return sum(map(sqrt, numbers))

def square_sum4(numbers):
    return(sum([i*i 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.101122 #Reduce

0:00:00.089216 #For loop

0:00:00.101532 #Map

0:00:00.068916 #List comprehension
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.