লেসিকাল স্কোপ দিয়ে কেন 'দ্রুত' করা হয়?


31

dolistম্যাক্রোর সোর্স কোডটি পড়ার সময় আমি নীচের মন্তব্যে চলে এসেছি।

;; এটি একটি নির্ভরযোগ্য পরীক্ষা নয়, তবে এটি কোনও ব্যাপার নয় কারণ উভয় শব্দার্থবিজ্ঞান গ্রহণযোগ্য, এটির একটি গতিশীল স্কোপিংয়ের সাথে সামান্য দ্রুত এবং অন্যটি লেক্সিকাল স্কোপিংয়ের সাথে সামান্য দ্রুত (এবং ক্লিনার শব্দার্থক রয়েছে)

যা এই স্নিপেটকে বোঝায় (যা আমি স্বচ্ছতার জন্য সরল করেছি)।

(if lexical-binding
    (let ((temp list))
      (while temp
        (let ((it (car temp)))
          ;; Body goes here
          (setq temp (cdr temp)))))
  (let ((temp list)
        it)
    (while temp
      (setq it (car temp))
      ;; Body goes here
      (setq temp (cdr temp)))))

letএকটি লুপের ভিতরে কোনও ফর্ম ব্যবহৃত হচ্ছে দেখে অবাক হয়েছি । setqএকই বাহ্যিক ভেরিয়েবলের উপর বারবার ব্যবহার করার তুলনায় আমি ধীর মনে করতাম (যেমনটি উপরের দ্বিতীয় ক্ষেত্রে করা হয়) is

আমি তা অস্বীকার করতাম, তত্ক্ষণাত উপরের মন্তব্যের জন্য না হলে স্পষ্টভাবে বলা যায় যে এটি বিকল্পের চেয়ে দ্রুত (লেজিকাল বাইন্ডিং সহ) দ্রুত। তো ... কেন?

  1. লেক্সিকাল বনাম গতিশীল বাঁধাইয়ের উপরের কোডটি কেন পারফরম্যান্সে পৃথক হয়?
  2. letলেক্সিকাল সহ ফর্মটি দ্রুততর কেন ?

উত্তর:


38

সাধারণভাবে গতিশীল বাইন্ডিং বনাম লেক্সিকাল বাইন্ডিং

নিম্নলিখিত উদাহরণ বিবেচনা করুন:

(let ((lexical-binding nil))
  (disassemble
   (byte-compile (lambda ()
                   (let ((foo 10))
                     (message foo))))))

এটি lambdaস্থানীয় ভেরিয়েবলের সাথে একটি সাধারণকে সংকলন এবং তাত্ক্ষণিকভাবে বিচ্ছিন্ন করে । সঙ্গে lexical-bindingঅক্ষম করা হলে, উপরের হিসাবে, নিম্নরূপ বাইট কোড দেখায়:

0       constant  10
1       varbind   foo
2       constant  message
3       varref    foo
4       call      1
5       unbind    1
6       return    

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

আপনি যদি উপরের উদাহরণে আবদ্ধ lexical-bindingহন tতবে বাইট কোডটি কিছুটা পৃথক দেখাচ্ছে:

0       constant  10
1       constant  message
2       stack-ref 1
3       call      1
4       return    

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

সাধারণত, (যেমন স্থানীয় ভেরিয়েবল আভিধানিক বাঁধাই লুক-সঙ্গে let, setqইত্যাদি) আছে অনেক কম রানটাইম এবং মেমরি জটিলতা

এই নির্দিষ্ট উদাহরণ

ডায়নামিকাল বাইন্ডিংয়ের সাথে, প্রতিটি কারণে উপরোক্ত কারণে একটি পারফরম্যান্স জরিমানা জোগায়। আরও বেশি দেয়, তত বেশি গতিশীল পরিবর্তনশীল বাইন্ডিং।

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

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

এটি সামান্য দ্রুত, কারণ এটি অনেক কম নির্দেশাবলীর সংকলন করে। পাশের পাশের বিচ্ছিন্নতা (ডানদিকে স্থানীয় যাক) বিবেচনা করুন:

0       varref    list            0       varref    list         
1       constant  nil             1:1     dup                    
2       varbind   it              2       goto-if-nil-else-pop 2 
3       dup                       5       dup                    
4       varbind   temp            6       car                    
5       goto-if-nil-else-pop 2    7       stack-ref 1            
8:1     varref    temp            8       cdr                    
9       car                       9       discardN-preserve-tos 2
10      varset    it              11      goto      1            
11      varref    temp            14:2    return                 
12      cdr       
13      dup       
14      varset    temp
15      goto-if-not-nil 1
18      constant  nil
19:2    unbind    2
20      return    

পার্থক্যটি কী কারণে ঘটছে তা সম্পর্কে আমার কোনও ধারণা নেই।


7

সংক্ষেপে - গতিশীল বাঁধাই খুব ধীর। লেটিক্যাল বাইন্ডিং রানটাইমের সময় অত্যন্ত দ্রুত। মূল কারণ হ'ল সংক্ষিপ্ত বাইন্ডিং সংকলন সময়ে সমাধান করা যায়, যখন গতিশীল বাঁধাই করতে পারে না।

নিম্নলিখিত কোড বিবেচনা করুন:

(let ((x 42))
    (foo)
    (message "%d" x))

এটি সংকলন করার সময় let, fooসংকলকটি জানতে পারে না যে (পরিবর্তনশীল) আবদ্ধ ভেরিয়েবলটি অ্যাক্সেস করবে কিনা x, সুতরাং এটির জন্য অবশ্যই একটি বাঁধাই তৈরি করা উচিত xএবং অবশ্যই ভেরিয়েবলের নাম ধরে রাখতে হবে। সঙ্গে আভিধানিক বাঁধাই, কম্পাইলার শুধু ডাম্প মান এর xবাইন্ডিং স্ট্যাক, তার নাম ছাড়া, এবং সরাসরি অধিকার এন্ট্রি ব্যবহারের হার।

তবে অপেক্ষা করুন - আরও কিছু আছে। লেক্সিকাল বাইন্ডিংয়ের সাথে, সংকলকটি যাচাই করতে সক্ষম হয় যে এই নির্দিষ্ট বাঁধাই xকেবল কোডটিতে ব্যবহৃত হয় message; যেহেতু xকখনই সংশোধন করা হয় না তাই এটি ইনলাইন xএবং ফলন নিরাপদ

(progn
  (foo)
  (message "%d" 42))

আমি মনে করি না যে বর্তমান বাইটকোড সংকলক এই অপ্টিমাইজেশনটি সম্পাদন করে তবে আমি নিশ্চিত যে এটি ভবিষ্যতে এটি করবে।

সংক্ষেপে:

  • ডায়নামিক বাইন্ডিং একটি ভারী ওজন অপারেশন যা অপ্টিমাইজেশনের জন্য কয়েকটি সুযোগকে মঞ্জুরি দেয়;
  • লেক্সিকাল বাইন্ডিং একটি লাইটওয়েট অপারেশন;
  • কেবলমাত্র পঠনযোগ্য মানটির লেজিকাল বাইন্ডিং প্রায়শই অপ্টিমাইজ করা যায়।

3

এই মন্তব্যটি নির্দেশ করে না যে ডেক্সটিক বাঁধাই গতিশীল বাঁধনের চেয়ে দ্রুত বা ধীর হয়। বরং এটি পরামর্শ দেয় যে different বিভিন্ন রূপের লেক্সিকাল এবং ডায়নামিক বাইন্ডিংয়ের অধীনে বিভিন্ন কার্যকারিতা বৈশিষ্ট্য রয়েছে, উদাহরণস্বরূপ, তাদের মধ্যে একটির একটি বাধ্যতামূলক শৃঙ্খলার অধীনে অপরটি অপরটিতে পছন্দনীয়।

সুতরাং হয় আভিধানিক সুযোগ দ্রুততর গতিশীল সুযোগ চেয়ে? আমি সন্দেহ করি যে এই ক্ষেত্রে খুব বেশি পার্থক্য নেই, তবে আমি জানি না - আপনাকে সত্যিই এটি পরিমাপ করতে হবে।


1
varbindলেক্সিকাল বাইন্ডিং এর অধীনে সংকলিত কোডের কোনও নেই । এটাই পুরো বিষয়টি এবং উদ্দেশ্য।
লুনারিওর

হুম। আমি উপরের উত্স সম্বলিত একটি ফাইল তৈরি ;; -*- lexical-binding: t -*-করেছি, এটি দিয়ে শুরু করে , এটি লোড করেছি এবং কল করেছি (byte-compile 'sum1), ধরে নিই যে লেক্সিকাল বাইন্ডিংয়ের অধীনে সংকলিত সংজ্ঞা তৈরি করেছে। তবে, এটি আছে বলে মনে হয় না।
gsg

সেই ভুল অনুমানের ভিত্তিতে বাইট কোডের মন্তব্যগুলি সরানো হয়েছে।
জিএসজি

lunaryon এর উত্তর শো হল, এ কোড পরিষ্কারভাবে হয় বাঁধাই আভিধানিক অধীনে দ্রুত (যদিও অবশ্যই শুধুমাত্র মাইক্রো পর্যায়ে)।
shosti

@gsg এই ঘোষণাটি কেবলমাত্র একটি স্ট্যান্ডার্ড ফাইল ভেরিয়েবল, যা সম্পর্কিত ফাইল বাফারের বাইরে থেকে ডাকা ফাংশনগুলিতে কোনও প্রভাব ফেলবে না। আইওডাব্লু, এর কেবলমাত্র যদি আপনি উত্স ফাইলটিতে যান এবং তার byte-compileসাথে সম্পর্কিত বাফারটি বর্তমান হওয়ার সাথে অনুরোধ করেন তবে এটি কেবলমাত্র বাইট সংকলকটি ঠিক কী করছে effect যদি আপনি byte-compileপৃথকভাবে প্রার্থনা করেন তবে আপনাকে lexical-bindingআমার স্পষ্টভাবে সেট করা দরকার , যেমনটি আমি আমার উত্তরে করেছি।
লুনারিওর
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.