۲۷ - ۳پروفایلینگِ برنامه‌ها

اینجا تمام تلاش‌مون رو می‌کنیم تا چیزهای لازم برای پروفایلینگ ِ برنامه‌هاتون با GHC، و همینطور مفاهیمی که به عقیده‌ی ما در منابعِ مختلف به اندازه‌ی کافی بیان نشدن رو توضیح بدیم، اما به هیچ عنوان قصدِ جایگزینیِ راهنمای کاربرِ GHC رو نداریم. به شدت پیشنهاد می‌کنیم برای اطلاعاتِ بیشتر، راهنما رو بخونین.

پروفایلینگ ِ مصرفِ زمان

بعضی وقت‌ها بجای اینکه بخوایم سرعتِ برنامه‌ها رو بدونیم، می‌خوایم بدونیم چرا کند یا سریع‌اند و وقت‌شون رو کجا صرف می‌کنن. این موقع‌ها از پروفایلینگ استفاده می‌کنیم. اول یه مثالِ ساده برای این کار سرِهَم کنیم:

-- profilingTime.hs
module Main where

f :: IO ()
f = do
  print ([1..] !! 999999)
  putStrLn "f"

g :: IO ()
g = do
  print ([1..] !! 9999999)
  putStrLn "g"

main :: IO ()
main = do
  f
  g

با توجه به اینکه تو ‏‎g‎‏ ساختار ِ لیست رو ۱۰ برابر بیشتر طی می‌کنیم، به نظر میاد CPU باید یه چیزی حدودِ ۱۰ برابر زمانِ بیشتری صرفِ ‏‎g‎‏ کنه. با این کار میشه دید درسته یا نه:

$ stack ghc -- -prof -fprof-auto \
> -rtsopts -O2 profilingTime.hs
$ ./profilingTime +RTS -P
1000000
f
10000000
g

کارِ هر پرچم رو توضیح میدیم:

۱.

پرچم ِ ‏‎-prof‎‏ پروفایلینگ رو فعال می‌کنه. بصورتِ پیش‌فرض پروفایلینگ فعال نیست چون یه ذره برنامه‌ها رو کند می‌کنه، اما چنین چیزی در بررسیِ عملکرد ِ برنامه‌ها عموماً مشکل‌ساز نیست. اگه از این پرچم به تنهایی استفاده کنین، اون موقع ‏‎-prof‎‏ انتظار داره مراکزِ هزینه رو خودتون دستی تعیین کنین (جاهایی که GHC زمانِ محاسبه رو اندازه بگیره).

۲.

پرچم ِ ‏‎-fprof-auto‎‏ به همه‌ی انقیاد ‌هایی که با ‏‎INLINE‎‏ نشانه‌گذاری نشدن، یه مرکز هزینه (هم‌نام با اسمِ انقیاد) نسبت میده. این برای کارهای کوچیک یا کارهایی که خیلی عملکرد‌ِشون حساس نیست خوبه، اما اگه با یه برنامه‌ی بزرگ، یا برنامه‌ای که عملکردِش خیلی به تغییرات حساس‌ه کار دارین، بهتره از این پرچم استفاده نکنین، و بجاش SCC‌ها رو دستی تعیین کنین (مستنداتِ GHC به مرکزِ هزینه میگه SCC).

۳.

پرچم ِ ‏‎-rtsopts‎‏ به شما این امکان رو میده که تنظیماتِ GHC RTS رو به باینری ِ ایجاد شده بدین. این یه چیزِ اختیاری‌ه تا اگه لازم بود، باینری ِ کوچیک‌تری درست بشه. با این به برنامه میگیم که نتیجه‌ی پروفایلینگ رو توی یه فایلِ ‏‎.prof‎‏ هم‌نام با برنامه بریزه.

۴.

پرچم ِ ‏‎-O2‎‏ بالاترین سطحِ بهینه‌سازی ِ برنامه رو فعال می‌کنه. اگه عملکرد ِ برنامه براتون مهمه، این گزینه‌ی خوبی‌ه. خودِ ‏‎-O‎‏ هم بهینه‌سازی رو فعال می‌کنه، ولی نه به شدتِ ‏‎-O2‎‏. هر دو گزینه برای بنچمارکینگ مناسب‌اند؛ کلاً یه چیزِ موردی ِه، اما بیشترِ هسکل‌نویس‌ها فقط از ‏‎-O2‎‏ استفاده می‌کنن.

بعد از باز کردن فایلِ ‏‎.prof‎‏ (که نتایجِ پروفایلینگ رو ذخیره کرده) یه چیزی شبیهِ این می‌بینیم:

$ cat profilingTime.prof

Sun Feb 14 21:34 2016
Time and Allocation
  Profiling Report  (Final)

  profile +RTS -P -RTS

total time  =        0.22 secs
  (217 ticks @ 1000 us, 1 processor)
total alloc = 792,056,016 bytes
  (excludes profiling overheads)

COST CENTRE MODULE %time %alloc ticks bytes

g      Main     91.2   90.9 198   720004344
f      Main      8.8    9.1 19    72012568

... ‎‏بقیه برامون مهم نیستن،‏‎
    ‎‏فقط با این بخش کار داریم.‏‎ ...

و همونطور که حدس میزدیم، %۹۱٫۲ زمان صرفِ ‏‎g‎‏، و %۸٫۸ زمان صرفِ ‏‎f‎‏ شده.

پروفایلینگ ِ مصرفِ هیپ

زمان رو اندازه گرفتیم؛ حالا فضا رو اندازه می‌گیریم. یعنی همون حافظه؛ ستاره‌شناس نیستیم. می‌خوایم زود از این بخش بگذریم تا به قسمت‌های جذاب برسیم:

module Main where

import Control.Monad

blah :: [Integer]
blah = [1..1000] 

main :: IO ()
main =
  replicateM_ 10000 (print blah)
$ ghc -prof -fprof-auto -rtsopts -O2 - loci.hs
$ ./loci +RTS -hc -p
$ hp2ps loci.hp

اگه فایلِ پُست‌اسکریپت ِ ‏‎loci.ps‎‏ رو با برنامه‌ی PDF خوانِ‌تون باز کنین، می‌تونین ببینین برنامه در طولِ اجراش چقدر حافظه مصرف کرده. دقت کنین که برنامه باید یه مدت زمانِ حداقلی اجرا بشه تا چند نمونه‌ی پروفایلینگ از اندازه‌ی هیپ جمع شه.