একটি হাস্কেল প্রোগ্রামের পারফরম্যান্স বিশ্লেষণের সরঞ্জাম


104

হাস্কেল শিখতে কিছু প্রজেক্ট অলারের সমস্যা সমাধান করার সময় (সুতরাং বর্তমানে আমি পুরোপুরি শিক্ষানবিশ) আমি সমস্যা 12 এ এসেছি । আমি এই (নিষ্পাপ) সমাধানটি লিখেছি:

--Get Number of Divisors of n
numDivs :: Integer -> Integer
numDivs n = toInteger $ length [ x | x<-[2.. ((n `quot` 2)+1)], n `rem` x == 0] + 2

--Generate a List of Triangular Values
triaList :: [Integer]
triaList =  [foldr (+) 0 [1..n] | n <- [1..]]

--The same recursive
triaList2 = go 0 1
  where go cs n = (cs+n):go (cs+n) (n+1)

--Finds the first triangular Value with more than n Divisors
sol :: Integer -> Integer
sol n = head $ filter (\x -> numDivs(x)>n) triaList2

এই সমাধানটির জন্য n=500 (sol 500)অত্যন্ত ধীর গতি (এখন ২ ঘন্টারও বেশি সময় ধরে চলছে), তাই কেন এই সমাধানটি এত ধীর হয় তা আমি কীভাবে জানতে পারি won এমন কোনও আদেশ রয়েছে যা আমাকে জানায় যে গণনা-সময়টির বেশিরভাগ সময় ব্যয় হয় তাই আমি জানি যে আমার হাস্কেল-প্রোগ্রামের কোন অংশটি ধীর? সরল প্রোফাইলারের মতো কিছু।

এটা পরিষ্কার করার জন্য, আমি জিজ্ঞাসা করছি না জন্য একটি দ্রুত সমাধান কিন্তু একটি উপায় এই সমাধান খুঁজে। আপনার যদি কোন শীঘ্রই জ্ঞান না থাকে তবে আপনি কীভাবে শুরু করবেন?

আমি দুটি triaListফাংশন লেখার চেষ্টা করেছি তবে কোনটি দ্রুত তা পরীক্ষা করার কোন উপায় খুঁজে পেলাম না, সুতরাং এখান থেকেই আমার সমস্যাগুলি শুরু হয়।

ধন্যবাদ

উত্তর:


187

কীভাবে এই সমাধানটি এত ধীর হয় তা সন্ধান করবেন। এমন কোনও আদেশ রয়েছে যা আমাকে জানায় যে গণনা-সময়টির বেশিরভাগ সময় ব্যয় হয় তাই আমি জানি যে আমার হাস্কেল-প্রোগ্রামের কোন অংশটি ধীর?

অবিকল! জিএইচসি অনেকগুলি দুর্দান্ত সরঞ্জাম সরবরাহ করে, যার মধ্যে রয়েছে:

সময় এবং স্থানের প্রোফাইলের ব্যবহার সম্পর্কে একটি টিউটোরিয়াল রিয়েল ওয়ার্ল্ড হাস্কেলের একটি অংশ

জিসি পরিসংখ্যান

প্রথমত, আপনি ghc -O2 দিয়ে সংকলন করছেন তা নিশ্চিত করুন। এবং আপনি নিশ্চিত করতে পারেন যে এটি একটি আধুনিক জিএইচসি (উদাঃ জিএইচসি 6.12.x)

আমরা প্রথমে যা করতে পারি তা পরীক্ষা করে নিন যে আবর্জনা সংগ্রহ করা সমস্যা নয়। আপনার প্রোগ্রামটি + আরটিএস-গুলি দিয়ে চালান

$ time ./A +RTS -s
./A +RTS -s 
749700
   9,961,432,992 bytes allocated in the heap
       2,463,072 bytes copied during GC
          29,200 bytes maximum residency (1 sample(s))
         187,336 bytes maximum slop
               **2 MB** total memory in use (0 MB lost due to fragmentation)

  Generation 0: 19002 collections,     0 parallel,  0.11s,  0.15s elapsed
  Generation 1:     1 collections,     0 parallel,  0.00s,  0.00s elapsed

  INIT  time    0.00s  (  0.00s elapsed)
  MUT   time   13.15s  ( 13.32s elapsed)
  GC    time    0.11s  (  0.15s elapsed)
  RP    time    0.00s  (  0.00s elapsed)
  PROF  time    0.00s  (  0.00s elapsed)
  EXIT  time    0.00s  (  0.00s elapsed)
  Total time   13.26s  ( 13.47s elapsed)

  %GC time       **0.8%**  (1.1% elapsed)

  Alloc rate    757,764,753 bytes per MUT second

  Productivity  99.2% of total user, 97.6% of total elapsed

./A +RTS -s  13.26s user 0.05s system 98% cpu 13.479 total

যা ইতিমধ্যে আমাদের প্রচুর তথ্য দেয়: আপনার কাছে কেবল একটি 2 এম হিপ রয়েছে, এবং জিসি 0.8% সময় নেয়। সুতরাং চিন্তার দরকার নেই যে বরাদ্দই সমস্যা।

সময় প্রোফাইল

আপনার প্রোগ্রামের জন্য একটি সময় প্রোফাইল পাওয়া সরাসরি এগিয়ে রয়েছে: -প্রফিট-অটো-সব দিয়ে সংকলন করুন

 $ ghc -O2 --make A.hs -prof -auto-all
 [1 of 1] Compiling Main             ( A.hs, A.o )
 Linking A ...

এবং, এন = 200 এর জন্য:

$ time ./A +RTS -p                   
749700
./A +RTS -p  13.23s user 0.06s system 98% cpu 13.547 total

যা এ.প্রোফযুক্ত একটি ফাইল তৈরি করে:

    Sun Jul 18 10:08 2010 Time and Allocation Profiling Report  (Final)

       A +RTS -p -RTS

    total time  =     13.18 secs   (659 ticks @ 20 ms)
    total alloc = 4,904,116,696 bytes  (excludes profiling overheads)

COST CENTRE          MODULE         %time %alloc

numDivs            Main         100.0  100.0

আপনার সমস্ত সময় numDivs এ ব্যয় করা ইঙ্গিত করে এবং এটি আপনার সমস্ত বরাদ্দের উত্সও।

গাদা প্রোফাইল

আপনি এই বরাদ্দগুলির একটি বিরতিও পেতে পারেন, + আরটিএস-পি-হাই দিয়ে চালিয়ে যা এএইচপি তৈরি করে, যা আপনি এটিকে পোস্টস্ক্রিপ্ট ফাইল (hp2ps -c A.hp) এ রূপান্তর করে দেখতে পারেন:

বিকল্প পাঠ

যা আমাদের জানায় আপনার মেমোরি ব্যবহারে কোনও সমস্যা নেই: এটি ধ্রুবক স্থানে বরাদ্দ করা হয়।

সুতরাং আপনার সমস্যাটি নামডিজের অ্যালগোরিদমিক জটিলতা:

toInteger $ length [ x | x<-[2.. ((n `quot` 2)+1)], n `rem` x == 0] + 2

এটি স্থির করুন, যা আপনার চলমান সময়ের 100% এবং অন্য সব কিছুই সহজ।

নিখুঁতকরণ

এই অভিব্যক্তিটি স্ট্রিম ফিউশন অপ্টিমাইজেশনের জন্য ভাল প্রার্থী , তাই আমি ডেটা ব্যবহারের জন্য এটি পুনরায় লিখব rite ভেক্টর , এর মতো:

numDivs n = fromIntegral $
    2 + (U.length $
        U.filter (\x -> fromIntegral n `rem` x == 0) $
        (U.enumFromN 2 ((fromIntegral n `div` 2) + 1) :: U.Vector Int))

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

এটি পরীক্ষা করা হচ্ছে, ghc -O2 - মেক জেডএইচ

$ time ./Z     
749700
./Z  3.73s user 0.01s system 99% cpu 3.753 total

সুতরাং এটি অ্যালগরিদম নিজেই পরিবর্তন না করেই এন = ১৫০ এর জন্য চলমান সময়কে 3.5x দ্বারা হ্রাস করেছে।

উপসংহার

আপনার সমস্যাটি নামডিজ। এটি আপনার চলমান সময়ের 100%, এবং ভয়াবহ জটিলতা রয়েছে। NumDivs সম্পর্কে চিন্তা করুন এবং কীভাবে, উদাহরণস্বরূপ, প্রতিটি এন এর জন্য আপনি [2 .. n div2 + 1] এন বার তৈরি করছেন। এটি স্মরণে রাখার চেষ্টা করুন, যেহেতু মানগুলি পরিবর্তন হয় না।

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


Addenda

যেহেতু numDivs আপনার চলমান সময়ের 100%, তাই প্রোগ্রামের অন্যান্য অংশগুলিকে স্পর্শ করার ফলে খুব বেশি পার্থক্য হবে না, তবে, পাঠ্যক্রমিক উদ্দেশ্যে, আমরা স্ট্রিম ফিউশন ব্যবহারকারীদেরও আবার লিখতে পারি।

আমরা ট্রায়াললিস্টও আবার লিখতে পারি, এবং ট্রায়াললিস্ট 2 এ হাত দ্বারা লেখার লুপটিতে রূপান্তর করতে ফিউশনটির উপর নির্ভর করতে পারি, এটি একটি "প্রিফিক্স স্ক্যান" ফাংশন (ওরফে স্ক্যানল):

triaList = U.scanl (+) 0 (U.enumFrom 1 top)
    where
       top = 10^6

একইভাবে সমাধানের জন্য:

sol :: Int -> Int
sol n = U.head $ U.filter (\x -> numDivs x > n) triaList

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


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

1
ভবিষ্যতের ব্যবহারকারীদের -auto-allপক্ষে, অনুকৃত হয় -fprof-auto
বি। মেহতা

60

সমস্যার সরাসরি সমাধান না দিয়ে ডনসের উত্তর কোনও স্পয়লার না হয়ে দুর্দান্ত।
এখানে আমি একটি সামান্য সরঞ্জামের পরামর্শ দিতে চাই যা আমি সম্প্রতি লিখেছি। আপনি যখন ডিফল্টের চেয়ে আরও বিস্তৃত প্রোফাইল চান তখন হাতে থেকে এসসিসি টিকা রচনার সময় সাশ্রয় হয় ghc -prof -auto-all। তা ছাড়াও রঙিন!

আপনি যে কোডটি দিয়েছেন তার একটি উদাহরণ এখানে রয়েছে (*), সবুজ ঠিক আছে, লাল ধীর: বিকল্প পাঠ

সমস্ত সময় বিভাজকের তালিকা তৈরিতে যায়। এটি আপনি করতে পারেন এমন কয়েকটি জিনিসের পরামর্শ দেয়:
১. ফিল্টারিং n rem x == 0দ্রুত করুন, তবে এটি একটি অন্তর্নির্মিত কার্য সম্ভবত এটি ইতিমধ্যে দ্রুত।
একটি সংক্ষিপ্ত তালিকা তৈরি করুন। আপনি ইতিমধ্যে কিছু যাচাই করে ইতিমধ্যে সেই দিকটিতে কিছু করেছেন n quot 2
৩. তালিকা তৈরি করা সম্পূর্ণরূপে ফেলে দিন এবং দ্রুত সমাধান পেতে কিছু গণিত ব্যবহার করুন। প্রকল্পটি ইউলারের সমস্যাগুলির জন্য এটি সাধারণ উপায়।

(*) আমি আপনার কোডটি eu13.hsএকটি ফাংশন যুক্ত করে একটি ফাইলে রেখে কোড পেয়েছি main = print $ sol 90। তারপরে দৌড় visual-prof -px eu13.hs eu13এবং ফলাফল হয় eu13.hs.html


3

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

triaList = 1 : zipWith (+) triaList [2..]

গণিত সম্পর্কিত নোট: n / 2 অবধি সমস্ত বিভাজনকারী চেক করার প্রয়োজন নেই, এটি স্কয়ারটি (এন) পর্যন্ত পরীক্ষা করার জন্য যথেষ্ট।


2
এছাড়াও বিবেচনা করুন: স্ক্যানেল (+) 1 [2 ..]
ডন স্টুয়ার্ট

1

সময় প্রোফাইলিং সক্ষম করার জন্য আপনি পতাকা সহ আপনার প্রোগ্রামটি চালাতে পারেন। এটার মতো কিছু:

./program +RTS -P -sprogram.stats -RTS

এটি প্রোগ্রাম চালাবে এবং প্রোগ্রাম.স্ট্যাটস নামে একটি ফাইল তৈরি করবে যা প্রতিটি ফাংশনে কত সময় ব্যয় করেছিল তা থাকবে। আপনি জিএইচসি ব্যবহারকারী নির্দেশিকায় জিএইচসি দিয়ে প্রোফাইলিং সম্পর্কে আরও তথ্য পেতে পারেন । বেঞ্চমার্কিংয়ের জন্য, মানদণ্ড গ্রন্থাগার রয়েছে। আমি এই ব্লগ পোস্ট একটি দরকারী ভূমিকা আছে পেয়েছি ।


1
তবে প্রথমে এটি সংকলন করুনghc -prof -auto-all -fforce-recomp --make -O2 program.hs
ড্যানিয়েল
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.