টেল কল অপ্টিমাইজেশন কী?


817

খুব সহজভাবে, টেল-কল অপ্টিমাইজেশন কী?

আরও সুনির্দিষ্টভাবে বলা যায় যে কয়েকটি ছোট কোড স্নিপেটগুলি কোথায় এটি প্রয়োগ করা যেতে পারে, এবং কেন নয়, কেন তার ব্যাখ্যা দিয়ে?


10
টিসিও লেজ পজিশনে একটি ফাংশন কলকে গোটো, লাফাতে পরিণত করে।
নেস

8
এই প্রশ্নটি এর একের 8 বছর পূর্বে পুরোপুরি জিজ্ঞাসা করা হয়েছিল;)
el

উত্তর:


754

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

স্কিম হ'ল কয়েকটি প্রোগ্রামিং ল্যাঙ্গুয়েজের একটি যা এই অনুমানের গ্যারান্টি দেয় যে কোনও বাস্তবায়ন অবশ্যই এই অপ্টিমাইজেশন সরবরাহ করতে পারে (জাভাস্ক্রিপ্ট এছাড়াও করে, ইএস 6 দিয়ে শুরু করে) , সুতরাং এখানে স্কিমের ফ্যাক্টরিয়াল ফাংশনের দুটি উদাহরণ রয়েছে:

(define (fact x)
  (if (= x 0) 1
      (* x (fact (- x 1)))))

(define (fact x)
  (define (fact-tail x accum)
    (if (= x 0) accum
        (fact-tail (- x 1) (* x accum))))
  (fact-tail x 1))

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

(fact 3)
(* 3 (fact 2))
(* 3 (* 2 (fact 1)))
(* 3 (* 2 (* 1 (fact 0))))
(* 3 (* 2 (* 1 1)))
(* 3 (* 2 1))
(* 3 2)
6

বিপরীতে, লেজ পুনরাবৃত্ত ফ্যাক্টরিয়াল জন্য স্ট্যাক ট্রেস নিম্নলিখিত হিসাবে দেখায়:

(fact 3)
(fact-tail 3 1)
(fact-tail 2 3)
(fact-tail 1 6)
(fact-tail 0 6)
6

আপনি দেখতে পাচ্ছেন, আমাদের কেবল প্রতিটি লেকের ফ্যাক্ট লেজ-তে একই পরিমাণের ডেটা রাখতে হবে কারণ আমরা কেবল যে মানটি উপরে পৌঁছেছি তা কেবল ফিরে আসছি। এর অর্থ হ'ল আমি কল করতে চাইলেও (সত্য 1000000), আমার কাছে একই পরিমাণের পরিমাণ (সত্য 3) প্রয়োজন। এটি অ-পুচ্ছ-পুনরাবৃত্তিযোগ্য সত্যের ক্ষেত্রে নয় এবং যেমন বড় মানগুলি স্ট্যাকের ওভারফ্লো হতে পারে cause


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

3
দুর্দান্ত উত্তর, পুরোপুরি ব্যাখ্যা করা।
জোনাহ

15
কড়া কথায় বলতে গেলে, টেল কল অপটিমাইজেশন কলারের স্ট্যাক ফ্রেমটি প্রয়োজনীয়ভাবে কলিদের সাথে প্রতিস্থাপন করে না, বরং এটি নিশ্চিত করে যে লেজ পজিশনে একটি সীমাহীন সংখ্যক কলগুলির জন্য কেবল একটি সীমিত পরিমাণের জায়গার প্রয়োজন হয়। উইল ক্লিঞ্জারের কাগজটি "যথাযথ লেজ পুনরাবৃত্তি এবং স্থান দক্ষতা" দেখুন: cesura17.net/~will/ পেশাগত
জন

3
এটি কি স্থির-স্থানের পথে পুনরাবৃত্ত ফাংশনগুলি লেখার এক উপায়? কারণ আপনি পুনরাবৃত্তি পদ্ধতির সাহায্যে একই ফলাফল অর্জন করতে পারেননি?
dclowd9901

5
@ dclowd9901, টিসিও আপনাকে পুনরাবৃত্ত লুপের চেয়ে কার্যকরী শৈলীর পছন্দ করতে দেয়। আপনি অপরিহার্য শৈলী পছন্দ করতে পারেন। অনেক ভাষা (জাভা, পাইথন) টিসিও সরবরাহ করে না, তারপরে আপনাকে জানতে হবে যে একটি কার্যকরী কল মেমরির জন্য ব্যয় করে ... এবং অপরিহার্য শৈলীটি পছন্দনীয়।
এমকুলিভ

551

আসুন একটি সাধারণ উদাহরণ দিয়ে চলুন: সি তে বাস্তবকৃত ফাংশনাল ফাংশন

আমরা সুস্পষ্ট পুনরাবৃত্ত সংজ্ঞা দিয়ে শুরু করি

unsigned fac(unsigned n)
{
    if (n < 2) return 1;
    return n * fac(n - 1);
}

ফাংশনটি টেল কল দিয়ে শেষ হয় যদি ফাংশনটি ফেরার আগে শেষ অপারেশনটি অন্য ফাংশন কল হয়। যদি এই কল একই ক্রিয়াকলাপটি আহ্বান করে, এটি লেজ-পুনরাবৃত্ত হয়।

যদিও fac()প্রথম নজরে পুচ্ছ-পুনরাবৃত্তি মনে হচ্ছে, বাস্তবে যা ঘটে তা তা নয়

unsigned fac(unsigned n)
{
    if (n < 2) return 1;
    unsigned acc = fac(n - 1);
    return n * acc;
}

অর্থাৎ শেষ অপারেশনটি হ'ল গুণ এবং না ফাংশন কল।

যাইহোক, fac()অতিরিক্ত যুক্তি হিসাবে কল চেইনে জমা হওয়া মানটি প্রেরণ করে এবং ফেরতের মান হিসাবে কেবলমাত্র চূড়ান্ত ফলাফলটি পাস করে পুনরায় লিখতে পারা যায়:

unsigned fac(unsigned n)
{
    return fac_tailrec(1, n);
}

unsigned fac_tailrec(unsigned acc, unsigned n)
{
    if (n < 2) return acc;
    return fac_tailrec(n * acc, n - 1);
}

এখন, কেন এটি দরকারী? যেহেতু আমরা তাত্ক্ষণিকভাবে লেজ কলের পরে ফিরে আসছি, লেজ অবস্থানে ফাংশনটি শুরু করার আগে আমরা পূর্ববর্তী স্ট্যাকফ্রেমটি বাতিল করতে পারি বা পুনরাবৃত্ত ফাংশনগুলির ক্ষেত্রে স্ট্যাকফ্রেমটিকে যেমন রয়েছে তেমনভাবে পুনরায় ব্যবহার করতে পারি।

টেল-কল অপ্টিমাইজেশন আমাদের পুনরাবৃত্ত কোডকে রূপান্তর করে

unsigned fac_tailrec(unsigned acc, unsigned n)
{
TOP:
    if (n < 2) return acc;
    acc = n * acc;
    n = n - 1;
    goto TOP;
}

এটি ইনলাইন করা যেতে পারে fac()এবং আমরা পৌঁছাতে

unsigned fac(unsigned n)
{
    unsigned acc = 1;

TOP:
    if (n < 2) return acc;
    acc = n * acc;
    n = n - 1;
    goto TOP;
}

যা সমান

unsigned fac(unsigned n)
{
    unsigned acc = 1;

    for (; n > 1; --n)
        acc *= n;

    return acc;
}

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


আপনি কি স্ট্যাকফ্রেমের অবিকল অর্থ বোঝাতে পারেন? কল স্ট্যাক এবং স্ট্যাকফ্রেমের মধ্যে কি পার্থক্য রয়েছে?
শশাক

10
@ কাসাহস: একটি স্ট্যাক ফ্রেম কল স্ট্যাকের একটি অংশ যা প্রদত্ত (সক্রিয়) ফাংশনের সাথে 'সম্পর্কিত'; সিএফ এন.ইউইকিপিডিয়া.আর
ক্রিস্টোফ

1
আমি এই পোস্টটি পড়ার পরে সবেমাত্র তীব্র এপিফনি
পেয়েছি

198

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

এই অপ্টিমাইজেশানটি পুনরাবৃত্ত কলগুলি বিস্ফোরণের পরিবর্তে ধ্রুব স্ট্যাক স্থান নিতে পারে।

উদাহরণ: এই ফ্যাক্টরিয়াল ফাংশনটি TCOptimizable নয়:

def fact(n):
    if n == 0:
        return 1
    return n * fact(n-1)

এই ফাংশনটি তার ফিরতি বিবৃতিতে অন্য ফাংশন কল করার পাশাপাশি কিছু করে।

এই নীচের ফাংশনটি TCOptimizable:

def fact_h(n, acc):
    if n == 0:
        return acc
    return fact_h(n-1, acc*n)

def fact(n):
    return fact_h(n, 1)

এর কারণ এই ফাংশনগুলির মধ্যে যে কোনওটি ঘটতে শেষ কাজটি হ'ল অন্য ফাংশনটি কল করা।


3
পুরো 'ফাংশন জি এফ হতে পারে' জিনিসটি কিছুটা বিভ্রান্তিকর ছিল তবে আপনি যা বলতে চাইছেন তা পেয়েছি এবং উদাহরণগুলি সত্যিই স্পষ্ট করে দিয়েছে। অনেক ধন্যবাদ!
majelbstoat

10
ধারণাটি চিত্রিত করে এমন দুর্দান্ত উদাহরণ। আপনার যে ভাষাটি চয়ন করেছেন তা টেল কল নির্মূলকরণ বা টেল কল অপ্টিমাইজেশন প্রয়োগ করতে হবে তা কেবল অ্যাকাউন্টে নিন। উদাহরণস্বরূপ, পাইথনে লিখিত, আপনি যদি 1000 এর মান লিখেন তবে আপনি একটি "রানটাইম’রর সর্বাধিক পুনরাবৃত্তির গভীরতা অতিক্রম করেছেন" কারণ ডিফল্ট পাইথন প্রয়োগটি টেল রিকার্সন ইলিমিনেশনকে সমর্থন করে না। গুইডোর একটি পোস্ট দেখুন যা সে কারণেই ব্যাখ্যা করছে: neopythonic.blogspot.pt/2009/04/tail-recursion-elimination.html
আরএমসিসি

" একমাত্র পরিস্থিতি" কিছুটা নিখুঁত; এছাড়াও আছে TRMC অন্তত তত্ত্ব, যা নিখুত হবে (cons a (foo b))বা (+ c (bar d))একই ভাবে লেজ অবস্থানে।
নেস

আমি গ্রহণযোগ্য উত্তরের চেয়ে আপনার এফ এবং জি পদ্ধতির পছন্দ করলাম, সম্ভবত কারণ আমি গণিতের মানুষ।
নিতিন

আমি মনে করি আপনি টিসিপিটিমাইজড বলতে চাইছেন। এটি টিসিপিটিমাইজযোগ্য ইনফার্স নয় যে এটি কখনই অনুকূলিত হতে পারে না (যখন এটি বাস্তবে করতে পারে)
জ্যাক ম্যাথিউ

65

টেল কল, পুনরাবৃত্ত টেল কল এবং লেজ কল অপ্টিমাইজেশনের জন্য সম্ভবত সবচেয়ে ভাল উচ্চ স্তরের বিবরণ আমি পেয়েছি এটি ব্লগ পোস্ট

"হ্যাকটি কী: একটি লেজ কল"

লিখেছেন ড্যান সুগালস্কি। লেজ কল অপ্টিমাইজেশনে তিনি লিখেছেন:

এক মুহুর্তের জন্য, এই সাধারণ ক্রিয়াটি বিবেচনা করুন:

sub foo (int a) {
  a += 15;
  return bar(a);
}

সুতরাং, আপনি বা আপনার ভাষা সংকলক, কী করতে পারেন? ভাল, এটি কী করতে পারে তা হ'ল ফর্মের return somefunc();কোডগুলি নিম্ন-স্তরের ক্রমে পরিণত করা pop stack frame; goto somefunc();। আমাদের উদাহরণে, মানে আগে আমরা কল bar, fooনিজেই আপ সাফ করে এবং তারপর, বরং কলিং চেয়ে barএকটি সাবরুটিন হিসাবে, আমরা একটি নিম্ন স্তরের না gotoঅপারেশন শুরু barFoo'ইতিমধ্যে নিজেই স্ট্যাকের বাইরে পরিষ্কার র, তাই যখন barদেখে মনে হচ্ছে শুরু মত যে কেহ নামক fooসত্যিই ডেকেছেন bar, এবং যখন barতার মান, এটা সরাসরি যে কেহ নামে ফেরৎ foo, বরং তা ফিরে চেয়ে fooযা পরে তার কলারের কাছে এটা ফিরে আসবে।

এবং লেজ পুনরাবৃত্তি:

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

যাতে এটি:

sub foo (int a, int b) {
  if (b == 1) {
    return a;
  } else {
    return foo(a*a + a, b - 1);
  }

নিঃশব্দে পরিণত হয়:

sub foo (int a, int b) {
  label:
    if (b == 1) {
      return a;
    } else {
      a = a*a + a;
      b = b - 1;
      goto label;
   }

এই বিবরণটি সম্পর্কে আমি যা পছন্দ করি তা হ'ল একটি অত্যাবশ্যকীয় ভাষার পটভূমি (সি, সি ++, জাভা) থেকে আসা ব্যক্তিদের জন্য উপলব্ধি করা কতটা সংক্ষিপ্ত এবং সহজ (


4
404 ত্রুটি. তবে এটি আর্কাইভ.অর্গ: ওয়েব.আরচিভ.আর.ইউবি
টমি

আমি এটি পাইনি, প্রাথমিক fooফাংশন টেল কলটি অনুকূলিত হয়নি? এটি কেবল কোনও ফাংশনকে তার শেষ পদক্ষেপ হিসাবে ডেকে আনে এবং এটি কেবল সেই মানটি ফিরিয়ে দেয়, তাই না?
সেক্সিবিস্ট

1
@ ট্রাইনহার্ড সম্ভবত আপনার মনে যা ছিল তা নয়, তবে আমি কী আপডেট করছি তা জানাতে এটি আপডেট করেছি। দুঃখিত, পুরো নিবন্ধটি পুনরাবৃত্তি করা যাচ্ছে না!
btiernay

2
আপনাকে ধন্যবাদ, এটি সর্বাধিক আপ-ভোট দেওয়া প্রকল্পের উদাহরণের চেয়ে সহজ এবং বোধগম্য (উল্লেখ করার মতো নয়, স্কিম কোনও সাধারণ ভাষা নয় যা বেশিরভাগ বিকাশকারীরা বুঝতে পারে)
সেভিন 7

2
যে কেউ খুব কমই কার্যকরী ভাষায় ডুব দেয়, "আমার উপভাষা" এর ব্যাখ্যা দেখতে সন্তুষ্ট হয়। ক্রিয়ামূলক প্রোগ্রামারদের তাদের পছন্দের ভাষায় সুসমাচার প্রচার করার জন্য একটি (বোধগম্য) প্রবণতা রয়েছে, তবে অপরিহার্য পৃথিবী থেকে এসে আমি এইরকম একটি উত্তরকে ঘিরে আমার মাথা মোড়ানো এত সহজ বলে মনে করি।
জেমস বেনঞ্জার

15

সবার আগে নোট করুন যে সমস্ত ভাষা এটি সমর্থন করে না।

টিসিও পুনরাবৃত্তির একটি বিশেষ ক্ষেত্রে প্রযোজ্য। এর সংক্ষিপ্তসারটি হ'ল, কোনও ফাংশনে আপনি শেষ কাজটি নিজে কল করুন (যেমন এটি "লেজ" অবস্থান থেকে নিজেকে কল করে), এটি সংকলক দ্বারা মানক পুনরাবৃত্তির পরিবর্তে পুনরাবৃত্তির মতো কাজ করতে অনুকূলিত করা যেতে পারে।

আপনি দেখতে পাচ্ছেন, সাধারণত পুনরাবৃত্তি চলাকালীন, রানটাইমের জন্য সমস্ত পুনরাবৃত্ত কলগুলি ট্র্যাক করে রাখা দরকার, যাতে কোনও ব্যক্তি যখন ফিরে আসে তখন আগের কলটিতে আবার শুরু হতে পারে can (এটি কীভাবে কাজ করে তার একটি ভিজ্যুয়াল ধারণা পেতে পুনরাবৃত্ত কলের ফলাফলটি ম্যানুয়ালি লিখে দেওয়ার চেষ্টা করুন all) সমস্ত কলের খোঁজ রাখা জায়গাগুলি গ্রহণ করে, যা ফাংশনটি নিজেকে অনেক কল করলে তা তাৎপর্যপূর্ণ হয়। তবে টিসিওর সাহায্যে এটি কেবল "শুরুতে ফিরে যান, কেবলমাত্র এই সময়ে প্যারামিটারের মানগুলিকে এই নতুনগুলিতে পরিবর্তন করুন" বলতে পারেন। এটি এটি করতে পারে কারণ পুনরাবৃত্তির কলের পরে কিছুই সেই মানগুলিকে বোঝায় না।


3
টেইল কলগুলি নন-রিকার্সিভ ফাংশনেও প্রযোজ্য হতে পারে। প্রত্যাবর্তনের আগে শেষ ফাংশন যার অন্য ফাংশন অন্য ফাংশনে একটি কল একটি লেজ কল ব্যবহার করতে পারে।
ব্রায়ান

ভাষার ভিত্তিতে কোনও ভাষার ক্ষেত্রে অগত্যা সত্য নয় - bit৪ বিট সি # সংকলক লেজ অপকড সন্নিবেশ করতে পারে যেখানে ৩২-বিট সংস্করণ থাকবে না; এবং এফ # রিলিজ বিল্ড করবে তবে এফ # ডিবাগ ডিফল্টরূপে হবে না।
স্টিভ গিলহাম

3
"টিসিও পুনরাবৃত্তির একটি বিশেষ ক্ষেত্রে প্রযোজ্য"। আমি ভীত যে সম্পূর্ণ ভুল। টেইল কলগুলি লেজ অবস্থানের যে কোনও কলের জন্য প্রযোজ্য। পুনরাবৃত্তির প্রসঙ্গে সাধারণত আলোচনা করা হয় তবে পুনরাবৃত্তি নিয়ে আসলে কিছুই করার থাকে না।
জন হ্যারোপ

@ ব্রায়ান, উপরে প্রদত্ত লিঙ্কটি দেখুন প্রাথমিক fooপদ্ধতির টেল কলটি অনুকূলিত নয়?
সেক্সিবিস্ট

13

X86 বিযুক্তির বিশ্লেষণ সহ জিসিসির ন্যূনতম চলমান উদাহরণ

আসুন দেখুন জিসিসি কীভাবে উত্পন্ন সমাবেশটি দেখে আমাদের জন্য টেল কল অপ্টিমাইজেশন স্বয়ংক্রিয়ভাবে করতে পারে।

এটি https://stackoverflow.com/a/9814654/895245 এর মতো অন্যান্য উত্তরে যা উল্লেখ করা হয়েছিল তার চূড়ান্ত উদাহরণ হিসাবে কাজ করবে যা অপ্টিমাইজেশন পুনরাবৃত্ত ফাংশন কলগুলিকে একটি লুপে রূপান্তর করতে পারে।

এর ফলে স্মৃতিশক্তি সাশ্রয় হয় এবং কর্মক্ষমতা উন্নত হয়, যেহেতু আজকাল মেমরির অ্যাক্সেসগুলি মূলত যা প্রোগ্রামগুলি ধীর করে দেয়

একটি ইনপুট হিসাবে, আমরা জিসিসি একটি অপ্টিমাইটিজড নিষ্পাপ স্ট্যাক ভিত্তিক ফ্যাকটোরিয়াল দিচ্ছি:

tail_call.c

#include <stdio.h>
#include <stdlib.h>

unsigned factorial(unsigned n) {
    if (n == 1) {
        return 1;
    }
    return n * factorial(n - 1);
}

int main(int argc, char **argv) {
    int input;
    if (argc > 1) {
        input = strtoul(argv[1], NULL, 0);
    } else {
        input = 5;
    }
    printf("%u\n", factorial(input));
    return EXIT_SUCCESS;
}

গিটহাব উজানের দিকে

সংকলন এবং বিচ্ছিন্ন:

gcc -O1 -foptimize-sibling-calls -ggdb3 -std=c99 -Wall -Wextra -Wpedantic \
  -o tail_call.out tail_call.c
objdump -d tail_call.out

-foptimize-sibling-callsঅনুসারে লেজ কলগুলির সাধারণীকরণের নাম কোথায় man gcc:

   -foptimize-sibling-calls
       Optimize sibling and tail recursive calls.

       Enabled at levels -O2, -O3, -Os.

যেমনটি উল্লেখ করা হয়েছে: জিসিসি টেল-পুনরাবৃত্তি অপ্টিমাইজেশন সম্পাদন করছে কিনা তা আমি কীভাবে পরীক্ষা করব?

আমি নির্বাচন করি -O1কারণ:

  • অপ্টিমাইজেশন সঙ্গে করা হয় না -O0। আমার সন্দেহ হয় যে এটি প্রয়োজনীয় কারণগুলির মধ্যে অন্তর্বর্তী ট্রান্সফর্মেশনগুলি অনুপস্থিত।
  • -O3 অধার্মিক দক্ষ কোড উত্পাদন করে যা খুব শিক্ষামূলক নয়, যদিও এটি লেজ কলটিও অনুকূলিত।

এর সাথে বিচ্ছিন্ন -fno-optimize-sibling-calls:

0000000000001145 <factorial>:
    1145:       89 f8                   mov    %edi,%eax
    1147:       83 ff 01                cmp    $0x1,%edi
    114a:       74 10                   je     115c <factorial+0x17>
    114c:       53                      push   %rbx
    114d:       89 fb                   mov    %edi,%ebx
    114f:       8d 7f ff                lea    -0x1(%rdi),%edi
    1152:       e8 ee ff ff ff          callq  1145 <factorial>
    1157:       0f af c3                imul   %ebx,%eax
    115a:       5b                      pop    %rbx
    115b:       c3                      retq
    115c:       c3                      retq

সহ -foptimize-sibling-calls:

0000000000001145 <factorial>:
    1145:       b8 01 00 00 00          mov    $0x1,%eax
    114a:       83 ff 01                cmp    $0x1,%edi
    114d:       74 0e                   je     115d <factorial+0x18>
    114f:       8d 57 ff                lea    -0x1(%rdi),%edx
    1152:       0f af c7                imul   %edi,%eax
    1155:       89 d7                   mov    %edx,%edi
    1157:       83 fa 01                cmp    $0x1,%edx
    115a:       75 f3                   jne    114f <factorial+0xa>
    115c:       c3                      retq
    115d:       89 f8                   mov    %edi,%eax
    115f:       c3                      retq

দুজনের মধ্যে মূল পার্থক্য হ'ল:

  • -fno-optimize-sibling-callsব্যবহারসমূহ callq, যা সাধারণত অ অপ্টিমাইজ ফাংশন কল।

    এই নির্দেশটি স্ট্যাকের দিকে ফেরতের ঠিকানাটিকে ধাক্কা দেয়, তাই এটি বৃদ্ধি করে।

    তদতিরিক্ত, এই সংস্করণটিও করে push %rbx, যা স্ট্যাকের দিকে ধাক্কা দেয়%rbx

    জিসিসি এটি করে কারণ এটি সঞ্চয় করে edi, যা প্রথম ফাংশন আর্গুমেন্ট ( n) এর মধ্যে ebx, তারপরে কল factorial

    জিসিসির এটি করা দরকার কারণ এটি অন্য একটি কল করার জন্য প্রস্তুতি নিচ্ছে factorial, যা নতুনটি ব্যবহার করবে edi == n-1

    এটি চয়ন করে ebxকারণ এই রেজিস্টারটি ক্যালি-সেভড: লিনাক্স x86-64 ফাংশন কলের মাধ্যমে কী রেজিস্টারগুলি সংরক্ষণ করা হয় যাতে সাবকেলটি factorialএটি পরিবর্তন না করে এবং হারাতে পারে না n

  • -foptimize-sibling-callsকোনো নির্দেশাবলী স্ট্যাকে ধাক্কা ব্যবহার করে না: এটি শুধুমাত্র করেন gotoমধ্যে জাম্প factorialনির্দেশাবলী সহ jeএবং jne

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

উবুন্টু 18.10, জিসিসি 8.2 তে পরীক্ষিত।


6

এখানে দেখুন:

http://tratt.net/laurie/tech_articles/articles/tail_call_optimization

আপনি সম্ভবত জানেন যে, পুনরাবৃত্তি ফাংশন কল একটি স্ট্যাকের সর্বনাশ করতে পারে; স্ট্যাক স্পেস থেকে দ্রুত রান আউট করা সহজ। টেইল কল অপ্টিমাইজেশন হল এমন এক উপায় যার মাধ্যমে আপনি একটি পুনরাবৃত্ত শৈলীর অ্যালগোরিদম তৈরি করতে পারেন যা ধ্রুব স্ট্যাক স্পেস ব্যবহার করে, তাই এটি বৃদ্ধি পায় না এবং বৃদ্ধি পায় না এবং আপনি স্ট্যাক ত্রুটি পান।


3
  1. আমাদের নিশ্চিত করা উচিত যে ফাংশনে নিজেই কোনও গোটো স্টেটমেন্ট নেই .. ফাংশন কল দ্বারা যত্ন নেওয়া কলি ফাংশনের শেষ জিনিস।

  2. বৃহত্তর স্কেল পুনরাবৃত্তিগুলি এটি অপ্টিমাইজেশনের জন্য ব্যবহার করতে পারে তবে ছোট স্কেলে ফাংশন কলটি টেল কল করার জন্য নির্দেশের ওভারহেডটি আসল উদ্দেশ্যকে হ্রাস করে।

  3. টিসিও চিরকালের জন্য চলমান কার্যকারিতার কারণ হতে পারে:

    void eternity()
    {
        eternity();
    }
    

3 এখনও অপ্টিমাইজ করা হয়নি। এটি হ'ল অপ্রচলিত উপস্থাপনা যা সংকলক পুনরাবৃত্ত কোডের পরিবর্তে ধ্রুব স্ট্যাক স্পেস ব্যবহার করে পুনরাবৃত্ত কোডে রূপান্তর করে। টিসিও কোনও তথ্য কাঠামোর জন্য ভুল পুনরাবৃত্তি স্কিমটি ব্যবহার করার কারণ নয়।
নাম

"টিসিও কোনও ডাটা স্ট্রাকচারের জন্য ভুল পুনরাবৃত্তি স্কিমটি ব্যবহার করার কারণ নয়" দয়া করে এটি কীভাবে প্রদত্ত ক্ষেত্রে প্রাসঙ্গিক তা ব্যাখ্যা করুন। উপরের উদাহরণটি কেবল ফ্রেমগুলির একটি উদাহরণ টিসিও সহ এবং ছাড়াই কল স্ট্যাকের মধ্যে বরাদ্দ করা হয়েছে out
গ্রিলস্যান্ডউইচ

আপনি ট্র্যাভারস () অবিচ্ছিন্ন পুনরাবৃত্তি ব্যবহার করতে পছন্দ করেছেন। টিসিওর সাথে এর কোন যোগসূত্র ছিল না। অনন্তকাল লেজু-কল অবস্থান হিসাবে ঘটে, তবে লেজু-কল অবস্থান প্রয়োজনীয় নয়: শূন্য অনাদি () {চিরকালীন (); থেকে প্রস্থান (); }
নাম 18

আমরা যখন এটির সাথে রয়েছি, তখন "বৃহত আকারের পুনরাবৃত্তি" কী? ফাংশনটিতে কেন আমাদের এড়ানো উচিত? এটি টিসিওর অনুমোদনের জন্য প্রয়োজনীয় বা না পর্যাপ্ত। এবং ওভারহেড কি নির্দেশ? টিসিওর পুরো পয়েন্টটি হ'ল সংকলকটি একটি গোটো দ্বারা লেজ পজিশনে ফাংশন কলকে প্রতিস্থাপন করে।
নাম

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

3

পুনরাবৃত্ত ফাংশন পদ্ধতির একটি সমস্যা আছে। এটি আকার ও (এন) এর একটি কল স্ট্যাক তৈরি করে যা আমাদের মোট মেমরির জন্য ও (এন) খরচ করে। এটি এটিকে স্ট্যাক ওভারফ্লো ত্রুটির কাছে ঝুঁকিপূর্ণ করে তোলে, যেখানে কল স্ট্যাকটি খুব বড় হয়ে যায় এবং স্থানের বাইরে চলে যায়।

টেইল কল অপ্টিমাইজেশন (টিসিও) স্কিম। লম্বা কল স্ট্যাক তৈরি না করা এবং এটি মেমরির ব্যয় সাশ্রয় করতে যেখানে এটি পুনরাবৃত্ত ফাংশনগুলি অনুকূল করতে পারে।

এমন অনেক ভাষা রয়েছে যারা টিসিও করছেন (জাভাস্ক্রিপ্ট, রুবি এবং কয়েকটি সি) যেখানে পাইথন এবং জাভা টিসিও করেন না।

জাভাস্ক্রিপ্ট ভাষা :) :) ব্যবহারের বিষয়টি নিশ্চিত করেছে http:// http://2ality.com/2015/06/tail-call-optimization.html


0

একটি কার্যকরী ভাষায়, টেল কল অপ্টিমাইজেশন যেমন ফাংশন কল ফলাফল হিসাবে একটি আংশিক মূল্যায়ন বাক্স ফিরে আসতে পারে, যার পরে কলার দ্বারা মূল্যায়ন করা হবে।

f x = g x

f 6 হ্রাস করে জি 6 এ। সুতরাং যদি ফলস্বরূপ বাস্তবায়ন জি 6 ফিরে আসতে পারে এবং তারপরে এই অভিব্যক্তিটিকে কল করে এটি একটি স্ট্যাক ফ্রেম সংরক্ষণ করবে।

এছাড়াও

f x = if c x then g x else h x.

জি 6 বা এইচ 6 এফ 6 এ হ্রাস পায় তাই বাস্তবায়ন যদি সি 6 এর মূল্যায়ন করে এবং এটি সত্য বলে মনে হয় তবে এটি হ্রাস করতে পারে,

if true then g x else h x ---> g x

f x ---> h x

একটি সাধারণ নন টেল কল অপ্টিমাইজেশন দোভাষী এইরকম দেখতে পারে,

class simple_expresion
{
    ...
public:
    virtual ximple_value *DoEvaluate() const = 0;
};

class simple_value
{
    ...
};

class simple_function : public simple_expresion
{
    ...
private:
    simple_expresion *m_Function;
    simple_expresion *m_Parameter;

public:
    virtual simple_value *DoEvaluate() const
    {
        vector<simple_expresion *> parameterList;
        parameterList->push_back(m_Parameter);
        return m_Function->Call(parameterList);
    }
};

class simple_if : public simple_function
{
private:
    simple_expresion *m_Condition;
    simple_expresion *m_Positive;
    simple_expresion *m_Negative;

public:
    simple_value *DoEvaluate() const
    {
        if (m_Condition.DoEvaluate()->IsTrue())
        {
            return m_Positive.DoEvaluate();
        }
        else
        {
            return m_Negative.DoEvaluate();
        }
    }
}

একটি লেজ কল অপ্টিমাইজেশন দোভাষী এর মত দেখতে হতে পারে,

class tco_expresion
{
    ...
public:
    virtual tco_expresion *DoEvaluate() const = 0;
    virtual bool IsValue()
    {
        return false;
    }
};

class tco_value
{
    ...
public:
    virtual bool IsValue()
    {
        return true;
    }
};

class tco_function : public tco_expresion
{
    ...
private:
    tco_expresion *m_Function;
    tco_expresion *m_Parameter;

public:
    virtual tco_expression *DoEvaluate() const
    {
        vector< tco_expression *> parameterList;
        tco_expression *function = const_cast<SNI_Function *>(this);
        while (!function->IsValue())
        {
            function = function->DoCall(parameterList);
        }
        return function;
    }

    tco_expresion *DoCall(vector<tco_expresion *> &p_ParameterList)
    {
        p_ParameterList.push_back(m_Parameter);
        return m_Function;
    }
};

class tco_if : public tco_function
{
private:
    tco_expresion *m_Condition;
    tco_expresion *m_Positive;
    tco_expresion *m_Negative;

    tco_expresion *DoEvaluate() const
    {
        if (m_Condition.DoEvaluate()->IsTrue())
        {
            return m_Positive;
        }
        else
        {
            return m_Negative;
        }
    }
}
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.