জিএইচসি হাস্কেলের স্মৃতিচারণ কখন হয়?


106

আমি বুঝতে পারি না যে এম 1 কেন আপাতভাবে মেমোমেজ করা হয়েছে যখন এম 2 নীচে নেই:

m1      = ((filter odd [1..]) !!)

m2 n    = ((filter odd [1..]) !! n)

এম 1 10000000 প্রথম কলটিতে প্রায় 1.5 সেকেন্ড সময় নেয় এবং পরবর্তী কলগুলিতে এর একটি ভগ্নাংশ (সম্ভবত এটি তালিকাটি ক্যাশে করে), যেখানে এম 2 10000000 সর্বদা একই পরিমাণ সময় নেয় (প্রতিটি কলের সাথে তালিকাকে পুনর্নির্মাণ করে)। কোন ধারণা কি হচ্ছে? থাম্বের কোনও নিয়ম আছে কি এবং কখন জিএইচসি কোনও ফাংশন স্মরণ করবে? ধন্যবাদ।

উত্তর:


112

জিএইচসি কার্যাদি স্মরণ করে না।

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

m1' = (!!) (filter odd [1..])              -- NB: See below!
m2' = \n -> (!!) (filter odd [1..]) n

(দ্রষ্টব্য: হাস্কেল 98 প্রতিবেদনে আসলে বাম অপারেটর বিভাগের (a %)সমতুল্য বর্ণনা করা হয়েছে \b -> (%) a b, তবে জিএইচসি এটির নকশা করে (%) aThese এগুলি প্রযুক্তিগতভাবে আলাদা কারণ এগুলি আলাদা করে চিহ্নিত করা যেতে পারে seqI আমি মনে করি আমি এই বিষয়ে কোনও জিএইচসি ট্র্যাকের টিকিট জমা দিয়েছি))

এই দেওয়া, আপনি যে দেখতে পাবেন m1'প্রকাশের filter odd [1..]কোনো ল্যামডা প্রকাশ অন্তর্ভুক্ত করা হয় না তাই এটি শুধুমাত্র আপনার প্রোগ্রামের রান প্রতি একবার নির্ণিত হবে যখন m2', filter odd [1..]প্রতিটি সময় ল্যামডা প্রকাশ প্রবেশ করানো হয় নির্ণিত হবে অর্থাত, প্রতিটি কল এm2' । এটি আপনি যে সময় দেখছেন তার মধ্যে পার্থক্য ব্যাখ্যা করে।


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

f = \x -> let y = [1..30000000] in foldl' (+) 0 (y ++ [x])

জিএইচসি লক্ষ্য করে যে yএটি নির্ভর করে xএবং এতে ফাংশনটি পুনর্লিখন করে না

f = let y = [1..30000000] in \x -> foldl' (+) 0 (y ++ [x])

এই ক্ষেত্রে, নতুন সংস্করণটি খুব কম দক্ষ কারণ এটি যেখানে yসঞ্চিত আছে সেখানে মেমরি থেকে প্রায় 1 গিগাবাইট পড়তে হবে , যখন মূল সংস্করণটি স্থির জায়গায় চলবে এবং প্রসেসরের ক্যাশে ফিট করবে। প্রকৃতপক্ষে, জিএইচসি 6.12.1 এর অধীনে, ফাংশনটি fপ্রায় দ্বিগুণ দ্রুত যখন অপটিমাইজেশন ছাড়াই সংকলিত হয় তার চেয়ে কম কম্পাইল করা হয় -O2


1
মূল্য নির্ধারণের জন্য (ফিল্টার বিজোড় [1 ..]) অভিব্যক্তি যাইহোক শূন্যের কাছাকাছি - এটি সর্বোপরি অলস তালিকাগুলি, সুতরাং তালিকাটি যখন মূল্যায়ন করা হয় তখন আসল ব্যয় (x !! 10000000) অ্যাপ্লিকেশনটিতে হয়। এছাড়াও, এম 1 এবং এম 2 উভয়ই কমপক্ষে নিম্নলিখিত পরীক্ষার মধ্যে -O2 এবং -O1 (আমার জিএফসি 6.12.3 এ) দিয়ে একবার মূল্যায়ন করা বলে মনে হচ্ছে: (পরীক্ষা = এম 1 10000000 seqএম 1 10000000)। কোনও অপ্টিমাইজেশন পতাকা নির্দিষ্ট না করা হলেও একটি পার্থক্য রয়েছে। এবং আপনার "এফ" এর উভয় প্রকারের উপায়ে অপ্টিমাইজেশন নির্বিশেষে সর্বাধিক 5356 বাইটের রেসিডেন্সি রয়েছে (উপায় যখন (ও -2 ব্যবহৃত হয় তখন কম মোট বরাদ্দ সহ)।
এড'কা

1
@ Ed'ka: এর উপরোক্ত সংজ্ঞা সঙ্গে এই পরীক্ষাটি প্রোগ্রাম চেষ্টা করুন, f: main = interact $ unlines . (show . map f . read) . lines; সঙ্গে বা বাইরে সংকলন -O2; তারপর echo 1 | ./main। আপনি যদি পরীক্ষাটি লিখেন main = print (f 5), তবে yএটি আবশ্যক হিসাবে আবর্জনা সংগ্রহ করা যেতে পারে এবং দুটি fএর মধ্যে কোনও পার্থক্য নেই ।
রিড বার্টন

এর, map (show . f . read)অবশ্যই হওয়া উচিত । এবং এখন আমি জিএইচসি 6.12.3 ডাউনলোড করেছি, আমি জিএইচসি 6.12.1 তে একই ফলাফল দেখতে পাচ্ছি। এবং হ্যাঁ, আপনি মূল m1এবং সম্পর্কেটি ঠিক বলেছেন m2: সক্ষম এইচটিএমএল সহ এই জাতীয় উত্তোলন সম্পাদন করে এমন GHC এর সংস্করণগুলিতে রূপান্তরিত m2হবে m1
রিড বার্টন

হ্যাঁ, এখন আমি পার্থক্যটি দেখতে পাচ্ছি (-O2 অবশ্যই ধীর)। এই উদাহরণের জন্য আপনাকে ধন্যবাদ!
এড'কা

29

এম 1 কেবল একবার গণনা করা হয়েছে কারণ এটি একটি ধ্রুবক প্রয়োগকারী ফর্ম, যখন এম 2 সিএএফ নয়, এবং প্রতিটি মূল্যায়নের জন্য এটি গণনা করা হয়।

সিএএফ-তে জিএইচসি উইকি দেখুন: http://www.haskell.org/haskellwiki/ কনস্ট্যান্ট_ অ্যাপ্লিকেশন_ফর্ম


1
"এম 1 কেবল একবার গণনা করা হয়েছে কারণ এটি একটি ধ্রুবক প্রয়োগকারী ফর্ম" আমার কাছে তা বোঝায় না। কারণ সম্ভবত এম 1 এবং এম 2 উভয়ই শীর্ষ-স্তরের ভেরিয়েবল, আমি মনে করি যে এই ফাংশনগুলি কেবল একবারই গণনা করা হবে, তারা সিএএফ হয় বা না তা বিবেচ্য নয়। পার্থক্যটি হ'ল যে [1 ..]প্রোগ্রামটি কার্যকর করার সময় কেবল তালিকাটি একবারে গণনা করা হয় বা ফাংশনের প্রয়োগ অনুযায়ী এটি একবার গণনা করা হয়, তবে এটি কি সিএএফের সাথে সম্পর্কিত?
Tsuyoshi Ito

1
লিঙ্কযুক্ত পৃষ্ঠা থেকে: "একটি সিএএফ ... হয় গ্রাফের এক টুকরোতে সংকলিত করা যেতে পারে যা সমস্ত ব্যবহার দ্বারা ভাগ করা হবে বা কিছু ভাগ করা কোড যা প্রথমবার যখন মূল্যায়ন করা হয় তখন কিছু গ্রাফের সাথে নিজেকে ওভাররাইট করে"। যেহেতু m1সিএএফ, দ্বিতীয়টি প্রয়োগ হয় এবং filter odd [1..](কেবল নয় [1..]!) কেবল একবার গণনা করা হয়। জিএইচসি m2উল্লেখ করতে পারে যে এটি উল্লেখ করে filter odd [1..]এবং একই থাঙ্কটিতে ব্যবহৃত লিঙ্কটি m1রাখতে পারে, তবে এটি একটি খারাপ ধারণা হবে: এটি কিছু পরিস্থিতিতে বড় মেমরির ফাঁস হতে পারে।
আলেক্সি রোমানভ

@ অ্যালেক্সা: সম্পর্কে [1..]এবং সংশোধন করার জন্য আপনাকে ধন্যবাদ filter odd [1..]। বাকি জন্য, আমি এখনও অপরিবর্তিত। যদি আমার ভুল না হয় তবে সিএএফ কেবল তখনই প্রাসঙ্গিক যখন আমরা যুক্তি দিতে চাই যে কোনও সংকলক পারে প্রতিস্থাপন filter odd [1..]মধ্যে m2একটি বিশ্বব্যাপী thunk (যা এমনকি ব্যবহৃত এক হিসাবে একই thunk হতে পারে দ্বারা m1)। কিন্তু প্রশ্নকর্তা এর পরিস্থিতিতে, কম্পাইলার হয়নি না যে, "অপ্টিমাইজেশান," এবং আমি প্রশ্ন তার প্রাসঙ্গিকতা দেখতে পাবে না।
Tsuyoshi Ito

2
এটি প্রাসঙ্গিক যে এটি এটি প্রতিস্থাপন করতে পারে মধ্যে m1 , এবং এটা আছে।
আলেক্সি রোমানভ

13

দুটি রূপের মধ্যে একটি গুরুত্বপূর্ণ পার্থক্য রয়েছে: মনোমর্ফিজম সীমাবদ্ধতা এম 1 এর ক্ষেত্রে প্রযোজ্য তবে এম 2 নয়, কারণ এম 2 স্পষ্টভাবে যুক্তি দিয়েছে। সুতরাং এম 2 এর ধরণটি সাধারণ তবে এম 1 এর সুনির্দিষ্ট। তাদের যে ধরণের নিয়োগ করা হয়েছে সেগুলি হ'ল:

m1 :: Int -> Integer
m2 :: (Integral a) => Int -> a

বেশিরভাগ হাস্কেল সংকলক এবং দোভাষী (যেগুলিতে আমি প্রকৃতই জানি) বহুবর্ষীয় কাঠামোগুলি স্মরণ করে না, সুতরাং এম 2 এর অভ্যন্তরীণ তালিকাটি যতবার বলা হয় সেখানে পুনরায় তৈরি করা হয়, যেখানে এম 1 নেই is


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

1

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

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

primes = filter isPrime [2..]
    where isPrime n = null [factor | factor <- [2..n-1], factor `divides` n]
        where f `divides` n = (n `mod` f) == 0

তারপর এটি পরীক্ষা করার জন্য, আমি GHCI প্রবেশ করে লিখেছিলেন: primes !! 1000। এটি কয়েক সেকেন্ড সময় নিয়েছে, তবে শেষ পর্যন্ত আমি উত্তর পেয়েছি:7927 । তারপরে আমি ফোন করে primes !! 1001সাথে সাথে উত্তরটি পেয়েছি। একইভাবে তাত্ক্ষণিকভাবে আমি এর ফলাফল পেয়েছি take 1000 primes, কারণ হাস্কেলকে আগে 1001 তম উপাদানটি ফেরত দেওয়ার জন্য পুরো হাজার-উপাদান তালিকাটি গণনা করতে হয়েছিল।

সুতরাং আপনি যদি আপনার ফাংশনটি এমনভাবে লিখতে পারেন যা কোনও পরামিতি লাগে না, আপনি সম্ভবত এটি চান। ;)

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