[] তালিকার চেয়ে দ্রুত () কেন?


706

আমি সম্প্রতি প্রক্রিয়াকরণ গতি তুলনা []এবং list()এবং যে আবিষ্কার বিস্মিত []রানে দ্রুত তিনবার চেয়ে বেশি চেয়ে list()। আমি সঙ্গে একই পরীক্ষা দৌড়ে {}এবং dict()এবং ফলাফল কার্যত অভিন্ন ছিল: []এবং {}উভয় প্রায় 0.128sec / মিলিয়ন চক্র নেন, যখন list()এবং dict()মোটামুটিভাবে 0.428sec / মিলিয়ন চক্র নেন।

কেন? কি []এবং {}(এবং সম্ভবত ()এবং '', অত্যধিক) অবিলম্বে ফিরে কিছু খালি স্টক আক্ষরিক একটি কপি পাস তাদের স্পষ্টভাবে নামক প্রতিরূপ (যখন list(), dict(), tuple(), str()) সম্পূর্ণরূপে একটি বস্তু তৈরি সম্পর্কে যান, হোক বা না হোক তারা আসলে উপাদান আছে?

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

আমি যথাসময়ে কল timeit.timeit("[]")এবং timeit.timeit("list()")এবং timeit.timeit("{}")এবং timeit.timeit("dict()")তালিকা এবং অভিধানের তুলনা করে আমার সময়সীমার ফলাফল পেয়েছি । আমি পাইথন 2.7.9 চালাচ্ছি।

আমি সম্প্রতি আবিষ্কৃত " কেন যদি সত্যতে ধীর চেয়ে যদি 1? " যে কর্মক্ষমতা তুলনা if Trueকরার if 1এবং এর স্পর্শ বলে মনে হয় একইরকম আক্ষরিক-বনাম-বিশ্বব্যাপী দৃশ্যকল্প; সম্ভবত এটি পাশাপাশি বিবেচনা করাও মূল্যবান।


2
দ্রষ্টব্য: ()এবং ''বিশেষ, কারণ এগুলি কেবল খালি নয়, তারা পরিবর্তনযোগ্য এবং এগুলি হিসাবে, তাদের একক করে তোলা এটি একটি সহজ জয়; তারা এমনকি নতুন অবজেক্টগুলি তৈরি করে না, খালি tuple/ একা জন্য সিঙ্গলটন লোড করে str। টেকনিক্যালি একটি বাস্তবায়ন বিস্তারিত, কিন্তু আমি একটি কঠিন সময় কল্পী কেন তারা আছে না খালি ক্যাশে tuple/ strকর্মক্ষমতা কারণে। সুতরাং একটি স্টক আক্ষরিক সম্পর্কে আপনার অনুভূতি []এবং {}ফিরে পাস ভুল ছিল, কিন্তু এটি প্রযোজ্য ()এবং ''
শ্যাডোর্যাঞ্জার

2
এছাড়াও সম্পর্কিত: কল করার চেয়ে দ্রুত কেন ? {}set()
cs95

উত্তর:


757

কারণ []এবং {}হয় আক্ষরিক সিনট্যাক্স । পাইথন কেবল তালিকা বা অভিধানের বিষয়গুলি তৈরি করতে বাইটকোড তৈরি করতে পারে:

>>> import dis
>>> dis.dis(compile('[]', '', 'eval'))
  1           0 BUILD_LIST               0
              3 RETURN_VALUE        
>>> dis.dis(compile('{}', '', 'eval'))
  1           0 BUILD_MAP                0
              3 RETURN_VALUE        

list()এবং dict()পৃথক বস্তু হয়। তাদের নামগুলি সমাধান করা দরকার, যুক্তিগুলি ঠেকাতে স্ট্যাকটি জড়িত থাকতে হবে, ফ্রেমটি পরে পুনরুদ্ধার করতে সঞ্চয় করতে হবে এবং একটি কল করতে হবে। যে সব আরও সময় নেয়।

খালি ক্ষেত্রে, তার মানে আপনার খুব কমপক্ষে একটি LOAD_NAME(যা গ্লোবাল নেমস্পেসের পাশাপাশি __builtin__মডিউলটি অনুসন্ধান করতে হবে ) এর পরে ক CALL_FUNCTION, যা বর্তমান ফ্রেমটি সংরক্ষণ করতে হবে:

>>> dis.dis(compile('list()', '', 'eval'))
  1           0 LOAD_NAME                0 (list)
              3 CALL_FUNCTION            0
              6 RETURN_VALUE        
>>> dis.dis(compile('dict()', '', 'eval'))
  1           0 LOAD_NAME                0 (dict)
              3 CALL_FUNCTION            0
              6 RETURN_VALUE        

আপনি নামের সাথে আলাদাভাবে সময়টির সাথে সময় করতে পারেন timeit:

>>> import timeit
>>> timeit.timeit('list', number=10**7)
0.30749011039733887
>>> timeit.timeit('dict', number=10**7)
0.4215109348297119

সময়ের তাত্পর্য সম্ভবত একটি অভিধান হ্যাশ সংঘাত আছে। সেই বিষয়গুলিকে কল করার জন্য সময় থেকে সেই সময়গুলি বিয়োগ করুন এবং আক্ষরিক ব্যবহারের জন্য ফলাফলের সাথে সময়ের তুলনা করুন:

>>> timeit.timeit('[]', number=10**7)
0.30478692054748535
>>> timeit.timeit('{}', number=10**7)
0.31482696533203125
>>> timeit.timeit('list()', number=10**7)
0.9991960525512695
>>> timeit.timeit('dict()', number=10**7)
1.0200958251953125

সুতরাং অবজেক্টটি কল করতে 1.00 - 0.31 - 0.30 == 0.3910 মিলিয়ন কল প্রতি অতিরিক্ত সেকেন্ড সময় নেয় ।

স্থানীয় হিসাবে বিশ্বব্যাপী নামগুলি বাদ দিয়ে আপনি বিশ্বব্যাপী অনুসন্ধানের ব্যয়টি এড়াতে পারবেন (একটি timeitসেটআপ ব্যবহার করে , আপনি নামের সাথে আবদ্ধ সমস্ত কিছু স্থানীয়):

>>> timeit.timeit('_list', '_list = list', number=10**7)
0.1866450309753418
>>> timeit.timeit('_dict', '_dict = dict', number=10**7)
0.19016098976135254
>>> timeit.timeit('_list()', '_list = list', number=10**7)
0.841480016708374
>>> timeit.timeit('_dict()', '_dict = dict', number=10**7)
0.7233691215515137

তবে আপনি কখনই এই ব্যয়টি কাটিয়ে উঠতে পারবেন না CALL_FUNCTION


150

list()একটি গ্লোবাল সন্ধান এবং একটি ফাংশন কল প্রয়োজন তবে []একটি একক নির্দেশনায় সংকলিত। দেখা:

Python 2.7.3
>>> import dis
>>> print dis.dis(lambda: list())
  1           0 LOAD_GLOBAL              0 (list)
              3 CALL_FUNCTION            0
              6 RETURN_VALUE        
None
>>> print dis.dis(lambda: [])
  1           0 BUILD_LIST               0
              3 RETURN_VALUE        
None

75

কারণ কোনও স্ট্রিংকে তালিকার একটি বস্তুতে রূপান্তর করার listজন্য একটি ফাংশন , যখন []ব্যাট থেকে একটি তালিকা তৈরি করতে ব্যবহৃত হয়। এটি চেষ্টা করুন (আপনাকে আরও বোঝাতে পারে):

x = "wham bam"
a = list(x)
>>> a
["w", "h", "a", "m", ...]

যদিও

y = ["wham bam"]
>>> y
["wham bam"]

আপনি এতে যা কিছু রেখেছেন সেগুলি আপনাকে একটি আসল তালিকা দেয়।


7
এটি সরাসরি প্রশ্নের সমাধান করে না। প্রশ্ন কেন সম্পর্কে ছিল []চেয়ে দ্রুত list()না কেন ['wham bam']দ্রুত চেয়ে list('wham bam')
জেরেমি ভিজার 0

2
@ জেরেমিভিজার আমার কাছে কিছুটা বোধগম্য হয়নি কারণ []/ list()ঠিক একইরকম ['wham']/ list('wham')কারণ তাদের গণিতের 1000/10মতো একই রকমের পার্থক্য রয়েছে 100/1। আপনি তত্ত্বগতভাবে দূরে নিতে পারেন wham bamএবং সত্য এখনও একই হতে পারে, যে list()কোনও ফাংশন নাম কল করে কিছু রূপান্তর করার চেষ্টা করে যখন []সরাসরি পরিবর্তনশীল রূপান্তরিত হবে। ফাংশন কলগুলি পৃথক হ্যাঁ, এটি সমস্যাটির কেবলমাত্র যৌক্তিক ওভারভিউ হিসাবে উদাহরণস্বরূপ কোনও সংস্থার একটি নেটওয়ার্ক মানচিত্রও সমাধান / সমস্যার যৌক্তিক। আপনি চান ভোট দিন।
6:55

বিপরীতে @ জেরেমিভিজার, এটি দেখায় যে তারা সামগ্রীতে বিভিন্ন অপারেশন করে।
বাল্ড্রিক

20

এখানে উত্তরগুলি দুর্দান্ত, এই প্রশ্নটি পুরোপুরি coverেকে রাখে। আগ্রহীদের জন্য আমি বাইট-কোড থেকে আরও একটি পদক্ষেপ নেব। আমি সিপিথনের অতি সাম্প্রতিক রেপো ব্যবহার করছি; পুরানো সংস্করণগুলি এ ক্ষেত্রে একই রকম আচরণ করে তবে সামান্য পরিবর্তন সম্ভবত হতে পারে।

এখানে প্রতিটি BUILD_LISTজন্য []এবং এর CALL_FUNCTIONজন্য মৃত্যুদন্ড কার্যকর করা হবে list()


BUILD_LISTনির্দেশ:

আপনার কেবল ভয়াবহতা দেখা উচিত:

PyObject *list =  PyList_New(oparg);
if (list == NULL)
    goto error;
while (--oparg >= 0) {
    PyObject *item = POP();
    PyList_SET_ITEM(list, oparg, item);
}
PUSH(list);
DISPATCH();

মারাত্মকভাবে সংশ্লেষিত, আমি জানি। এটি কতটা সহজ:

  • একটি নতুন তালিকা তৈরি করুন PyList_New(এটি মূলত একটি নতুন তালিকার জন্য মেমরির বরাদ্দ করে), opargস্ট্যাকের উপর আর্গুমেন্টের সংকেত দেয়। সোজা কথা।
  • পরীক্ষা করে দেখুন যে কিছুতেই ভুল হয়নি if (list==NULL)
  • স্ট্যাকের সাথে থাকা কোনও যুক্তি (আমাদের ক্ষেত্রে এটি কার্যকর করা হয় না) যুক্ত করুন PyList_SET_ITEM(একটি ম্যাক্রো) ) যুক্ত করুন।

এটা দ্রুত যে আশ্চর্যজনক নয়! এটি নতুন তালিকা তৈরির জন্য কাস্টম-ইন, আর কিছুই নয় :-)

দ্য CALL_FUNCTIONনির্দেশ:

আপনি কোড হ্যান্ডলিংতে উঁকি দেওয়ার সময় এখানে প্রথম জিনিসটি দেখুন CALL_FUNCTION:

PyObject **sp, *res;
sp = stack_pointer;
res = call_function(&sp, oparg, NULL);
stack_pointer = sp;
PUSH(res);
if (res == NULL) {
    goto error;
}
DISPATCH();

বেশ নিরীহ দেখাচ্ছে, তাই না? ঠিক আছে, না, দুর্ভাগ্যক্রমে না, call_functionকোনও সরল লোক নয় যে এটি সঙ্গে সঙ্গে ফাংশনটি কল করবে, এটি পারে না, পরিবর্তে, এটি স্ট্যাক থেকে বস্তুকে আঁকড়ে ধরে, স্ট্যাকের সমস্ত আর্গুমেন্ট ধরে এবং তারপরে বস্তুর প্রকারের উপর ভিত্তি করে স্যুইচ করে; এটি একটি:

  • PyCFunction_Type? না, তা না হয় list, listটাইপ নয়PyCFunction
  • PyMethodType? নাহ, আগেরটি দেখুন।
  • PyFunctionType? নাপি, আগেরটি দেখুন।

আমরা আহ্বান করছি listধরন, যুক্তি প্রেরণ call_functionকরা হয় PyList_Type। সিপিথনকে এখন যে কোনও কলযোগ্য বস্তু হ্যান্ডেল করার জন্য জেনেরিক ফাংশনটি কল করতে হবে_PyObject_FastCallKeywords , ইয়া আরও ফাংশন কলগুলি ফাংশনটি কল করতে হবে।

এই ফাংশনটি আবার কিছু নির্দিষ্ট ক্রিয়াকলাপের জন্য কিছু পরীক্ষা করে তোলে (যা আমি বুঝতে পারি না কেন) এবং তারপরে, কাওয়ার্গসের জন্য ডিক তৈরি করার পরে , প্রয়োজনে কল করা যায় _PyObject_FastCallDict

_PyObject_FastCallDictঅবশেষে আমাদের কোথাও পায়! করণ পরে আরও বেশি চেক এটা grabs tp_callথেকে স্লটtype এর typeআমরা পাস করেছি, যে, এটা grabs type.tp_call। এটি এর পরে পাস হওয়া আর্গুমেন্টগুলির মধ্যে একটি টিউপল তৈরি করতে এগিয়ে যায় _PyStack_AsTupleএবং অবশেষে, একটি কল শেষ পর্যন্ত করা যায় !

tp_call, যা মেলে type.__call__এবং শেষ অবধি তালিকা অবজেক্ট তৈরি করে। এটি সেই তালিকাগুলিকে কল করে __new__যা এর সাথে PyType_GenericNewমেমরির জন্য বরাদ্দ করে PyType_GenericAlloc: এটি আসলে এটিই সেই অংশ যা PyList_Newশেষ পর্যন্ত এটির সাথে । পূর্ববর্তী সমস্তগুলি জেনেরিক ফ্যাশনে অবজেক্টগুলি পরিচালনা করতে প্রয়োজনীয়।

শেষ পর্যন্ত, type_callকলগুলি পাওয়া যায় list.__init__এবং যে কোনও উপলভ্য যুক্তি দিয়ে তালিকাটি সূচনা করে, তারপরে আমরা যেভাবে ফিরে এসেছি সেদিকে ফিরে আসি। :-)

অবশেষে, স্মরণ করিয়ে দিন LOAD_NAME, সে এখানে অন্যরকম অবদান রাখে।


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

এটি হ'ল এখানে list()অনেক কিছুই হ্রাস পায়: হ্যাকটি কী করা উচিত তা অনুসন্ধানের জন্য পাইথন অন্বেষণ করতে হবে।

অন্যদিকে আক্ষরিক সিনট্যাক্সের অর্থ হ'ল এক জিনিস; এটি পরিবর্তন করা যায় না এবং সর্বদা পূর্ব নির্ধারিত পদ্ধতিতে আচরণ করে।

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


13

এর []চেয়ে দ্রুত কেন list()?

বৃহত্তম কারণ হ'ল পাইথন list()কেবলমাত্র একটি ব্যবহারকারী-সংজ্ঞায়িত ফাংশনটির মতো আচরণ করে যার অর্থ আপনি অন্য কোনও কিছুকে এ্যালাইজ করে এটিকে বাধা দিতে পারেনlist কিছু আলাদা করতে পারেন (যেমন নিজের নিজস্ব সাবক্ল্যাসড তালিকা বা সম্ভবত কোনও ডীক ব্যবহার করুন)।

এটি সঙ্গে সঙ্গে বিল্টিন তালিকার একটি নতুন উদাহরণ তৈরি করে []

আমার ব্যাখ্যা আপনাকে এর জন্য অন্তর্দৃষ্টি দিতে চায়।

ব্যাখ্যা

[] সাধারণত আক্ষরিক বাক্য গঠন হিসাবে পরিচিত।

ব্যাকরণে, এটি একটি "তালিকা প্রদর্শন" হিসাবে উল্লেখ করা হয়। ডক্স থেকে :

একটি তালিকা প্রদর্শন হ'ল বর্গাকার বন্ধনীগুলিতে আবদ্ধ একটি সম্ভাব্য শৃঙ্খলা সিরিজ:

list_display ::=  "[" [starred_list | comprehension] "]"

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

সংক্ষেপে, এর অর্থ হ'ল একটি বিল্টিন অবজেক্ট টাইপ listতৈরি করা হয়েছে।

এটির কোনও ছাঁটাই নেই - যার অর্থ পাইথন যত তাড়াতাড়ি সম্ভব এটি করতে পারে।

অন্যদিকে, বিল্টিন তালিকা নির্মাণকারী ব্যবহার করে বিল্টিন list()তৈরি করা থেকে বিরত থাকতে পারে list

উদাহরণস্বরূপ, বলুন আমরা চাই আমাদের তালিকাটি শোরগোলের সাথে তৈরি করা হোক:

class List(list):
    def __init__(self, iterable=None):
        if iterable is None:
            super().__init__()
        else:
            super().__init__(iterable)
        print('List initialized.')

এরপরে আমরা listমডিউল স্তরের বৈশ্বিক listস্কোপটিতে নামটি আটকাতে পারি এবং তারপরে যখন আমরা একটি তৈরি করি, আমরা আসলে আমাদের সাব-টাইপ তালিকা তৈরি করি:

>>> list = List
>>> a_list = list()
List initialized.
>>> type(a_list)
<class '__main__.List'>

একইভাবে আমরা এটি বিশ্বব্যাপী নেমস্পেস থেকে সরিয়ে ফেলতে পারি

del list

এবং এটি বিল্টিন নেমস্পেসে রাখুন:

import builtins
builtins.list = List

এবং এখন:

>>> list_0 = list()
List initialized.
>>> type(list_0)
<class '__main__.List'>

এবং নোট করুন যে তালিকা প্রদর্শনটি নিঃশর্তভাবে একটি তালিকা তৈরি করে:

>>> list_1 = []
>>> type(list_1)
<class 'list'>

আমরা সম্ভবত এটি অস্থায়ীভাবে করি, সুতরাং আমাদের পরিবর্তনগুলি পূর্বাবস্থায় ফেলা যাক - প্রথমে Listবিল্টিনগুলি থেকে নতুন অবজেক্টটি সরিয়ে দিন:

>>> del builtins.list
>>> builtins.list
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'builtins' has no attribute 'list'
>>> list()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'list' is not defined

ওহ, না, আমরা আসলটির ট্র্যাক হারিয়ে ফেলেছি।

চিন্তা করার দরকার নেই, আমরা এখনও পেতে পারি list- এটি তালিকার আক্ষরিক তালিকার ধরণ:

>>> builtins.list = type([])
>>> list()
[]

তাই ...

এর []চেয়ে দ্রুত কেন list()?

যেমনটি আমরা দেখেছি - আমরা ওভাররাইট করতে পারি list- তবে আমরা আক্ষরিক ধরণের তৈরিতে বাধা দিতে পারি না। যখন আমরা ব্যবহার listকরি তখন আমাদের কিছু আছে কিনা তা দেখার জন্য আমাদের অনুসন্ধান করতে হবে।

তারপরে আমরা যা কলযোগ্য তা কল করতে হবে। ব্যাকরণ থেকে:

কল একটি কলযোগ্য বস্তুকে কল করে (উদাহরণস্বরূপ, একটি ফাংশন) সম্ভবত খালি সিরিজের যুক্তিগুলির সাথে:

call                 ::=  primary "(" [argument_list [","] | comprehension] ")"

আমরা দেখতে পাচ্ছি যে এটি কেবল নামের জন্য নয়, কোনও নামের জন্য একই কাজ করে:

>>> import dis
>>> dis.dis('list()')
  1           0 LOAD_NAME                0 (list)
              2 CALL_FUNCTION            0
              4 RETURN_VALUE
>>> dis.dis('doesnotexist()')
  1           0 LOAD_NAME                0 (doesnotexist)
              2 CALL_FUNCTION            0
              4 RETURN_VALUE

জন্য []সেখানে পাইথন বাইটকোড পর্যায়ে কোন ফাংশন কল হল:

>>> dis.dis('[]')
  1           0 BUILD_LIST               0
              2 RETURN_VALUE

এটি কেবল বাইকোড স্তরে কোনও লুকআপ বা কল ছাড়াই তালিকাটি তৈরি করতে সরাসরি চলে যায়।

উপসংহার

আমরা প্রমাণ করে দিয়েছি যে listস্কোপিংয়ের নিয়ম ব্যবহার করে ব্যবহারকারীর কোডের সাথে বাধা দেওয়া যেতে পারে এবং এটি list()একটি কলযোগ্য অনুসন্ধান করে এবং তারপরে এটিকে কল করে।

যদিও []একটি তালিকা প্রদর্শন, বা একটি আক্ষরিক, এবং এইভাবে নাম অনুসন্ধান এবং ফাংশন কল এড়ানো হয়।


2
আপনি হাইজ্যাক করতে পারেন listএবং পাইথন সংকলকটি সত্যই এটি একটি খালি তালিকা ফেরত দেবে কিনা তা নিশ্চিত হতে পারে না এমন নির্দেশ করার জন্য +1 ।
গরুর মাংস 21
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.