কোনও তালিকার শেষ কিন্তু দ্বিতীয় উপাদানটি সন্ধান করার সময় কেন এইগুলির মধ্যে `সর্বশেষতম ব্যবহার করা হচ্ছে?


10

নীচে 3 টি ফাংশন দেওয়া আছে যা একটি তালিকার শেষ কিন্তু দ্বিতীয় উপাদানটি সন্ধান করে। যেটি ব্যবহার করছেন last . initসেটি অন্যদের চেয়ে অনেক দ্রুত বলে মনে হচ্ছে। আমি বুঝতে পারি না কেন।

পরীক্ষার জন্য, আমি একটি ইনপুট তালিকা ব্যবহার করেছি [1..100000000](100 মিলিয়ন)। শেষটি প্রায় তাত্ক্ষণিকভাবে চালিত হয় অন্যদিকে কয়েক সেকেন্ড সময় নেয়।

-- slow
myButLast :: [a] -> a
myButLast [x, y] = x
myButLast (x : xs) = myButLast xs
myButLast _ = error "List too short"

-- decent
myButLast' :: [a] -> a
myButLast' = (!! 1) . reverse

-- fast
myButLast'' :: [a] -> a
myButLast'' = last . init

5
initতালিকাটিকে একাধিকবার "প্যাক করা" এড়াতে অনুকূলিত করা হয়েছে।
উইলেম ভ্যান অনসেম

1
@ উইলিমভ্যানঅনসেম তবে এত myButLastধীর কেন ?. দেখে মনে হচ্ছে এটি কোনও তালিকা আনপ্যাক করছে না, তবে কেবল এটি initফাংশন হিসাবে
চালিয়ে যাচ্ছে

1
@Ismor: এটা [x, y]-এর সংক্ষিপ্ত রূপ (x:(y:[])), তাই এটি বাইরের কনস একটি দ্বিতীয় কনস, এবং চেক unpacks যদি সেকেন্ডের লেজ consহয় []। তবুও দ্বিতীয় ধারাটি আবার তালিকাটিকে আনপ্যাক করবে (x:xs)। হ্যাঁ আনপ্যাকিংটি যুক্তিসঙ্গতভাবে দক্ষ, তবে অবশ্যই যদি এটি প্রায়শই ঘটে থাকে তবে প্রক্রিয়াটি ধীর হয়ে যাবে।
উইলেম ভ্যান অনসেম

1
তাকিয়ে hackage.haskell.org/package/base-4.12.0.0/docs/src/... , অপ্টিমাইজেশান যে মনে করা হয় initযদি তার যুক্তি Singleton তালিকা বা একটি খালি তালিকা বারবার পরীক্ষা করে না। একবার পুনরাবৃত্তি শুরু হয়ে গেলে, এটি কেবল ধরে নেয় যে পুনরাবৃত্তির কলটির ফলাফলের মধ্যে প্রথম উপাদানটি সরিয়ে নেওয়া হবে।
চ্যানার

2
@ উইলিমভ্যানঅনসেম আমার মনে হয় আনপ্যাক করা সম্ভবত এখানে সমস্যা নয়: জিএইচসি কল-প্যাটার্ন বিশেষায়িতকরণ করে যা আপনাকে myButLastস্বয়ংক্রিয়ভাবে অনুকূলিত সংস্করণ দেয় । আমি মনে করি এটি সম্ভবত সম্ভাব্য তালিকার ফিউশন যা গতির জন্য দায়ী।
oisdk

উত্তর:


9

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

বলা হচ্ছে যে...

বেঞ্চমার্কিং আমাদের বন্ধু।

criterionএকটি প্যাকেজ যা উন্নত বেঞ্চমার্কিং সরঞ্জাম সরবরাহ করে। আমি দ্রুত এটির মতো একটি মাপদণ্ড তৈরি করেছি:

module Main where

import Criterion
import Criterion.Main

-- slow
myButLast :: [a] -> a
myButLast [x, y] = x
myButLast (x : xs) = myButLast xs
myButLast _ = error "List too short"

-- decent
myButLast' :: [a] -> a
myButLast' = (!! 1) . reverse

-- fast
myButLast'' :: [a] -> a
myButLast'' = last . init

butLast2 :: [a] -> a
butLast2 (x :     _ : [ ] ) = x
butLast2 (_ : xs@(_ : _ ) ) = butLast2 xs
butLast2 _ = error "List too short"

setupEnv = do
  let xs = [1 .. 10^7] :: [Int]
  return xs

benches xs =
  [ bench "slow?"   $ nf myButLast   xs
  , bench "decent?" $ nf myButLast'  xs
  , bench "fast?"   $ nf myButLast'' xs
  , bench "match2"  $ nf butLast2    xs
  ]

main = defaultMain
    [ env setupEnv $ \ xs -> bgroup "main" $ let bs = benches xs in bs ++ reverse bs ]

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

% ghc --version
The Glorious Glasgow Haskell Compilation System, version 8.6.5


% ghc -O2 -package criterion A.hs && ./A
benchmarking main/slow?
time                 54.83 ms   (54.75 ms .. 54.90 ms)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 54.86 ms   (54.82 ms .. 54.93 ms)
std dev              94.77 μs   (54.95 μs .. 146.6 μs)

benchmarking main/decent?
time                 794.3 ms   (32.56 ms .. 1.293 s)
                     0.907 R²   (0.689 R² .. 1.000 R²)
mean                 617.2 ms   (422.7 ms .. 744.8 ms)
std dev              201.3 ms   (105.5 ms .. 283.3 ms)
variance introduced by outliers: 73% (severely inflated)

benchmarking main/fast?
time                 84.60 ms   (84.37 ms .. 84.95 ms)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 84.46 ms   (84.25 ms .. 84.77 ms)
std dev              435.1 μs   (239.0 μs .. 681.4 μs)

benchmarking main/match2
time                 54.87 ms   (54.81 ms .. 54.95 ms)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 54.85 ms   (54.81 ms .. 54.92 ms)
std dev              104.9 μs   (57.03 μs .. 178.7 μs)

benchmarking main/match2
time                 50.60 ms   (47.17 ms .. 53.01 ms)
                     0.993 R²   (0.981 R² .. 0.999 R²)
mean                 60.74 ms   (56.57 ms .. 67.03 ms)
std dev              9.362 ms   (6.074 ms .. 10.95 ms)
variance introduced by outliers: 56% (severely inflated)

benchmarking main/fast?
time                 69.38 ms   (56.64 ms .. 78.73 ms)
                     0.948 R²   (0.835 R² .. 0.994 R²)
mean                 108.2 ms   (92.40 ms .. 129.5 ms)
std dev              30.75 ms   (19.08 ms .. 37.64 ms)
variance introduced by outliers: 76% (severely inflated)

benchmarking main/decent?
time                 770.8 ms   (345.9 ms .. 1.004 s)
                     0.967 R²   (0.894 R² .. 1.000 R²)
mean                 593.4 ms   (422.8 ms .. 691.4 ms)
std dev              167.0 ms   (50.32 ms .. 226.1 ms)
variance introduced by outliers: 72% (severely inflated)

benchmarking main/slow?
time                 54.87 ms   (54.77 ms .. 55.00 ms)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 54.95 ms   (54.88 ms .. 55.10 ms)
std dev              185.3 μs   (54.54 μs .. 251.8 μs)

দেখে মনে হচ্ছে আমাদের "ধীর" সংস্করণটি মোটেও ধীর নয়! এবং প্যাটার্ন মিলের জটিলতা কিছু যোগ করে না। (আমি সামান্য গতি বাড়িয়ে দেখি যে match2আমি টানা দুটি রান চালানোর মধ্য দিয়ে আমি ক্যাশিংয়ের প্রভাবকে সম্মত করি ))

আরও "বৈজ্ঞানিক" ডেটা পাওয়ার একটি উপায় রয়েছে : আমরা -ddump-simplসংকলকটি আমাদের কোডটি কীভাবে দেখে and

মধ্যবর্তী কাঠামো পরিদর্শন আমাদের বন্ধু।

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

% for i in `seq 1 4`; do echo; cat A$i.hs; ghc -O2 -ddump-simpl A$i.hs > A$i.simpl; done

module A1 where

-- slow
myButLast :: [a] -> a
myButLast [x, y] = x
myButLast (x : xs) = myButLast xs
myButLast _ = error "List too short"

module A2 where

-- decent
myButLast' :: [a] -> a
myButLast' = (!! 1) . reverse

module A3 where

-- fast
myButLast'' :: [a] -> a
myButLast'' = last . init

module A4 where

butLast2 :: [a] -> a
butLast2 (x :     _ : [ ] ) = x
butLast2 (_ : xs@(_ : _ ) ) = butLast2 xs
butLast2 _ = error "List too short"

% ./EditDistance.hs *.simpl
(("A1.simpl","A2.simpl"),3866)
(("A1.simpl","A3.simpl"),3794)
(("A2.simpl","A3.simpl"),663)
(("A1.simpl","A4.simpl"),607)
(("A2.simpl","A4.simpl"),4188)
(("A3.simpl","A4.simpl"),4113)

মনে হচ্ছে যে A1A4সবচেয়ে অনুরূপ। পুরো পরিদর্শনটি দেখায় যে প্রকৃতপক্ষে কোডের কাঠামোটি অভিন্ন A1এবং A4একই রকম। যে A2এবং A3একইভাবে যেহেতু দুই ফাংশন একটি রচনা হিসাবে সংজ্ঞায়িত করা হয় এছাড়াও যুক্তিসঙ্গত।

আপনি যদি coreআউটপুটটি ব্যাপকভাবে পরীক্ষা করতে যাচ্ছেন তবে এটি এবং এর মতো পতাকা সরবরাহ করাও বোধগম্য । তারা এটি পড়া এত সহজ করে তোলে।-dsuppress-module-prefixes-dsuppress-uniques

আমাদের শত্রুদের একটি সংক্ষিপ্ত তালিকাও।

সুতরাং, বেঞ্চমার্কিং এবং অপ্টিমাইজেশনের সাথে কী ভুল হতে পারে?

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

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


খুব তথ্যপূর্ণ এবং এই পর্যায়ে আমার জন্য কিছুটা অভিভূতও। এই ক্ষেত্রে, আমি যে সমস্ত "বেঞ্চমার্কিং" করেছি তা হ'ল 100 মিলিয়ন আইটেম তালিকার জন্য সমস্ত ফাংশন চালানো হয়েছিল এবং লক্ষ্য করা যায় যে অন্যটির চেয়ে একটি বেশি সময় নেয়। মানদণ্ড সহ বেঞ্চমার্ক বরং দরকারী বলে মনে হচ্ছে। এ ছাড়াও, ghciআপনি যেমন বলেছিলেন প্রথমে একটি এক্সকে তৈরির তুলনায় বিভিন্ন গতিবেগ (গতির দিক দিয়ে) দেবে বলে মনে হচ্ছে।
ঝড় 125
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.