পাইথন কোড একটি ফাংশনে দ্রুত চালায় কেন?


832
def main():
    for i in xrange(10**8):
        pass
main()

পাইথনের কোডের এই অংশটি এতে চালিত হয় (দ্রষ্টব্য: লিনাক্সে BASH এ সময় ফাংশনটি দিয়ে সময় নির্ধারণ করা হয়))

real    0m1.841s
user    0m1.828s
sys     0m0.012s

তবে, যদি কোনও ফাংশনের মধ্যে লুপটি স্থাপন না করা হয়,

for i in xrange(10**8):
    pass

তারপরে এটি দীর্ঘ সময় ধরে চলে:

real    0m4.543s
user    0m4.524s
sys     0m0.012s

কেন?


16
আপনি প্রকৃতপক্ষে সময়টি কীভাবে করেছেন?
অ্যান্ড্রু জাফি

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

4
পছন্দ করেছেন চলমান সময়কে দৃশ্যমানভাবে প্রভাবিত না করে 200 কে ডামি ভেরিয়েবলগুলি স্কোপটিতে সংজ্ঞায়িত করে।
ডিস্টান

2
অ্যালেক্স মার্টেলি এই স্ট্যাকওভারফ্লো.com
জন লা রুই

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

উত্তর:


531

আপনি জিজ্ঞাসা করতে পারেন কেন গ্লোবালের তুলনায় স্থানীয় ভেরিয়েবলগুলি সংরক্ষণ করা দ্রুত। এটি সিপিথন বাস্তবায়ন বিশদ।

মনে রাখবেন সিপিথন বাইকোডে সংকলিত হয়েছে, যা দোভাষী দৌড়ায়। যখন কোনও ফাংশন সংকলিত হয়, স্থানীয় ভেরিয়েবলগুলি একটি নির্দিষ্ট আকারের অ্যারেতে সংরক্ষণ করা হয় ( একটি নয়)dict ) এ সংরক্ষণ করা হয় এবং ভেরিয়েবলের নামগুলি সূচকগুলিতে বরাদ্দ করা হয়। এটি সম্ভব কারণ আপনি কোনও ক্রিয়ায় গতিশীলভাবে স্থানীয় ভেরিয়েবলগুলি যুক্ত করতে পারবেন না। তারপরে একটি স্থানীয় ভেরিয়েবল পুনরুদ্ধার হ'ল আক্ষরিক অর্থে তালিকার দিকে একটি পয়েন্টার লুক এবং এটিতে PyObjectতুচ্ছ হিসাবে একটি রিফাউন্ট বৃদ্ধি ।

এটি একটি বিশ্বব্যাপী অনুসন্ধানের সাথে বিপরীতে করুন ( LOAD_GLOBAL), যা dictহ্যাশ এবং এর সাথে জড়িত একটি সত্য অনুসন্ধান। ঘটনাচক্রে, এ কারণেই আপনাকে নির্দিষ্ট করা দরকারglobal i এটি বিশ্বব্যাপী হতে চান কিনা করতে হবে: আপনি যদি কখনও কোনও স্কোপের অভ্যন্তরে কোনও ভেরিয়েবলকে অর্পণ করেন তবে কম্পাইলারটি STORE_FASTঅ্যাক্সেসের জন্য এটি প্রদান করবে যদি না আপনি এটি না জানান।

যাইহোক, বিশ্বব্যাপী অনুসন্ধানগুলি এখনও বেশ অনুকূলিতাপূর্ণ। অ্যাট্রিবিউট লুক- foo.barহয় সত্যিই ধীর বেশী!

স্থানীয় পরিবর্তনশীল দক্ষতার উপর এখানে ছোট চিত্র রয়েছে


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

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

2
@ ওয়াকার্নিও এখানে যে প্রাথমিক কথোপকথন চলছে তা হ'ল ফাংশনটির মধ্যে স্থানীয় ভেরিয়েবল লুকআপ এবং মডিউল স্তরে সংজ্ঞায়িত গ্লোবাল ভেরিয়েবল লুপ اپের মধ্যে তুলনা। যদি আপনি এই উত্তরের আপনার মূল মন্তব্যে জবাবদিহি করেন তবে আপনি বলেছিলেন "আমি ভেবে দেখতাম না যে বিশ্বব্যাপী ভেরিয়েবল লুকআপগুলি স্থানীয় ভেরিয়েবল সম্পত্তি অনুসন্ধানের চেয়ে দ্রুত হয়" " এবং তারা না। ক্যাটরিল্লেক্স বলেছে যে, স্থানীয় পরিবর্তনশীল অনুসন্ধানগুলি বিশ্বব্যাপী তুলনায় দ্রুত, এমনকি গ্লোবালগুলিও বৈশিষ্ট্যযুক্ত অনুসন্ধানের তুলনায় বেশ অনুকূল এবং দ্রুততর (যা আলাদা)। এই মন্তব্যে আমার আরও যথেষ্ট জায়গা নেই।
জেরেমি প্রাইডমোরে

3
@ ওয়াকার্নিও foo.bar একটি স্থানীয় অ্যাক্সেস নয়। এটি কোনও বস্তুর বৈশিষ্ট্য। (বিন্যাসের অভাবকে ক্ষমা করুন) def foo_func: x = 5, xকোনও ফাংশনের স্থানীয়। অ্যাক্সেস xস্থানীয়। foo = SomeClass(), foo.barবৈশিষ্ট্য অ্যাক্সেস হয়। val = 5গ্লোবাল বিশ্বব্যাপী। আমি এখানে যা পড়েছি তার অনুসারে গতি স্থানীয়> গ্লোবাল> অ্যাট্রিবিউট। সুতরাং অ্যাক্সেস xমধ্যে foo_funcদ্রুততম হয়, দ্বারা অনুসরণ val, দ্বারা অনুসরণ foo.barfoo.attrস্থানীয় অনুসন্ধান নয় কারণ এই কনভভের প্রসঙ্গে আমরা স্থানীয় ফাংশনগুলির সাথে কথা বলছি যা একটি ফাংশনের সাথে সম্পর্কিত একটি ভেরিয়েবলের অনুসন্ধান।
জেরেমি প্রাইডমোর

3
@ থেডোক্টারের globals()ফাংশনটি একবার দেখুন । আপনি যদি এর চেয়ে আরও বেশি তথ্য চান তবে আপনাকে পাইথনের সোর্স কোডটি সন্ধান করতে হতে পারে। পাইথন হ'ল পাইথনটির সাধারণ প্রয়োগের জন্য কেবল নাম - তাই আপনি সম্ভবত এটি ইতিমধ্যে ব্যবহার করছেন!
ক্যাট্রিয়েল

660

একটি ফাংশনের অভ্যন্তরে, বাইটকোডটি হ'ল:

  2           0 SETUP_LOOP              20 (to 23)
              3 LOAD_GLOBAL              0 (xrange)
              6 LOAD_CONST               3 (100000000)
              9 CALL_FUNCTION            1
             12 GET_ITER            
        >>   13 FOR_ITER                 6 (to 22)
             16 STORE_FAST               0 (i)

  3          19 JUMP_ABSOLUTE           13
        >>   22 POP_BLOCK           
        >>   23 LOAD_CONST               0 (None)
             26 RETURN_VALUE        

শীর্ষ স্তরে, বাইটকোডটি হ'ল:

  1           0 SETUP_LOOP              20 (to 23)
              3 LOAD_NAME                0 (xrange)
              6 LOAD_CONST               3 (100000000)
              9 CALL_FUNCTION            1
             12 GET_ITER            
        >>   13 FOR_ITER                 6 (to 22)
             16 STORE_NAME               1 (i)

  2          19 JUMP_ABSOLUTE           13
        >>   22 POP_BLOCK           
        >>   23 LOAD_CONST               2 (None)
             26 RETURN_VALUE        

পার্থক্যটি STORE_FASTএটির চেয়ে দ্রুত (!) STORE_NAME। এটি কারণ কোনও ফাংশনে, iস্থানীয় তবে শীর্ষ স্তরে এটি একটি বিশ্বব্যাপী।

বাইটকোড পরীক্ষা করতে, disমডিউলটি ব্যবহার করুন । আমি সরাসরি ফাংশন অবতরণ করা, কিন্তু toplevel কোড আমি ব্যবহার করতে হয়েছে অবতরণ করতে সক্ষম হন compilebuiltin


171
পরীক্ষার মাধ্যমে নিশ্চিত। ঢোকানো global iমধ্যে mainফাংশন চলমান বার সমতুল্য করে তোলে।
ডিস্টান

44
এটি প্রশ্নের উত্তর না দিয়েই প্রশ্নের উত্তর দেয় :) স্থানীয় ফাংশন ভেরিয়েবলের ক্ষেত্রে, সিপিথন আসলে এগুলি একটি টিপলে (যা সি কোড থেকে পরিবর্তনযোগ্য) সংরক্ষণ করে থাকে যতক্ষণ না কোনও অভিধানের অনুরোধ করা হয় (যেমন মাধ্যমে locals(), বা inspect.getframe()ইত্যাদি)। ধ্রুবক পূর্ণসংখ্যার দ্বারা একটি অ্যারের উপাদান অনুসন্ধান করা ডিক অনুসন্ধানের চেয়ে অনেক দ্রুত is
dmw

3
সি / সি ++ এর সাথেও এটি একই, বৈশ্বিক ভেরিয়েবলগুলি ব্যবহার করা উল্লেখযোগ্য মন্দার কারণ ঘটায়
কোডজ্যামার

3
এটি আমি প্রথম বাইটোকোডে দেখেছি .. কেউ কীভাবে এটি দেখে এবং এটি জেনে রাখা গুরুত্বপূর্ণ?
জ্যাক

4
@gkimsey আমি সম্মত কেবল দুটি জিনিস ভাগ করে নিতে চেয়েছিলাম i) অন্যান্য প্রোগ্রামিং ভাষায় এই আচরণটি লক্ষ করা যায় II) কার্যকারক এজেন্টটি স্থাপত্য দিকটি বেশি এবং সত্যিকার অর্থে
ভাষাটি

41

স্থানীয় / গ্লোবাল ভেরিয়েবল স্টোরের সময় বাদে , অপকোড পূর্বাভাসটি ফাংশনটিকে আরও দ্রুত করে তোলে।

অন্যান্য উত্তরগুলি ব্যাখ্যা করার সাথে সাথে ফাংশনটি STORE_FASTলুপে অপকোড ব্যবহার করে । ফাংশনের লুপের জন্য এখানে বাইকোড রয়েছে:

    >>   13 FOR_ITER                 6 (to 22)   # get next value from iterator
         16 STORE_FAST               0 (x)       # set local variable
         19 JUMP_ABSOLUTE           13           # back to FOR_ITER

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

এই ক্ষেত্রে, প্রতিবার পাইথন যখন দেখবে FOR_ITER(লুপের শীর্ষে), এটি "পূর্বাভাস" দেবে STORE_FASTএটি পরবর্তী অপকোড যা সম্পাদন করতে হবে। পাইথন তারপরে পরবর্তী অপকোডে উঁকি দেয় এবং পূর্বাভাসটি সঠিক হলে এটি সরাসরি লাফিয়ে STORE_FAST। এটি দুটি ওপকোডকে একটি একক অপকোডে চেপে ধরার প্রভাব ফেলে।

অন্যদিকে, STORE_NAMEঅপকোডটি বৈশ্বিক স্তরে লুপে ব্যবহৃত হয়। পাইথন যখন এই অপকোডটি দেখেন তখন অনুরূপ পূর্বাভাস দেয় না * । পরিবর্তে, এটি অবশ্যই মূল্যায়ন-লুপের শীর্ষে ফিরে যেতে হবে যার লুপটি যে গতিতে সঞ্চালিত হয় তার স্পষ্ট প্রভাব রয়েছে।

এই অপ্টিমাইজেশান সম্পর্কে আরও কিছু প্রযুক্তিগত বিবরণ দেওয়ার জন্য, ceval.cফাইলটি থেকে একটি উদ্ধৃতি (পাইথনের ভার্চুয়াল মেশিনের "ইঞ্জিন"):

কিছু ওপকোড জোড়ায় আসার প্রবণতা থাকে যাতে প্রথমটি চালিত হওয়ার পরে দ্বিতীয় কোডটির পূর্বাভাস দেওয়া সম্ভব হয়। উদাহরণস্বরূপ, GET_ITERপ্রায়শই অনুসরণ করা হয় FOR_ITER। এবং FOR_ITERপ্রায়শই অনুসরণ করা হয়STORE_FAST বা UNPACK_SEQUENCE

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

আমরা FOR_ITERঅপকোডের উত্স কোডে দেখতে পাই ঠিক যেখানে ভবিষ্যদ্বাণী STORE_FASTকরা হয়েছে:

case FOR_ITER:                         // the FOR_ITER opcode case
    v = TOP();
    x = (*v->ob_type->tp_iternext)(v); // x is the next value from iterator
    if (x != NULL) {                     
        PUSH(x);                       // put x on top of the stack
        PREDICT(STORE_FAST);           // predict STORE_FAST will follow - success!
        PREDICT(UNPACK_SEQUENCE);      // this and everything below is skipped
        continue;
    }
    // error-checking and more code for when the iterator ends normally                                     

PREDICTফাংশন বিস্তৃতি if (*next_instr == op) goto PRED_##opঅর্থাৎ আমরা শুধু পূর্বাভাস opcode শুরুর ঝাঁপ। এই ক্ষেত্রে, আমরা এখানে লাফিয়ে:

PREDICTED_WITH_ARG(STORE_FAST);
case STORE_FAST:
    v = POP();                     // pop x back off the stack
    SETLOCAL(oparg, v);            // set it as the new local variable
    goto fast_next_opcode;

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

পাইথন উইকি পাতা কিভাবে CPython এর ভার্চুয়াল মেশিন কাজ করে সে সম্পর্কে আরও তথ্য রয়েছে।


গৌণ আপডেট: সিপিথন ৩.6 হিসাবে, পূর্বাভাস থেকে প্রাপ্ত সঞ্চয় কিছুটা কমে যায়; দুটি অনির্দেশ্য শাখার পরিবর্তে, কেবল একটিই রয়েছে। পরিবর্তনটি বাইটোকোড থেকে ওয়ার্ডকোডে স্যুইচ করার কারণে হয়েছে ; এখন সমস্ত "ওয়ার্ডকোড" এর একটি যুক্তি রয়েছে, যখন নির্দেশটি যুক্তিযুক্তভাবে যুক্তি না নেয় তখন এটি কেবল শূন্য-এড আউট হয়। সুতরাং, HAS_ARGপরীক্ষাটি কখনই ঘটে না (কেবলমাত্র একটি অবিশ্বাস্য লাফ রেখে, কম স্তরের ট্রেসিং সংকলন এবং রানটাইম উভয় সময়ে সক্ষম করা বাদে)।
শ্যাডোর্যাঞ্জার

অনিশ্চিত লাফ ঘটবে না এমনকি যে অধিকাংশ কারণ নতুন (CPython এর তৈরী করে এ, পাইথন 3.1 হিসাবে , 3.2 ডিফল্টরূপে সক্রিয় নির্ণিত gotos আচরণ); যখন ব্যবহার করা হয়, PREDICTম্যাক্রো সম্পূর্ণ অক্ষম থাকে; পরিবর্তে বেশিরভাগ ক্ষেত্রেই DISPATCHসেই শাখায় সরাসরি শেষ হয় । তবে সিপিইউগুলির ভবিষ্যদ্বাণীকারী শাখায়, প্রভাবটি এর সাথে সমান PREDICT, যেহেতু ব্রাঞ্চিং (এবং পূর্বাভাস) অপকোড প্রতি হয়, যা সফল শাখার পূর্বাভাসের প্রতিক্রিয়া বাড়িয়ে তোলে।
শ্যাডোর্যাঞ্জার
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.