۲۷ - ۴فرمهای اپلیکتیو ثابت
وقتی صحبت از مصرفِ حافظه و اشتراکگذاری در هسکل میشه، حتماً باید از CAFها هم صحبت کرد: فرمهای اپلیکتیو ثابت. CAFها بیانیههایی بدون متغیرِ آزاد هستن که در حافظه برای اشتراکگذاری بین همهی بیانیههای دیگهی یه ماژول نگه داشته میشن. ممکنه مقادیر لیترال، یا تابعهای نیمه اعمالشدهای باشن که هیچ آرگومانِ اسمداری ندارن.
الان یه CAF ِ خیلی بزرگ درست میکنیم. دقت کنین که روی یه لیستِ بینهایت نگاشت کردیم و میخوایم بدونیم این کار چقدر حافظه مصرف کرده. احتمالاً خیلی زیاد:
module Main where
incdInts :: [Integer]
incdInts = map (+1) [1..]
main :: IO ()
main = do
print (incdInts !! 1000)
print (incdInts !! 9001)
print (incdInts !! 90010)
print (incdInts !! 9001000)
print (incdInts !! 9501000)
print (incdInts !! 9901000)اگه پروفایلِش کنیم:
Thu Jan 21 23:25 2016
Time and Allocation
Profiling Report (Final)
cafSaturation +RTS -p -RTS
total time = 0.28 secs
(283 ticks @ 1000 us, 1 processor)
total alloc = 1,440,216,712 bytes
(excludes profiling overheads)
COST CENTRE MODULE %time %alloc
incdInts Main 90.1 100.0
main Main 9.9 0.0
-- یه کم از شلوغیهای بیربط حذف شدن
COST CENTRE MODULE no. entries %time %alloc
MAIN MAIN 45 0 0.0 0.0
CAF Main 89 0 0.0 0.0
incdInts Main 91 1 90.1 100.0
main Main 90 1 9.9 0.0ببینید چطور incdInts برای خودش، جدای از main، یه فرمِ اپلیکتیو ثابت (CAF) ِه. به سایزِ اون تخصیصِ حافظه هم دقت کنین. دلیلش اینه که اون نگاشت روی لیست بینهایت، یه مقدارِ سطحِ بالا هست که میتونه در کلِ ماژول به اشتراک گذاشته بشه، پس باید برای اشتراکگذاری حساب و جوابش توی حافظه نگه داشته بشه.
CAFها شاملِ این موارد میشن:
مقادیر؛
توابعِ نیمه اعمالشده بدونِ آرگومانهای اسمدار؛
تابعهای تماماً اعمال شده مثلِ incdInts در مثال بالا، البته چنین چیزی در کدِ واقعی خیلی کم پیش میاد.
CAFها، چون جلوی محاسبهی تکراریِ مقادیرِ به اشتراک گذاشته شده رو میگیرن، ممکنه کمک به تسریعِ بعضی برنامهها کنن؛ اما خیلی سریع ممکنه حافظهپرکُن بشن. چیزِ مهمی که باید به خاطر بسپارین، اینه که اگه دیدین برنامهتون بیشتر از اونی که انتظار دارین حافظه مصرف میکنه، دنبالِ CAF ِ طلایی بگردین تا بکُشینِش.
خوشبختانه CAFها اکثراً در چنین کدهای بازیچهای پیدا میشن. کدِ واقعی معمولاً داده رو از جای دیگه میاره و این مشکلِ نگهداریِ مقادیرِ زیادِ داده در حافظه رو حذف میکنه.
ببینیم چطور با اضافه کردنِ یه آرگومان به incdInts، میشه از ساخت CAF جلوگیری کرد:
module Main where
-- نیست CAF
incdInts :: [Integer] -> [Integer]
incdInts x = map (+1) x
main :: IO ()
main = do
print (incdInts !! 1000)اگه پروفایلش رو ببینیم:
CAF
main
incdIntsتعاریفِ سطح بالا ِ بینقطه، CAF میشن، اما نقطهدارها نه. این رو در فصلِ قبل هم تا حدی توضیح دادیم. عموماً دلیلِ اهمیتِ این تفاوت، میزانِ حافظهی تخصیص داده شدهای که پروفایل گزارش میده نیست (که معمولاً هم گمراهکنندهست). به این دلیل مهمه که ساخت یه لیست (که آناً دور انداخته میشه) تو GHC خیلی کم هزینهست. این کار ممکنه مقدار حافظهای که در کل اختصاص داده میشه رو بیشتر کنه، اما مثل یه CAF توی هیپ نمیمونه که همین ممکنه حداکثر حافظه ِ مصرفی، و زمانی که صرفِ زبالهروبی میشه رو کاهش بده.
خب الان که به خودیِخودِش یه CAF نیست. اما اگه کاهش اِتا بدیم (یعنی آرگومانهای اسمدارِش رو حذف کنیم) تا بینقطه بشه چطور؟
module Main where
-- میشه. CAF این بار
incdInts :: [Integer]
incdInts = map (+1)
main :: IO ()
main = do
print (incdInts [1..] !! 1000)این بار که پروفایلش رو نگاه کنین، CAF ِ خودش میشه:
CAF
incdInts
main
incdIntsعجب!
برای چیزی به این پیشوپاافتادگی تأثیری روی عملکرد نداره، ولی منظورمون رو میرسونه. تفاوت اصلی بین این دوتا در پروفایلهای هیپ ِه. اگه خودتون نگاهشون کنین احتمالاً متوجهِ منظورمون میشین.