বদলানো তালিকার অনুলিপি কেন বেশি?


90

range(10**6)দশবার পরিবর্তিত তালিকাটি অনুলিপি করাতে আমার প্রায় 0.18 সেকেন্ড সময় লাগে: (এগুলি পাঁচ রান)

0.175597017661
0.173731403198
0.178601711594
0.180330912952
0.180811964451

দশবার আনফ্ল্যাশিত তালিকাটি অনুলিপি করা আমাকে প্রায় 0.05 সেকেন্ড সময় নেয়:

0.058402235973
0.0505464636856
0.0509734306934
0.0526022752744
0.0513324916184

আমার পরীক্ষার কোডটি এখানে:

from timeit import timeit
import random

a = range(10**6)
random.shuffle(a)    # Remove this for the second test.
a = list(a)          # Just an attempt to "normalize" the list.
for _ in range(5):
    print timeit(lambda: list(a), number=10)

আমিও অনুলিপি দিয়ে চেষ্টা করেছি a[:], ফলাফলগুলি একই রকম ছিল (যেমন, বড় গতির পার্থক্য)

বড় গতির পার্থক্য কেন? আমি বিখ্যাত এবং গতির পার্থক্য জানি এবং বুঝতে পারছি কেন একটি বাছাই করা অ্যারের চেয়ে বাছাই করা অ্যারে প্রক্রিয়া করা কেন দ্রুত হয়? উদাহরণস্বরূপ, তবে এখানে আমার প্রসেসিংয়ের কোনও সিদ্ধান্ত নেই। এটি কেবল অন্ধভাবে তালিকার ভিতরে উল্লেখগুলি অনুলিপি করছে, না?

আমি উইন্ডোজ 10 এ পাইথন 2.7.12 ব্যবহার করছি।

সম্পাদনা: পাইথনকে ৩.৫.২ চেষ্টা করেও এখন ফলাফল প্রায় একই রকম (ধারাবাহিকভাবে ০.০ around সেকেন্ডের মধ্যে প্রায় 0.17 সেকেন্ডের মতো অবিচ্ছিন্নভাবে পরিবর্তন করা যায়) were এর জন্য কোডটি এখানে:

a = list(range(10**6))
random.shuffle(a)
a = list(a)
for _ in range(5):
    print(timeit(lambda: list(a), number=10))


4
দয়া করে আমার দিকে চিত্কার করবেন না, আমি আপনাকে সাহায্য করার চেষ্টা করছিলাম! অর্ডার পরিবর্তন করার পরে, আমি 0.25পরীক্ষাগুলির প্রত্যেকটির প্রতিটি পুনরাবৃত্তিতে প্রায় পাই । সুতরাং আমার প্ল্যাটফর্মে, অর্ডার ব্যাপার না।
বারাক মানোস

4
@ ওলতাহ ধন্যবাদ, তবে আমি এখন এটি পড়েছি এবং আমি একমত নই। আমি যখন কোডটি দেখলাম, তখনই আমি তাত্ক্ষণিকভাবে ক্যান্সের হিট / মিস করাগুলি সম্পর্কে চিন্তা করি যা এটি লেখকের উপসংহারও। তবে তার কোড সংখ্যাগুলি যুক্ত করে, যার জন্য তাদের তাকানো দরকার। আমার কোড না। আমার কেবল রেফারেন্সগুলি অনুলিপি করতে হবে, তাদের মাধ্যমে অ্যাক্সেস করা উচিত নয়।
স্টিফান পোচম্যান

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

4
@ নিকোলেপ্রোকপিয়েভ হ্যাঁ, আমি এটি নিয়ে উদ্বিগ্ন নই, অন্য কিছু করার সময় এটি কেবল লক্ষ্য করেছেন, এটি ব্যাখ্যা করতে পারলেন না, এবং কৌতূহলী হয়ে উঠলেন। এবং আমি খুশি যে আমি জিজ্ঞাসা করেছি এবং এখনই উত্তর
পেয়েছি

উত্তর:


100

আকর্ষণীয় বিট এটি যা পূর্ণসংখ্যা অর্ডার নির্ভর করে প্রথম সৃষ্টি করেছেন। উদাহরণস্বরূপ এর shuffleসাথে এলোমেলো ক্রম তৈরির পরিবর্তে random.randint:

from timeit import timeit
import random

a = [random.randint(0, 10**6) for _ in range(10**6)]
for _ in range(5):
    print(timeit(lambda: list(a), number=10))

এটি আপনার অনুলিপি করার মতো দ্রুত list(range(10**6))(প্রথম এবং দ্রুত উদাহরণ)।

তবে আপনি যখন এলোমেলো হন - তারপরে আপনার পূর্ণসংখ্যাগুলি সেগুলি আর তৈরির ক্রমে হয় না, এটি এটিকে ধীর করে দেয়।

একটি দ্রুত ইন্টারমেজো:

  • সমস্ত পাইথন অবজেক্ট হিমের উপরে রয়েছে, তাই প্রতিটি বস্তু একটি পয়েন্টার।
  • একটি তালিকা অনুলিপি করা অগভীর অপারেশন।
  • তবে পাইথন রেফারেন্স কাউন্টিং ব্যবহার করে তাই যখন কোনও বস্তুকে নতুন ধারক করা হয় তখন তার রেফারেন্স গণনাটি অবশ্যই বাড়ানো ( Py_INCREFইনlist_slice ) করা উচিত, সুতরাং পাইথনকে সত্যই যেখানে বস্তুটি সেখানে যেতে হবে। এটি কেবল রেফারেন্সটি অনুলিপি করতে পারে না।

সুতরাং আপনি যখন আপনার তালিকাটি অনুলিপি করবেন আপনি সেই তালিকার প্রতিটি আইটেম পাবেন এবং এটিকে নতুন তালিকায় "যেমন আছে" রাখবেন। আপনার পরবর্তী আইটেমটি যখন বর্তমানের কিছু পরে তৈরি করা হয়েছিল তখন খুব ভাল সম্ভাবনা রয়েছে (কোনও গ্যারান্টি নেই!) এটি পাশের গাদাতে সংরক্ষণ করা হয়েছে।

আসুন ধরে নেওয়া যাক যখনই আপনার কম্পিউটার ক্যাশে কোনও আইটেম লোড করে তা xপরবর্তী মেমরি আইটেমগুলি (ক্যাশে লোকালাইটি) লোড করে । তারপরে আপনার কম্পিউটার x+1একই ক্যাশে আইটেমগুলির জন্য রেফারেন্স গণনা বৃদ্ধি করতে পারে !

এলোমেলো ক্রম দিয়ে এটি এখনও পরবর্তী-ইন-মেমরি আইটেমগুলি লোড করে তবে এগুলি পরবর্তী-ইন-তালিকাগুলি নয়। সুতরাং এটি পরবর্তী আইটেমটি অনুসন্ধান না করে "সত্যই" না করে রেফারেন্স-গণনা বৃদ্ধি করতে পারে না।

টিএল; ডিআর: আসল গতি কপির আগে কী ঘটেছিল তার উপর নির্ভর করে: এই আইটেমগুলিকে কোন ক্রমে তৈরি করা হয়েছিল এবং তালিকায় এইগুলি কী ক্রমে তৈরি করা হয়েছে।


আপনি এটি দেখে যাচাই করতে পারেন id:

সিপিথন বাস্তবায়নের বিশদ: এটি মেমরির অবজেক্টের ঠিকানা।

a = list(range(10**6, 10**6+100))
for item in a:
    print(id(item))

একটি সংক্ষিপ্ত অংশ প্রদর্শন করতে:

1496489995888
1496489995920  # +32
1496489995952  # +32
1496489995984  # +32
1496489996016  # +32
1496489996048  # +32
1496489996080  # +32
1496489996112
1496489996144
1496489996176
1496489996208
1496489996240
1496507297840
1496507297872
1496507297904
1496507297936
1496507297968
1496507298000
1496507298032
1496507298064
1496507298096
1496507298128
1496507298160
1496507298192

সুতরাং এই অবজেক্টগুলি সত্যই "গাদাতে একে অপরের পাশে"। সঙ্গে shuffleতারা নয়:

import random
a = list(range(10**6, 100+10**6))
random.shuffle(a)
last = None
for item in a:
    if last is not None:
        print('diff', id(item) - id(last))
    last = item

যা দেখায় যে এগুলি স্মৃতিতে একে অপরের পাশে নেই:

diff 736
diff -64
diff -17291008
diff -128
diff 288
diff -224
diff 17292032
diff -1312
diff 1088
diff -17292384
diff 17291072
diff 608
diff -17290848
diff 17289856
diff 928
diff -672
diff 864
diff -17290816
diff -128
diff -96
diff 17291552
diff -192
diff 96
diff -17291904
diff 17291680
diff -1152
diff 896
diff -17290528
diff 17290816
diff -992
diff 448

গুরুত্বপূর্ণ তথ্য:

আমি নিজেও এ কথা ভেবে দেখিনি। বেশিরভাগ তথ্য রিকি স্টুয়ার্টের ব্লগপোস্টে পাওয়া যাবে ।

এই উত্তরটি পাইথনের "অফিসিয়াল" সিপিথন প্রয়োগের উপর ভিত্তি করে। অন্যান্য বাস্তবায়নের বিবরণগুলি (জাইথন, পিপাই, আয়রন পাইথন, ...) আলাদা হতে পারে। এটি নির্দেশ করার জন্য @ জার্গডব্লিউমিত্যাগ ধন্যবাদ ।


6
@ অগুরার একটি রেফারেন্স অনুলিপি করা অর্থ অবজেক্টের মধ্যে থাকা রেফারেন্স কাউন্টারকে বাড়িয়ে তোলা বোঝায় (সুতরাং বস্তুর অ্যাক্সেস অনিবার্য)
লিওন

4
@ স্টেফানপোচম্যান্ন অনুলিপিটি সম্পাদন করে ফাংশনটি list_sliceএবং 453 লাইনে আপনি Py_INCREF(v);কলটি দেখতে পাচ্ছেন যে হ্যাপ-বরাদ্দ হওয়া অবজেক্টটি অ্যাক্সেস করতে হবে।
এমসিফার্ট

4
@ ম্যাসিফার্ট আরও একটি ভাল পরীক্ষা ব্যবহার করছেন a = [0] * 10**7(10 ** 6 অবধি এটি খুব অস্থির ছিল) যা ব্যবহারের চেয়েও দ্রুত a = range(10**7)(প্রায় 1.25 এর একটি ফ্যাক্টর দ্বারা)। স্পষ্টতই কারণ এটি ক্যাশিংয়ের জন্য আরও ভাল।
স্টিফান পোচম্যান

4
আমি কেবল ভাবছিলাম যে কেন পাইথন bit৪ বিটের একটি 64 বিবিট কম্পিউটারে আমি 32 বিট পূর্ণসংখ্যা পেয়েছি। তবে প্রকৃতপক্ষে এটি ক্যাচিংয়ের জন্যও ভাল :-) এমনকি [0,1,2,3]*((10**6) // 4)তত দ্রুত a = [0] * 10**6। তবে 0-255 থেকে পূর্ণসংখ্যার সাথে আরও একটি বাস্তবতা আসবে: এগুলি অভ্যন্তরীণভাবে তৈরি করা হয়েছে যাতে এই ক্রমের ক্রম (আপনার স্ক্রিপ্টের অভ্যন্তরে) আর গুরুত্বপূর্ণ নয় - কারণ আপনি অজগর শুরু করার সাথে সাথে এগুলি তৈরি করা হয়েছে।
এমেসিফার্ট

4
নোট করুন যে বর্তমানে বিদ্যমান চারটি উত্পাদন-প্রস্তুত পাইথন বাস্তবায়নগুলির মধ্যে কেবল একটি রেফারেন্স গণনা ব্যবহার করে। সুতরাং, এই বিশ্লেষণটি কেবলমাত্র একক বাস্তবায়নের ক্ষেত্রে প্রযোজ্য।
জার্গ ডব্লু মিত্তাগ

24

আপনি তালিকার আইটেমগুলিকে বদলে ফেললে এগুলির রেফারেন্সের খারাপ অবস্থান হয়, এর ফলে আরও খারাপ ক্যাশে কার্য সম্পাদন হয়।

আপনি ভাবতে পারেন যে তালিকাটি অনুলিপি করা কেবলমাত্র রেফারেন্সগুলি অনুলিপি করে, বস্তুগুলি নয়, তাই গাদাতে তাদের অবস্থানগুলি গুরুত্বপূর্ণ নয়। তবে অনুলিপিটি পরিবর্তন করতে প্রতিটি অনুলিপি অ্যাক্সেস করা জড়িত cop


এটি আমার জন্য আরও ভাল উত্তর হতে পারে (কমপক্ষে যদি এটি এমসিফার্টের মতো "প্রমাণের সাথে একটি লিঙ্ক থাকে) কারণ আমি যা অনুভব করেছি এটিই খুব সংবেদনশীল, তবে আমি মনে করি আমি এমসিফার্টের সাথে লেগে থাকব যেহেতু আমার মনে হচ্ছে এটি হতে পারে অন্যদের জন্য ভাল। ধন্যবাদ, ধন্যবাদ এটি পাশাপাশি।
স্টিফান পোচম্যান

এগুলি যুক্ত করে দেবে যে পেন্টিওয়েডস, অ্যাথলমস ইত্যাদির মধ্যে ঠিকানা নিদর্শনগুলি সনাক্ত করতে তাদের মধ্যে রহস্যময় যুক্তি রয়েছে এবং তারা যখন কোনও প্যাটার্ন দেখেন তখন ডেটা প্রিফেচিং শুরু করবেন। এই ক্ষেত্রে, সংখ্যার ক্রম যখন থাকে তখন ডেটা প্রিফেচ করতে (ক্যাসি মিস করা) হ্রাস করতে পারে। এই প্রভাবটি অবশ্যই, লোকালয় থেকে হিটগুলির বর্ধিত% পর্যন্ত রয়েছে।
গ্রেগগো

5

অন্যদের দ্বারা ব্যাখ্যা হিসাবে, এটা ঠিক রেফারেন্স অনুলিপি কিন্তু না বস্তু ভিতরে রেফারেন্স গন্য বৃদ্ধি এবং এইভাবে বস্তু হয় অ্যাক্সেস এবং ক্যাশে একটি ভূমিকা পালন করে।

এখানে আমি আরও পরীক্ষা-নিরীক্ষা যুক্ত করতে চাই। শাফলযুক্ত বনাম আনস্ফুল্ড সম্পর্কে তেমন কিছু নয় (যেখানে কোনও উপাদান অ্যাক্সেস করা ক্যাশে মিস করতে পারে তবে নিম্নলিখিত উপাদানগুলিকে ক্যাশে প্রবেশ করতে পারে যাতে তারা আঘাত পায়)। তবে উপাদানগুলির পুনরাবৃত্তি সম্পর্কে যেখানে পরবর্তী সময়ে একই উপাদানটির অ্যাক্সেসগুলি ক্যাশে আঘাত করতে পারে কারণ উপাদানটি এখনও ক্যাশে রয়েছে।

একটি সাধারণ ব্যাপ্তি পরীক্ষা করা:

>>> from timeit import timeit
>>> a = range(10**7)
>>> [timeit(lambda: list(a), number=100) for _ in range(3)]
[5.1915339142808925, 5.1436351868889645, 5.18055115701749]

একই আকারের একটি তালিকা কিন্তু কেবল একটি উপাদান দিয়ে বারবার পুনরাবৃত্তি করা দ্রুত হয় কারণ এটি সর্বদা ক্যাশে আঘাত করে:

>>> a = [0] * 10**7
>>> [timeit(lambda: list(a), number=100) for _ in range(3)]
[4.125743135926939, 4.128927210087596, 4.0941229388550795]

এটি কোন সংখ্যাটি বলে মনে হচ্ছে না:

>>> a = [1234567] * 10**7
>>> [timeit(lambda: list(a), number=100) for _ in range(3)]
[4.124106479141709, 4.156590225249886, 4.219242600790949]

মজার বিষয় হল, আমি পরিবর্তে একই দুটি বা চারটি উপাদানের পুনরাবৃত্তি করলে এটি আরও দ্রুত হয়:

>>> a = [0, 1] * (10**7 / 2)
>>> [timeit(lambda: list(a), number=100) for _ in range(3)]
[3.130586101607932, 3.1001001764957294, 3.1318465707127814]

>>> a = [0, 1, 2, 3] * (10**7 / 4)
>>> [timeit(lambda: list(a), number=100) for _ in range(3)]
[3.096105435911994, 3.127148431279352, 3.132872673690855]

আমি অনুমান করি যে কোনও কিছুতে একই সময় একই পরিমাণ বাড়ানো পছন্দ হয় না। হয়ত কিছু পাইপলাইন স্টল কারণ প্রতিটি বৃদ্ধি পূর্ববর্তী বৃদ্ধির ফলাফলের জন্য অপেক্ষা করতে হয় তবে এটি একটি বুনো অনুমান।

যাইহোক, পুনরাবৃত্ত উপাদানগুলির বৃহত্তর সংখ্যার জন্য এটি চেষ্টা করে:

from timeit import timeit
for e in range(26):
    n = 2**e
    a = range(n) * (2**25 / n)
    times = [timeit(lambda: list(a), number=20) for _ in range(3)]
    print '%8d ' % n, '  '.join('%.3f' % t for t in times), ' => ', sum(times) / 3

আউটপুট (প্রথম কলামটি বিভিন্ন উপাদানের সংখ্যা, প্রত্যেকটির জন্য আমি তিনবার পরীক্ষা করি এবং তারপরেও গড় গ্রহণ করি):

       1  2.871  2.828  2.835  =>  2.84446732686
       2  2.144  2.097  2.157  =>  2.13275338734
       4  2.129  2.297  2.247  =>  2.22436720645
       8  2.151  2.174  2.170  =>  2.16477771575
      16  2.164  2.159  2.167  =>  2.16328197911
      32  2.102  2.117  2.154  =>  2.12437970598
      64  2.145  2.133  2.126  =>  2.13462250728
     128  2.135  2.122  2.137  =>  2.13145065221
     256  2.136  2.124  2.140  =>  2.13336283943
     512  2.140  2.188  2.179  =>  2.1688431668
    1024  2.162  2.158  2.167  =>  2.16208440826
    2048  2.207  2.176  2.213  =>  2.19829998424
    4096  2.180  2.196  2.202  =>  2.19291917834
    8192  2.173  2.215  2.188  =>  2.19207065277
   16384  2.258  2.232  2.249  =>  2.24609975704
   32768  2.262  2.251  2.274  =>  2.26239771771
   65536  2.298  2.264  2.246  =>  2.26917420394
  131072  2.285  2.266  2.313  =>  2.28767871168
  262144  2.351  2.333  2.366  =>  2.35030805124
  524288  2.932  2.816  2.834  =>  2.86047313113
 1048576  3.312  3.343  3.326  =>  3.32721167007
 2097152  3.461  3.451  3.547  =>  3.48622758473
 4194304  3.479  3.503  3.547  =>  3.50964316455
 8388608  3.733  3.496  3.532  =>  3.58716466865
16777216  3.583  3.522  3.569  =>  3.55790996695
33554432  3.550  3.556  3.512  =>  3.53952594744

সুতরাং একটি একক (পুনরাবৃত্ত) উপাদানগুলির জন্য প্রায় 2.8 সেকেন্ড থেকে এটি 2, 4, 8, 16, ... বিভিন্ন উপাদানগুলির জন্য প্রায় 2.2 সেকেন্ডে নেমে আসে এবং এক লক্ষ হাজার অবধি প্রায় ২.২ সেকেন্ডে অবস্থান করে। আমি মনে করি এটি আমার এল 2 ক্যাশে ব্যবহার করে (4 × 256 কেবি, আমার একটি আই 7-6700 রয়েছে )।

তারপরে কয়েক ধাপে সময়গুলি 3.5 সেকেন্ডে চলে যায়। আমি মনে করি এটির "ক্লান্ত" না হওয়া অবধি এটি আমার L2 ক্যাশে এবং আমার L3 ক্যাশে (8 এমবি) মিশ্রণ ব্যবহার করবে।

শেষে এটি প্রায় 3.5 সেকেন্ডে স্থিত হয়, আমি অনুমান করি কারণ আমার ক্যাশেগুলি পুনরাবৃত্ত উপাদানগুলির সাথে আর সহায়তা করে না।


0

এলোমেলো হওয়ার আগে, যখন গাদাতে বরাদ্দ দেওয়া হয়, তখন সংলগ্ন সূচক বস্তুগুলি মেমরিতে সংলগ্ন থাকে এবং অ্যাক্সেস করা হলে মেমরির হিট রেট বেশি থাকে; বদলানোর পরে, নতুন তালিকার সংলগ্ন সূচকের অবজেক্টটি স্মৃতিতে নেই। সংলগ্ন, হিট রেট খুব খারাপ।

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