হাস্কেলের কি লেজ-পুনরাবৃত্তির অপ্টিমাইজেশন রয়েছে?


90

আমি আজ ইউনিক্সে "টাইম" কমান্ডটি আবিষ্কার করেছি এবং ভেবেছিলাম যে আমি হ্যাস্কেলের লেজ-পুনরাবৃত্ত এবং স্বাভাবিক পুনরাবৃত্তির কার্যকারিতার মধ্যে রানটাইমগুলির মধ্যে পার্থক্য পরীক্ষা করতে এটি ব্যবহার করব।

আমি নিম্নলিখিত ফাংশন লিখেছি:

--tail recursive
fac :: (Integral a) => a -> a
fac x = fac' x 1 where
    fac' 1 y = y
    fac' x y = fac' (x-1) (x*y) 

--normal recursive
facSlow :: (Integral a) => a -> a
facSlow 1 = 1
facSlow x = x * facSlow (x-1)

এগুলি একমাত্র এই প্রকল্পের জন্য ব্যবহারের জন্য ছিল তা মনে রেখে বৈধ, তাই আমি শূন্য বা নেতিবাচক সংখ্যাগুলি পরীক্ষা করার জন্য বিরক্ত করিনি।

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


8
আমি বিশ্বাস করি যে কিছু কল স্ট্যাক সংরক্ষণের জন্য টিসিও হ'ল একটি অপ্টিমাইজেশন, এটি বোঝায় না যে আপনি কিছু সিপিইউ সময় সাশ্রয় করবেন। ভুল হলে আমাকে সংশোধন করুন।
জেরোম

4
এটি লিসপ দিয়ে পরীক্ষা করা হয়নি, তবে আমি যে টিউটোরিয়ালটি পড়েছি তা বোঝানো হয়েছে যে স্ট্যাক স্থাপনের ফলে নিজেই আরও বেশি প্রসেসরের খরচ পড়ে, যেখানে সংকলিত থেকে পুনরাবৃত্ত পুচ্ছ-পুনরাবৃত্ত সমাধানটি করার ফলে কোনও শক্তি (সময়) ব্যয় হয় নি thus আরও দক্ষ ছিল।
হাস্কেল রাসেল

4
@ জিরোম ভাল এটি অনেক কিছুর উপর নির্ভর করে, তবে সাধারণত ক্যাচগুলিও খেলতে আসে, তাই
টিসিও

এর কারণ কী? এক কথায়: অলসতা।
ড্যান বার্টন

মজার বিষয় হল, আপনারা facআরও বা কম হ'ল product [n,n-1..1]সহায়িকা ফাংশনটি ব্যবহার করে জিএইচসি কীভাবে গণনা করেন prod, তবে অবশ্যই product [1..n]সহজ হবে। আমি কেবল ধরে নিতে পারি যে তারা এটির দ্বিতীয় যুক্তিতে এই ভিত্তিতে কঠোর করে তোলে নি যে এই জিনিসটি সাজানোর বিষয়টি হ'ল gc খুব আত্মবিশ্বাসী যে এটি একটি সাধারণ সঞ্চয়কারীকে সংকলন করতে পারে।
অ্যান্ড্রুসি

উত্তর:


170

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

আসুন দেখুন কীভাবে আমরা facSlow 5কেস স্টাডি হিসাবে মূল্যায়ন করি :

facSlow 5
5 * facSlow 4            -- Note that the `5-1` only got evaluated to 4
5 * (4 * facSlow 3)       -- because it has to be checked against 1 to see
5 * (4 * (3 * facSlow 2))  -- which definition of `facSlow` to apply.
5 * (4 * (3 * (2 * facSlow 1)))
5 * (4 * (3 * (2 * 1)))
5 * (4 * (3 * 2))
5 * (4 * 6)
5 * 24
120

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

হাস্কেলের পুনরাবৃত্ত ফাংশনগুলি খুব পুনরাবৃত্তির উপায়ে মূল্যায়ন করা হয় না! কেবল চারদিকে ঝুলন্ত কলগুলির স্ট্যাক হ'ল গুণগুলি themselves যদি (*)কোনও কঠোর ডেটা কনস্ট্রাক্টর হিসাবে দেখা হয় তবে এটি রক্ষিত পুনরাবৃত্তি হিসাবে পরিচিত (যদিও এটি সাধারণত নন- স্ট্রাক্ট ডেটা কনস্ট্রাক্টর হিসাবে পরিচিত হয় , যেখানে এর পরিপ্রেক্ষিতে যা অবশিষ্ট থাকে তা ডেটা কনস্ট্রাক্টর হয় - যখন আরও অ্যাক্সেস দ্বারা বাধ্য করা হয়)।

এবার লেজ-পুনরাবৃত্তির দিকে নজর দেওয়া যাক fac 5:

fac 5
fac' 5 1
fac' 4 {5*1}       -- Note that the `5-1` only got evaluated to 4
fac' 3 {4*{5*1}}    -- because it has to be checked against 1 to see
fac' 2 {3*{4*{5*1}}} -- which definition of `fac'` to apply.
fac' 1 {2*{3*{4*{5*1}}}}
{2*{3*{4*{5*1}}}}        -- the thunk "{...}" 
(2*{3*{4*{5*1}}})        -- is retraced 
(2*(3*{4*{5*1}}))        -- to create
(2*(3*(4*{5*1})))        -- the computation
(2*(3*(4*(5*1))))        -- on the stack
(2*(3*(4*5)))
(2*(3*20))
(2*60)
120

সুতরাং আপনি দেখতে পাচ্ছেন কীভাবে নিজেই লেজ পুনরাবৃত্তি আপনাকে কোনও সময় বা স্থান সংরক্ষণ করে নি। এটি সামগ্রিকভাবে কেবল আরও পদক্ষেপ গ্রহণ করে না facSlow 5, এটি নেস্টেড থাঙ্কও তৈরি করে (এখানে দেখানো হয়েছে {...}) - এটির জন্য অতিরিক্ত স্থানের প্রয়োজন - যা ভবিষ্যতের গণনা, নেস্টেড গুণগুলি সম্পাদন করার জন্য বর্ণনা করে।

এই thunk তারপর ঢোঁড়ন দ্বারা উন্মোচন করা হয় এটা নীচে, স্ট্যাক গণনার recreating। উভয় সংস্করণের জন্য খুব দীর্ঘ গণনাগুলির সাথে স্ট্যাক ওভারফ্লো তৈরির একটি বিপদও রয়েছে।

আমরা যদি এটি হ্যান্ড-অপ্টিমাইজ করতে চাই তবে আমাদের কেবল এটি কঠোর করা দরকার। আপনি $!সংজ্ঞায়িত করতে কঠোর অ্যাপ্লিকেশন অপারেটরটি ব্যবহার করতে পারেন

facSlim :: (Integral a) => a -> a
facSlim x = facS' x 1 where
    facS' 1 y = y
    facS' x y = facS' (x-1) $! (x*y) 

এটি facS'তার দ্বিতীয় যুক্তিতে কঠোর হতে বাধ্য করে। (এটি তার প্রথম যুক্তিতে ইতিমধ্যে কঠোর কারণ কোন সংজ্ঞা facS'প্রয়োগ করতে হবে তা নির্ধারণের জন্য এটি মূল্যায়ন করতে হবে ।)

কখনও কখনও কঠোরতা বিপুল পরিমাণে সাহায্য করতে পারে, কখনও কখনও এটি একটি বড় ভুল কারণ অলসতা আরও দক্ষ। এখানে এটি একটি ভাল ধারণা:

facSlim 5
facS' 5 1
facS' 4 5 
facS' 3 20
facS' 2 60
facS' 1 120
120

আপনি যা অর্জন করতে চেয়েছিলেন তা আমি মনে করি।

সারসংক্ষেপ

  • আপনি যদি নিজের কোডটি অপ্টিমাইজ করতে চান তবে প্রথম ধাপটি সংকলন করা -O2
  • লেজ পুনরাবৃত্তি কেবল তখনই ভাল যখন কোনও থ্রুক বিল্ড-আপ নেই এবং কঠোরতা যুক্ত করা সাধারণত এটি এবং যদি উপযুক্ত হয় তবে এটি প্রতিরোধ করতে সহায়তা করে। আপনি যখন এমন ফলাফল তৈরি করছেন যা পরে একবারে একবারে প্রয়োজন হয় This
  • কখনও কখনও লেজ পুনরাবৃত্তি একটি খারাপ পরিকল্পনা এবং রক্ষিত পুনরাবৃত্তি একটি ভাল ফিট, অর্থাত্ যখন আপনি ফলাফল তৈরি করছেন কিছুটা ধাপে প্রয়োজন হবে। এই প্রশ্নটি সম্পর্কে foldrএবং foldlউদাহরণস্বরূপ দেখুন এবং একে অপরের বিরুদ্ধে তাদের পরীক্ষা করুন।

এই দুটি চেষ্টা করুন:

length $ foldl1 (++) $ replicate 1000 
    "The size of intermediate expressions is more important than tail recursion."
length $ foldr1 (++) $ replicate 1000 
    "The number of reductions performed is more important than tail recursion!!!"

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

আপনি কোন হার্ডওয়্যার ব্যবহার করছেন তার উপর নির্ভর করে আপনার জিরো সংখ্যা সামঞ্জস্য করতে হবে।


4
@ উইলনেস এটি দুর্দান্ত, ধন্যবাদ প্রত্যাহার করার দরকার নেই। আমি মনে করি এটি এখন উত্তরোত্তর জন্য একটি ভাল উত্তর।
অ্যান্ড্রুসি

4
এটি দুর্দান্ত, তবে আমি কি কঠোরতার বিশ্লেষণের জন্য কোনও সম্মতি দিতে পারি ? আমি মনে করি এটি প্রায় অবশ্যই জিএইচসি-র সাম্প্রতিক সংস্করণে লেজ-পুনরাবৃত্ত ফ্যাক্টরিয়ালটির কাজ করবে।
dfeuer

16

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

{-# LANGUAGE BangPatterns #-}

fac :: (Integral a) => a -> a
fac x = fac' x 1 where
  fac' 1  y = y
  fac' x !y = fac' (x-1) (x*y)

আপনি যদি -O2(বা ন্যায়সঙ্গত -O) ব্যবহার করে সংকলন করেন তবে কঠোরতা বিশ্লেষণের পর্যায়ে জিএইচসি সম্ভবত এটি নিজেই করবেন ।


4
আমি মনে করি এটির $!তুলনায় এটি আরও পরিষ্কার BangPatterns, তবে এটি একটি ভাল উত্তর। বিশেষত কঠোরতার বিশ্লেষণের উল্লেখ।
সিঙ্গপোলিমা

7

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

(এবং হাস্কেল শেখার ক্ষেত্রে, সেই উইকি পৃষ্ঠাগুলির বাকী অংশগুলিও দুর্দান্ত!)


0

যদি আমি সঠিকভাবে স্মরণ করি তবে জিএইচসি স্বয়ংক্রিয়ভাবে প্লে-রিকার্সিভ ফাংশনগুলি লেজ-পুনরাবৃত্তির অপ্টিমাইজডগুলিতে অনুকূলিত করে।

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