जीएचसी से क्या अनुकूलन करने की उम्मीद की जा सकती है?


183

जीएचसी के पास बहुत सारे अनुकूलन हैं जो यह प्रदर्शन कर सकते हैं, लेकिन मुझे नहीं पता कि वे सभी क्या हैं, और न ही कितनी संभावना है कि उन्हें प्रदर्शन किया जाना चाहिए और किन परिस्थितियों में।

मेरा सवाल है: मैं हर बार या लगभग ऐसा करने के लिए किन परिवर्तनों की अपेक्षा कर सकता हूं? यदि मैं बार-बार निष्पादित होने वाले (मूल्यांकन) किए जाने वाले कोड के टुकड़े को देखता हूं और मेरा पहला विचार "हम्म, शायद मुझे इसे अनुकूलित करना चाहिए", तो ऐसे मामलों में मेरा दूसरा विचार होना चाहिए, "इसके बारे में भी मत सोचो," जीएचसी को यह मिला ”?

मैं पेपर स्ट्रीम फ़्यूज़न: लिस्ट्स फ्रॉम स्ट्रीम्स टू नथिंग टू नथिंग टू ऑल पर पढ़ रहा था , और जिस तकनीक का इस्तेमाल उन्होंने सूची पुन: लिखने के लिए किया था, वह एक अलग रूप में है, जिसे जीएचसी की सामान्य अनुकूलन फिर मज़बूती से सरल छोरों में बदल देगा। जब मेरे स्वयं के कार्यक्रम उस तरह के अनुकूलन के लिए पात्र हैं, तो मैं कैसे बता सकता हूं?

नहीं है कुछ जानकारी GHC के मैनुअल में, लेकिन यह केवल सवाल का जवाब देने की दिशा में जिस तरह का हिस्सा हो जाता है।

संपादित करें: मैं एक इनाम शुरू कर रहा हूँ। मैं निम्न-स्तरीय परिवर्तनों की एक सूची की तरह लैंबडा / लेट / केस-फ्लोटिंग, टाइप / कंस्ट्रक्टर / फंक्शन तर्क विशेषज्ञता, सख्ती से विश्लेषण और अनबॉक्सिंग, वर्कर / रैपर, और जो भी महत्वपूर्ण जीएचसी करता है, उसे छोड़ देना चाहिए। इनपुट और आउटपुट कोड के स्पष्टीकरण और उदाहरणों के साथ, और आदर्श रूप से स्थितियों का उदाहरण जब कुल प्रभाव इसके भागों के योग से अधिक हो। और आदर्श रूप से कुछ का उल्लेख जब परिवर्तन नहीं होगाहोता है। मैं हर परिवर्तन की उपन्यास-लंबाई की व्याख्या की उम्मीद नहीं कर रहा हूं, कुछ वाक्य और इनलाइन वन-लाइनर कोड उदाहरण पर्याप्त हो सकते हैं (या एक लिंक, यदि यह वैज्ञानिक कागज के बीस पृष्ठों तक नहीं है), जब तक कि बड़ी तस्वीर नहीं है इसके अंत तक स्पष्ट है। मैं कोड के एक टुकड़े को देखने में सक्षम होना चाहता हूं और इसके बारे में एक अच्छा अनुमान लगाने में सक्षम हूं कि क्या यह एक तंग लूप में संकलित होगा, या क्यों नहीं, या मुझे इसे बनाने के लिए क्या बदलना होगा। (मुझे स्ट्रीम फ़्यूज़न जैसे बड़े ऑप्टिमाइज़ेशन फ्रेमवर्क में यहाँ बहुत दिलचस्पी नहीं है (मैं सिर्फ इसके बारे में एक पेपर पढ़ता हूं), इस तरह के ज्ञान में अधिक है जो लोग इन रूपरेखाओं को लिखते हैं।)


10
यह सबसे योग्य प्रश्न है। एक योग्य उत्तर लिखना है ... मुश्किल।
मैथमेटिकलऑर्चिड

1
वास्तव में एक अच्छा प्रारंभिक बिंदु यह है: aosabook.org/en/ghc.html
गैब्रियल गोंजालेज

7
किसी भी भाषा में, यदि आपका पहला विचार "शायद मैं उसका अनुकूलन करूं", तो आपका दूसरा विचार "मैं इसे पहले बताऊंगा"।
जॉन एल

4
जबकि आप जिस तरह के ज्ञान के बाद मददगार हैं, और इसलिए यह अभी भी एक अच्छा सवाल है, मुझे लगता है कि आप जितना संभव हो उतना कम अनुकूलन करने की कोशिश करके वास्तव में बेहतर सेवा कर रहे हैं । लिखें तुम क्या मतलब है, और केवल जब यह स्पष्ट हो जाता है कि आप की जरूरत है कि उसके बाद प्रदर्शन की खातिर कोड कम सीधा करने के बारे में सोचते हैं। कोड को देखने और सोचने के बजाय "जो अक्सर निष्पादित होने वाला है, शायद मुझे इसे अनुकूलित करना चाहिए", यह केवल तब होना चाहिए जब आप कोड को बहुत धीरे-धीरे चला रहे हों जो आपको लगता है कि "मुझे पता लगाना चाहिए कि क्या अक्सर निष्पादित होता है और इसे अनुकूलित करें" ।
बेन

14
मैंने पूरी तरह से अनुमान लगाया है कि वह भाग "भविष्यवाणियां" करने के लिए उलाहना देगा! :)। लेकिन मुझे लगता है कि सिक्के का दूसरा पक्ष है, अगर मैं इसे प्रोफाइल करता हूं और यह धीमा है, तो शायद मैं इसे फिर से लिख सकता हूं या इसे एक ऐसे रूप में बदल सकता हूं जो अभी भी उच्च स्तर पर है लेकिन जीएचसी बेहतर कर सकता है, बजाय हाथ से अनुकूलन के? जिसके लिए उसी तरह के ज्ञान की आवश्यकता होती है। और अगर मेरे पास वह ज्ञान होता तो मैं पहली बार अपने आप को एक एडिट-प्रोफाइल चक्र बचा सकता था।
glaebhoerl

जवाबों:


110

यह जीएचसी ट्राक पृष्ठ पासों को भी अच्छी तरह से समझाता है। यह पृष्ठ ऑप्टिमाइज़िंग ऑर्डर की व्याख्या करता है, हालांकि, Trac Wiki के बहुमत की तरह, यह पुराना है।

बारीकियों के लिए, सबसे अच्छी बात शायद यह देखना है कि किसी विशिष्ट कार्यक्रम को कैसे संकलित किया जाता है। यह देखने का सबसे अच्छा तरीका है कि किस अनुकूलन का प्रदर्शन किया जा रहा है, -vझंडे का उपयोग करके कार्यक्रम को मौखिक रूप से संकलित करना है । एक उदाहरण के रूप में हास्केल का पहला टुकड़ा जिसे मैं अपने कंप्यूटर पर पा सकता हूं:

Glasgow Haskell Compiler, Version 7.4.2, stage 2 booted by GHC version 7.4.1
Using binary package database: /usr/lib/ghc-7.4.2/package.conf.d/package.cache
wired-in package ghc-prim mapped to ghc-prim-0.2.0.0-7d3c2c69a5e8257a04b2c679c40e2fa7
wired-in package integer-gmp mapped to integer-gmp-0.4.0.0-af3a28fdc4138858e0c7c5ecc2a64f43
wired-in package base mapped to base-4.5.1.0-6e4c9bdc36eeb9121f27ccbbcb62e3f3
wired-in package rts mapped to builtin_rts
wired-in package template-haskell mapped to template-haskell-2.7.0.0-2bd128e15c2d50997ec26a1eaf8b23bf
wired-in package dph-seq not found.
wired-in package dph-par not found.
Hsc static flags: -static
*** Chasing dependencies:
Chasing modules from: *SleepSort.hs
Stable obj: [Main]
Stable BCO: []
Ready for upsweep
  [NONREC
      ModSummary {
         ms_hs_date = Tue Oct 18 22:22:11 CDT 2011
         ms_mod = main:Main,
         ms_textual_imps = [import (implicit) Prelude, import Control.Monad,
                            import Control.Concurrent, import System.Environment]
         ms_srcimps = []
      }]
*** Deleting temp files:
Deleting: 
compile: input file SleepSort.hs
Created temporary directory: /tmp/ghc4784_0
*** Checking old interface for main:Main:
[1 of 1] Compiling Main             ( SleepSort.hs, SleepSort.o )
*** Parser:
*** Renamer/typechecker:
*** Desugar:
Result size of Desugar (after optimization) = 79
*** Simplifier:
Result size of Simplifier iteration=1 = 87
Result size of Simplifier iteration=2 = 93
Result size of Simplifier iteration=3 = 83
Result size of Simplifier = 83
*** Specialise:
Result size of Specialise = 83
*** Float out(FOS {Lam = Just 0, Consts = True, PAPs = False}):
Result size of Float out(FOS {Lam = Just 0,
                              Consts = True,
                              PAPs = False}) = 95
*** Float inwards:
Result size of Float inwards = 95
*** Simplifier:
Result size of Simplifier iteration=1 = 253
Result size of Simplifier iteration=2 = 229
Result size of Simplifier = 229
*** Simplifier:
Result size of Simplifier iteration=1 = 218
Result size of Simplifier = 218
*** Simplifier:
Result size of Simplifier iteration=1 = 283
Result size of Simplifier iteration=2 = 226
Result size of Simplifier iteration=3 = 202
Result size of Simplifier = 202
*** Demand analysis:
Result size of Demand analysis = 202
*** Worker Wrapper binds:
Result size of Worker Wrapper binds = 202
*** Simplifier:
Result size of Simplifier = 202
*** Float out(FOS {Lam = Just 0, Consts = True, PAPs = True}):
Result size of Float out(FOS {Lam = Just 0,
                              Consts = True,
                              PAPs = True}) = 210
*** Common sub-expression:
Result size of Common sub-expression = 210
*** Float inwards:
Result size of Float inwards = 210
*** Liberate case:
Result size of Liberate case = 210
*** Simplifier:
Result size of Simplifier iteration=1 = 206
Result size of Simplifier = 206
*** SpecConstr:
Result size of SpecConstr = 206
*** Simplifier:
Result size of Simplifier = 206
*** Tidy Core:
Result size of Tidy Core = 206
writeBinIface: 4 Names
writeBinIface: 28 dict entries
*** CorePrep:
Result size of CorePrep = 224
*** Stg2Stg:
*** CodeGen:
*** CodeOutput:
*** Assembler:
'/usr/bin/gcc' '-fno-stack-protector' '-Wl,--hash-size=31' '-Wl,--reduce-memory-overheads' '-I.' '-c' '/tmp/ghc4784_0/ghc4784_0.s' '-o' 'SleepSort.o'
Upsweep completely successful.
*** Deleting temp files:
Deleting: /tmp/ghc4784_0/ghc4784_0.c /tmp/ghc4784_0/ghc4784_0.s
Warning: deleting non-existent /tmp/ghc4784_0/ghc4784_0.c
link: linkables are ...
LinkableM (Sat Sep 29 20:21:02 CDT 2012) main:Main
   [DotO SleepSort.o]
Linking SleepSort ...
*** C Compiler:
'/usr/bin/gcc' '-fno-stack-protector' '-Wl,--hash-size=31' '-Wl,--reduce-memory-overheads' '-c' '/tmp/ghc4784_0/ghc4784_0.c' '-o' '/tmp/ghc4784_0/ghc4784_0.o' '-DTABLES_NEXT_TO_CODE' '-I/usr/lib/ghc-7.4.2/include'
*** C Compiler:
'/usr/bin/gcc' '-fno-stack-protector' '-Wl,--hash-size=31' '-Wl,--reduce-memory-overheads' '-c' '/tmp/ghc4784_0/ghc4784_0.s' '-o' '/tmp/ghc4784_0/ghc4784_1.o' '-DTABLES_NEXT_TO_CODE' '-I/usr/lib/ghc-7.4.2/include'
*** Linker:
'/usr/bin/gcc' '-fno-stack-protector' '-Wl,--hash-size=31' '-Wl,--reduce-memory-overheads' '-o' 'SleepSort' 'SleepSort.o' '-L/usr/lib/ghc-7.4.2/base-4.5.1.0' '-L/usr/lib/ghc-7.4.2/integer-gmp-0.4.0.0' '-L/usr/lib/ghc-7.4.2/ghc-prim-0.2.0.0' '-L/usr/lib/ghc-7.4.2' '/tmp/ghc4784_0/ghc4784_0.o' '/tmp/ghc4784_0/ghc4784_1.o' '-lHSbase-4.5.1.0' '-lHSinteger-gmp-0.4.0.0' '-lgmp' '-lHSghc-prim-0.2.0.0' '-lHSrts' '-lm' '-lrt' '-ldl' '-u' 'ghczmprim_GHCziTypes_Izh_static_info' '-u' 'ghczmprim_GHCziTypes_Czh_static_info' '-u' 'ghczmprim_GHCziTypes_Fzh_static_info' '-u' 'ghczmprim_GHCziTypes_Dzh_static_info' '-u' 'base_GHCziPtr_Ptr_static_info' '-u' 'base_GHCziWord_Wzh_static_info' '-u' 'base_GHCziInt_I8zh_static_info' '-u' 'base_GHCziInt_I16zh_static_info' '-u' 'base_GHCziInt_I32zh_static_info' '-u' 'base_GHCziInt_I64zh_static_info' '-u' 'base_GHCziWord_W8zh_static_info' '-u' 'base_GHCziWord_W16zh_static_info' '-u' 'base_GHCziWord_W32zh_static_info' '-u' 'base_GHCziWord_W64zh_static_info' '-u' 'base_GHCziStable_StablePtr_static_info' '-u' 'ghczmprim_GHCziTypes_Izh_con_info' '-u' 'ghczmprim_GHCziTypes_Czh_con_info' '-u' 'ghczmprim_GHCziTypes_Fzh_con_info' '-u' 'ghczmprim_GHCziTypes_Dzh_con_info' '-u' 'base_GHCziPtr_Ptr_con_info' '-u' 'base_GHCziPtr_FunPtr_con_info' '-u' 'base_GHCziStable_StablePtr_con_info' '-u' 'ghczmprim_GHCziTypes_False_closure' '-u' 'ghczmprim_GHCziTypes_True_closure' '-u' 'base_GHCziPack_unpackCString_closure' '-u' 'base_GHCziIOziException_stackOverflow_closure' '-u' 'base_GHCziIOziException_heapOverflow_closure' '-u' 'base_ControlziExceptionziBase_nonTermination_closure' '-u' 'base_GHCziIOziException_blockedIndefinitelyOnMVar_closure' '-u' 'base_GHCziIOziException_blockedIndefinitelyOnSTM_closure' '-u' 'base_ControlziExceptionziBase_nestedAtomically_closure' '-u' 'base_GHCziWeak_runFinalizzerBatch_closure' '-u' 'base_GHCziTopHandler_flushStdHandles_closure' '-u' 'base_GHCziTopHandler_runIO_closure' '-u' 'base_GHCziTopHandler_runNonIO_closure' '-u' 'base_GHCziConcziIO_ensureIOManagerIsRunning_closure' '-u' 'base_GHCziConcziSync_runSparks_closure' '-u' 'base_GHCziConcziSignal_runHandlers_closure'
link: done
*** Deleting temp files:
Deleting: /tmp/ghc4784_0/ghc4784_1.o /tmp/ghc4784_0/ghc4784_0.s /tmp/ghc4784_0/ghc4784_0.o /tmp/ghc4784_0/ghc4784_0.c
*** Deleting temp dirs:
Deleting: /tmp/ghc4784_0

पहले *** Simplifier:से लेकर अंतिम तक, जहां सभी अनुकूलन चरण होते हैं, हम काफी कुछ देखते हैं।

सबसे पहले, सरलीकरण लगभग सभी चरणों के बीच चलता है। इससे कई पास लिखना आसान हो जाता है। उदाहरण के लिए, कई अनुकूलन को लागू करते समय, वे केवल मैन्युअल रूप से करने के बजाय परिवर्तनों को प्रचारित करने के लिए पुनर्लेखन नियम बनाते हैं। सरलीकरण में कई सरल अनुकूलन शामिल हैं, जिसमें इनलाइनिंग और फ्यूजन शामिल हैं। इसका मुख्य सीमा जो मुझे पता है कि जीएचसी इनलाइन पुनरावर्ती कार्यों को अस्वीकार कर देता है, और यह कि काम करने के लिए फ्यूजन के लिए चीजों को सही ढंग से नामित करना होगा।

इसके बाद, हम प्रदर्शन किए गए सभी अनुकूलन की पूरी सूची देखते हैं:

  • विशेषज्ञ

    विशेषज्ञता का मूल विचार बहुरूपता को दूर करना है और उन स्थानों की पहचान करके ओवरलोडिंग करना है जहां फ़ंक्शन को बुलाया जाता है और फ़ंक्शन के संस्करणों का निर्माण होता है जो बहुरूपी नहीं हैं - वे उन प्रकारों के लिए विशिष्ट हैं जिनके साथ उन्हें बुलाया जाता है। आप कंपाइलर को SPECIALISEप्रज्ञा के साथ ऐसा करने के लिए भी कह सकते हैं । एक उदाहरण के रूप में, एक फैक्टरियल फ़ंक्शन लें:

    fac :: (Num a, Eq a) => a -> a
    fac 0 = 1
    fac n = n * fac (n - 1)

    जैसा कि कंपाइलर गुणन के किसी भी गुण को नहीं जानता है जिसका उपयोग किया जाना है, वह इसे बिल्कुल भी अनुकूलित नहीं कर सकता है। हालाँकि, यह देखता है कि इसका उपयोग a पर किया जाता है Int, यह अब एक नया संस्करण बना सकता है, केवल प्रकार में भिन्न:

    fac_Int :: Int -> Int
    fac_Int 0 = 1
    fac_Int n = n * fac_Int (n - 1)

    अगला, नीचे उल्लिखित नियम आग लगा सकते हैं, और आप अनबॉक्स Intएस पर काम कर रहे कुछ के साथ समाप्त होते हैं , जो मूल से बहुत तेज है। विशेषज्ञता को देखने का एक अन्य तरीका प्रकार वर्ग शब्दकोशों और प्रकार चर पर आंशिक आवेदन है।

    यहाँ स्रोत में नोटों का भार है।

  • तैर कर बाहर निकलना

    संपादित करें: मैं स्पष्ट रूप से यह पहले गलत समझा। मेरा स्पष्टीकरण पूरी तरह से बदल गया है।

    इसका मूल विचार संगणना को स्थानांतरित करना है जिसे कार्यों से दोहराया नहीं जाना चाहिए। उदाहरण के लिए, मान लें कि हमारे पास यह था:

    \x -> let y = expensive in x+y

    उपरोक्त लैम्ब्डा में, हर बार फ़ंक्शन को कॉल किया जाता है, yपुन: प्रतिष्ठित किया जाता है। एक बेहतर फ़ंक्शन, जो फ्लोटिंग आउट उत्पन्न करता है

    let y = expensive in \x -> x+y

    प्रक्रिया को सुविधाजनक बनाने के लिए, अन्य परिवर्तन लागू किए जा सकते हैं। उदाहरण के लिए, ऐसा होता है:

     \x -> x + f 2
     \x -> x + let f_2 = f 2 in f_2
     \x -> let f_2 = f 2 in x + f_2
     let f_2 = f 2 in \x -> x + f_2

    फिर, बार-बार गणना की बचत होती है।

    स्रोत इस मामले में बहुत पठनीय है।

    फिलहाल दो आसन्न मेमनों के बीच बाँध नहीं हैं। उदाहरण के लिए, ऐसा नहीं होता है:

    \x y -> let t = x+x in ...

    जा रहा हूँ

     \x -> let t = x+x in \y -> ...
  • अंदर की ओर तैरते हैं

    स्रोत कोड का हवाला देते हुए,

    का मुख्य उद्देश्य floatInwardsएक मामले की शाखाओं में तैर रहा है, ताकि हम चीजों को आवंटित न करें, उन्हें स्टैक पर सहेजें, और फिर पता चले कि उन्हें चुने हुए शाखा में ज़रूरत नहीं है।

    एक उदाहरण के रूप में, मान लें कि हमारे पास यह अभिव्यक्ति थी:

    let x = big in
        case v of
            True -> x + 1
            False -> 0

    यदि vमूल्यांकन किया जाता है False, तो आवंटित करके x, जो संभवतः कुछ बड़ा हिस्सा है, हमने समय और स्थान बर्बाद कर दिया है। फ्लोटिंग इनवॉयर इसे ठीक करता है, जिससे यह उत्पादन होता है:

    case v of
        True -> let x = big in x + 1
        False -> let x = big in 0

    , जिसे बाद में सरलीकरण द्वारा बदल दिया गया

    case v of
        True -> big + 1
        False -> 0

    यह पेपर , हालांकि अन्य विषयों को कवर करता है, लेकिन यह काफी स्पष्ट परिचय देता है। ध्यान दें कि उनके नाम के बावजूद, तैरना और बाहर तैरना दो कारणों से अनंत लूप में नहीं मिलता है:

    1. फ़्लोट में फ़्लोट caseस्टेटमेंट में देता है , जबकि फ़्लोट आउट फ़ंक्शंस से संबंधित है।
    2. पास का एक निश्चित क्रम है, इसलिए उन्हें बारी-बारी से नहीं जाना चाहिए।

  • मांग का विश्लेषण

    डिमांड एनालिसिस, या कड़ाई का विश्लेषण कम ट्रांसफॉर्मेशन और अधिक है, जैसा कि नाम से पता चलता है, एक सूचना एकत्र करने वाले पास का। कंपाइलर ऐसे फ़ंक्शंस पाता है जो हमेशा अपने तर्कों (या उनमें से कम से कम कुछ) का मूल्यांकन करते हैं, और कॉल-बाय-वैल्यू के बजाय कॉल-बाय-वैल्यू का उपयोग करके उन तर्कों को पास करते हैं। चूंकि आप थ्रॉक्स के ओवरहेड्स को खाली करने के लिए मिलते हैं, यह अक्सर बहुत तेज होता है। हास्केल में कई प्रदर्शन समस्याएं या तो इस पास फेल होने से उत्पन्न होती हैं, या कोड केवल पर्याप्त सख्त नहीं होने के कारण। एक सरल उदाहरण का उपयोग करने के बीच का अंतर है foldr,foldl औरfoldl'पूर्णांकों की सूची का योग करने के लिए - पहला कारण स्टैक ओवरफ्लो, दूसरा कारण ढेर ओवरफ्लो और अंतिम रन सख्त होने के कारण। यह शायद इन सभी को समझने में सबसे आसान और बेहतरीन दस्तावेज है। मेरा मानना ​​है कि बहुरूपता और सीपीएस कोड अक्सर इसे पराजित करते हैं।

  • श्रमिक आवरण बांधता है

    कार्यकर्ता / रैपर परिवर्तन का मूल विचार एक सरल संरचना पर एक तंग लूप करना है, जो उस संरचना से अंत में और उस से परिवर्तित होता है। उदाहरण के लिए, इस फ़ंक्शन को लें, जो किसी संख्या के भाज्य की गणना करता है।

    factorial :: Int -> Int
    factorial 0 = 1
    factorial n = n * factorial (n - 1)

    Intजीएचसी की परिभाषा का उपयोग करना , हमारे पास है

    factorial :: Int -> Int
    factorial (I# 0#) = I# 1#
    factorial (I# n#) = I# (n# *# case factorial (I# (n# -# 1#)) of
        I# down# -> down#)

    ध्यान दें कि कोड किस तरह से कवर किया गया है I#? हम उन्हें ऐसा करके हटा सकते हैं:

    factorial :: Int -> Int
    factorial (I# n#) = I# (factorial# n#)
    
    factorial# :: Int# -> Int#
    factorial# 0# = 1#
    factorial# n# = n# *# factorial# (n# -# 1#)

    हालाँकि यह विशिष्ट उदाहरण SpecConstr द्वारा भी किया जा सकता था, लेकिन यह जो काम कर सकता है उसमें कार्यकर्ता / आवरण परिवर्तन बहुत सामान्य है।

  • सामान्य उप-अभिव्यक्ति

    यह एक और वास्तव में सरल अनुकूलन है जो बहुत प्रभावी है, जैसे कि सख्ती से विश्लेषण। मूल विचार यह है कि यदि आपके पास दो भाव हैं जो समान हैं, तो उनका मूल्य समान होगा। उदाहरण के लिए, यदि fibएक फाइबोनैचि संख्या कैलकुलेटर है, तो CSE रूपांतरित करेगा

    fib x + fib x

    में

    let fib_x = fib x in fib_x + fib_x

    जो आधे में गणना में कटौती करता है। दुर्भाग्य से, यह कभी-कभी अन्य अनुकूलन के रास्ते में मिल सकता है। एक और समस्या यह है कि दो अभिव्यक्तियों को एक ही स्थान पर होना है और उन्हें समान रूप से समान होना है, न कि मूल्य द्वारा समान होना। उदाहरण के लिए, CSE इनलाइनिंग के एक समूह के बिना निम्नलिखित कोड में फायर नहीं करेगा:

    x = (1 + (2 + 3)) + ((1 + 2) + 3)
    y = f x
    z = g (f x) y

    हालाँकि, यदि आप llvm के माध्यम से संकलित करते हैं, तो आपको इसके ग्लोबल वैल्यू नंबरिंग पास के कारण, इनमें से कुछ संयुक्त मिल सकते हैं।

  • मुक्ति का मामला

    यह एक बहुत ही प्रलेखित परिवर्तन प्रतीत होता है, इस तथ्य के अलावा कि यह कोड विस्फोट का कारण बन सकता है। यहाँ थोड़ा सुधार के संस्करण (और थोड़ा फिर से लिखा गया) संस्करण मैंने पाया है:

    यह मॉड्यूल चलता है Core, और caseमुफ्त चर पर दिखता है । मानदंड है: यदि caseपुनरावर्ती कॉल करने के लिए मार्ग पर एक निशुल्क चर पर है, तो पुनरावर्ती कॉल को एक अनफ़ॉल्डिंग के साथ बदल दिया जाता है। उदाहरण के लिए, में

    f = \ t -> case v of V a b -> a : f t

    भीतर fको बदल दिया जाता है। बनाना

    f = \ t -> case v of V a b -> a : (letrec f = \ t -> case v of V a b -> a : f t in f) t

    छायांकन की आवश्यकता पर ध्यान दें। सरलीकरण, हम प्राप्त करते हैं

    f = \ t -> case v of V a b -> a : (letrec f = \ t -> a : f t in f t)

    यह बेहतर कोड है, क्योंकि aभीतर से मुक्त है letrec, बजाय जरूरत से प्रक्षेपण के v। ध्यान दें कि यह नि: शुल्क चर के साथ संबंधित है , SpecConstr के विपरीत, जो कि तर्क से संबंधित है जो ज्ञात रूप से हैं।

    SpecConstr के बारे में अधिक जानकारी के लिए नीचे देखें।

  • SpecConstr - यह जैसे प्रोग्राम को बदल देता है

    f (Left x) y = somthingComplicated1
    f (Right x) y = somethingComplicated2

    में

    f_Left x y = somethingComplicated1
    f_Right x y = somethingComplicated2
    
    {-# INLINE f #-}
    f (Left x) = f_Left x
    f (Right x) = f_Right x

    एक विस्तारित उदाहरण के रूप में, इसकी परिभाषा लें last:

    last [] = error "last: empty list"
    last (x:[]) = x
    last (x:x2:xs) = last (x2:xs)

    हम पहले इसे रूपांतरित करते हैं

    last_nil = error "last: empty list"
    last_cons x [] = x
    last_cons x (x2:xs) = last (x2:xs)
    
    {-# INLINE last #-}
    last [] = last_nil
    last (x : xs) = last_cons x xs

    अगला, सरलीकरण चलता है, और हमारे पास है

    last_nil = error "last: empty list"
    last_cons x [] = x
    last_cons x (x2:xs) = last_cons x2 xs
    
    {-# INLINE last #-}
    last [] = last_nil
    last (x : xs) = last_cons x xs

    ध्यान दें कि कार्यक्रम अब तेज है, क्योंकि हम बार-बार बॉक्सिंग नहीं कर रहे हैं और सूची के सामने वाले बॉक्स को अनबॉक्स नहीं कर रहे हैं। यह भी ध्यान दें कि इनलाइनिंग महत्वपूर्ण है, क्योंकि यह नए, अधिक कुशल परिभाषाओं को वास्तव में उपयोग करने की अनुमति देता है, साथ ही पुनरावर्ती परिभाषाओं को बेहतर बनाता है।

    SpecConstr को कई सांख्यिकी द्वारा नियंत्रित किया जाता है। कागज में उल्लिखित निम्नानुसार हैं:

    1. लंबोदर सुस्पष्ट हैं और आरती है a
    2. दाहिने हाथ की तरफ "पर्याप्त रूप से छोटा," एक ध्वज द्वारा नियंत्रित कुछ है।
    3. फ़ंक्शन पुनरावर्ती है, और विशेष कॉलिंग का उपयोग दाहिने हाथ की ओर किया जाता है।
    4. फ़ंक्शन के सभी तर्क मौजूद हैं।
    5. कम से कम तर्कों में से एक कंस्ट्रक्टर एप्लिकेशन है।
    6. उस तर्क को मामले में कहीं विश्लेषण किया जाता है।

    हालाँकि, आंकड़े निश्चित रूप से बदल गए हैं। वास्तव में, कागज में एक वैकल्पिक छठे हेयुरिस्टिक का उल्लेख है:

    एक तर्क पर विशेषज्ञ xही अगर xहै केवल एक द्वारा छानबीन case, और एक साधारण समारोह को पास नहीं की, या परिणाम के हिस्से के रूप में लौट आए।

यह एक बहुत छोटी फ़ाइल (12 लाइनें) थी और इसलिए संभवत: इतने सारे अनुकूलन नहीं हुए (हालांकि मुझे लगता है कि यह उन सभी ने किया था)। यह भी आपको यह नहीं बताता है कि इसने उन पासों को क्यों उठाया और इसे उस क्रम में क्यों रखा।


अब हम कहीं जा रहे हैं! टिप्पणियाँ: आपको लगता है कि विशेष के बारे में एक कट-ऑफ वाक्य है। मैं फ्लोट-आउट का बिंदु नहीं देखता: यह किस लिए है? यह कैसे तय करता है कि अंदर या बाहर तैरना है (यह लूप में क्यों नहीं आता)? मैंने कहीं से धारणा थी कि GHC सीएसई नहीं किया सब पर है, लेकिन जाहिरा तौर पर है कि गलत था। मुझे लगता है कि मैं एक बड़ी तस्वीर देखने के बजाय विवरण में खो रहा हूं ... विषय जितना मैंने सोचा था उससे कहीं अधिक जटिल है। हो सकता है कि मेरा सवाल असंभव हो और जीएचसी पर काम करने का अनुभव या खुद को छोड़कर इस अंतर्ज्ञान को हासिल करने का कोई तरीका नहीं है?
glaebhoerl

खैर, मुझे नहीं पता, लेकिन मैंने जीएचसी पर कभी काम नहीं किया है, इसलिए आपको कुछ अंतर्ज्ञान प्राप्त करने में सक्षम होना चाहिए ।
गेरेटर

मैंने आपके द्वारा बताए गए मुद्दों को तय किया।
गेरेटर

1
इसके अलावा, बड़ी तस्वीर के बारे में, यह मेरी राय है कि वास्तव में एक नहीं है। जब मैं अनुमान लगाना चाहता हूं कि क्या अनुकूलन किए जाएंगे, तो मैं एक चेकलिस्ट नीचे जाता हूं। फिर मैं इसे फिर से करता हूं, यह देखने के लिए कि प्रत्येक पास चीजों को कैसे बदलेगा। और फिर। अनिवार्य रूप से, मैं कंपाइलर बजाता हूं। एकमात्र अनुकूलन योजना, जिसके बारे में मुझे पता है कि वास्तव में एक "बड़ी तस्वीर" सुपरकंपुलेशन है।
गेरेटर

1
"काम करने के लिए फ्यूजन के लिए चीजों को सही ढंग से नामित करना है" से आपका क्या मतलब है?
विंसेंट बेफ़ारा

65

आलस्य

यह "कंपाइलर ऑप्टिमाइज़ेशन" नहीं है, लेकिन यह भाषा विनिर्देश द्वारा गारंटीकृत कुछ है, इसलिए आप हमेशा ऐसा होने पर भरोसा कर सकते हैं। अनिवार्य रूप से, इसका मतलब है कि जब तक आप परिणाम के साथ "कुछ नहीं" करते हैं, तब तक कार्य नहीं किया जाता है। (जब तक आप जानबूझकर आलस्य को बंद करने के लिए कई चीजों में से एक करते हैं।)

यह, जाहिर है, अपने आप में एक संपूर्ण विषय है, और एसओ के पास इसके बारे में बहुत सारे सवाल और जवाब हैं।

मेरे सीमित अनुभव में, आपके कोड को बहुत आलसी या बहुत सख्त बनाने से मेरे द्वारा बात की जाने वाली किसी भी अन्य सामग्री की तुलना में बहुत बड़ा प्रदर्शन दंड (समय और स्थान में) होता है ...

कठोरता का विश्लेषण

आलस्य काम से बचने के बारे में है जब तक कि यह आवश्यक न हो। यदि संकलक यह निर्धारित कर सकता है कि एक दिया गया परिणाम "हमेशा" की आवश्यकता होगी, तो यह गणना को संग्रहीत करने और बाद में प्रदर्शन करने से परेशान नहीं करेगा; यह सिर्फ इसे सीधे प्रदर्शन करेगा, क्योंकि यह अधिक कुशल है। यह तथाकथित "कठोरता विश्लेषण" है।

गेटा, जाहिर है, संकलक हमेशा नहीं कर सकता है पता लगा कब कुछ सख्त किया जा सकता है। कभी-कभी आपको संकलक को थोड़ा संकेत देने की आवश्यकता होती है। (मैं यह निर्धारित करने के लिए किसी भी आसान तरीके से अवगत नहीं हूं कि क्या कठोरता विश्लेषण ने आपको लगता है कि यह क्या है, मुख्य आउटपुट के माध्यम से वैडिंग के अलावा है।)

इनलाइन

यदि आप किसी फ़ंक्शन को कॉल करते हैं, और कंपाइलर यह बता सकता है कि आप किस फ़ंक्शन को कॉल कर रहे हैं, तो यह फ़ंक्शन को "इनलाइन" करने का प्रयास कर सकता है - अर्थात, फ़ंक्शन कॉल को फ़ंक्शन की एक प्रति के साथ बदलने के लिए। एक फ़ंक्शन कॉल का ओवरहेड आमतौर पर बहुत छोटा होता है, लेकिन इनलाइनिंग अक्सर अन्य अनुकूलन करने में सक्षम होती है जो अन्यथा नहीं हुई होती, इसलिए इनलाइनिंग एक बड़ी जीत हो सकती है।

फ़ंक्शंस केवल इनबिल्ड हैं यदि वे "छोटे पर्याप्त" हैं (या यदि आप विशेष रूप से इनलाइनिंग के लिए एक प्रागम जोड़ते हैं)। इसके अलावा, फ़ंक्शन केवल इनबिल्ड हो सकते हैं यदि कंपाइलर बता सकता है कि आप किस फ़ंक्शन को कॉल कर रहे हैं। दो मुख्य तरीके हैं जो संकलक बताने में असमर्थ हो सकते हैं:

  • यदि आप जिस फ़ंक्शन को कॉल कर रहे हैं वह कहीं और से पारित किया गया है उदाहरण के लिए, जब filterफ़ंक्शन संकलित किया जाता है, तो आप फ़िल्टर को विधेय को इनलाइन नहीं कर सकते, क्योंकि यह एक उपयोगकर्ता द्वारा दिया गया तर्क है।

  • यदि आप जिस फ़ंक्शन को कॉल कर रहे हैं वह एक क्लास विधि है और कंपाइलर को पता नहीं है कि क्या प्रकार शामिल है। जैसे, जब sumफ़ंक्शन संकलित किया जाता है, तो कंपाइलर +फ़ंक्शन को इनलाइन नहीं कर सकता है , क्योंकि sumकई अलग-अलग प्रकारों के साथ काम करता है, जिनमें से प्रत्येक का एक अलग +फ़ंक्शन होता है।

बाद के मामले में, आप {-# SPECIALIZE #-}किसी विशेष प्रकार के हार्ड-कोडेड फ़ंक्शन के संस्करणों को उत्पन्न करने के लिए प्रज्ञा का उपयोग कर सकते हैं । उदाहरण के लिए, हार्ड-कोडित {-# SPECIALIZE sum :: [Int] -> Int #-}का एक संस्करण संकलित करेगा , जिसका अर्थ है कि इस संस्करण में इनलेट किया जा सकता है।sumInt+

ध्यान दें, हालांकि, हमारा नया विशेष sumकार्य केवल तभी कहा जाएगा जब कंपाइलर बता सकता है कि हम साथ काम कर रहे हैं Int। अन्यथा मूल, बहुरूपी sumकहलाता है। फिर, वास्तविक फ़ंक्शन कॉल ओवरहेड काफी छोटा है। यह अतिरिक्त अनुकूलन है जो इनलाइनिंग को सक्षम कर सकता है जो लाभकारी हैं।

सामान्य उपसर्ग उन्मूलन

यदि कोड का एक निश्चित ब्लॉक दो बार समान मूल्य की गणना करता है, तो कंपाइलर उसी गणना के एकल उदाहरण के साथ प्रतिस्थापित कर सकता है। उदाहरण के लिए, यदि आप करते हैं

(sum xs + 1) / (sum xs + 2)

तब कंपाइलर इसे ऑप्टिमाइज़ कर सकता है

let s = sum xs in (s+1)/(s+2)

आप उम्मीद कर सकते हैं कि कंपाइलर हमेशा ऐसा करेगा । हालांकि, जाहिरा तौर पर कुछ स्थितियों में इसका परिणाम खराब प्रदर्शन हो सकता है, बेहतर नहीं, इसलिए जीएचसी हमेशा ऐसा नहीं करता है । सच कहूँ तो, मैं वास्तव में इस एक के पीछे के विवरण को नहीं समझता। लेकिन नीचे की रेखा है, अगर यह परिवर्तन आपके लिए महत्वपूर्ण है, तो इसे मैन्युअल रूप से करना मुश्किल नहीं है। (और अगर यह महत्वपूर्ण नहीं है, तो आप इसके बारे में चिंता क्यों कर रहे हैं?)

केस के भाव

निम्नलिखित को धयान मे रखते हुए:

foo (0:_ ) = "zero"
foo (1:_ ) = "one"
foo (_:xs) = foo xs
foo (  []) = "end"

पहले तीन समीकरण सभी जाँचते हैं कि क्या सूची गैर-रिक्त है (अन्य चीजों के बीच)। लेकिन एक ही चीज को तीन बार चेक करना बेकार है। सौभाग्य से, यह संकलक के लिए बहुत से नेस्टेड केस अभिव्यक्तियों में इसे अनुकूलित करना बहुत आसान है। इस मामले में, कुछ इस तरह

foo xs =
  case xs of
    y:ys ->
      case y of
        0 -> "zero"
        1 -> "one"
        _ -> foo ys
    []   -> "end"

यह कम सहज है, लेकिन अधिक कुशल है। क्योंकि संकलक इस परिवर्तन को आसानी से कर सकता है, आपको इसके बारे में चिंता करने की आवश्यकता नहीं है। बस संभव सबसे सहज तरीके से अपने पैटर्न मिलान लिखें; कंपाइलर इसे रीक्रिएट करने में बहुत अच्छा है और इसे जितना जल्दी संभव हो सके उतना फिर से व्यवस्थित करने के लिए।

विलय

सूची प्रसंस्करण के लिए मानक हास्केल मुहावरा एक साथ कार्य करने वाली श्रृंखला है जो एक सूची लेती है और एक नई सूची तैयार करती है। विहित उदाहरण

map g . map f

दुर्भाग्य से, जबकि आलसी आवश्यक काम लंघन की गारंटी देता है, मध्यवर्ती सूची एसएपी प्रदर्शन के लिए सभी आवंटन और सौदे। "फ्यूजन" या "वनों की कटाई" वह जगह है जहां संकलक इन मध्यवर्ती चरणों को खत्म करने की कोशिश करता है।

परेशानी यह है कि इनमें से अधिकांश कार्य पुनरावर्ती हैं। पुनरावृत्ति के बिना, यह सभी कार्यों को एक बड़े कोड ब्लॉक में स्क्वैश करने के लिए इनलाइन करने के लिए एक प्रारंभिक अभ्यास होगा, इस पर सरलीकृत चलाएं और कोई मध्यवर्ती सूचियों के साथ वास्तव में इष्टतम कोड का उत्पादन करें। लेकिन पुनरावृत्ति के कारण, यह काम नहीं करेगा।

{-# RULE #-}इसमें से कुछ को ठीक करने के लिए आप प्रैग्मस का उपयोग कर सकते हैं । उदाहरण के लिए,

{-# RULES "map/map" forall f g xs. map f (map g xs) = map (f.g) xs #-}

अब हर बार जीएचसी mapलागू होता हैmap , तो यह सूची में एक ही पास में अंतरिम सूची को समाप्त कर देता है।

परेशानी है, यह केवल mapउसके बाद के लिए काम करता है map। कई अन्य संभावनाएं हैं - mapके बाद filter, filterजिसके बादmap , आदि हाथ से कोड के बजाय उनमें से प्रत्येक के लिए एक समाधान, तथाकथित "स्ट्रीम फ्यूजन" का आविष्कार किया गया था। यह एक अधिक जटिल चाल है, जिसका मैं यहां वर्णन नहीं करूंगा।

इसका लंबा और छोटा होना: ये प्रोग्रामर द्वारा लिखे गए सभी विशेष अनुकूलन ट्रिक्स हैं । जीएचसी ही फ्यूजन के बारे में कुछ नहीं जानता है; यह सभी सूची पुस्तकालय और अन्य कंटेनर पुस्तकालयों में है। तो क्या अनुकूलन होता है यह इस बात पर निर्भर करता है कि आपके कंटेनर पुस्तकालयों को कैसे लिखा जाता है (या, अधिक वास्तविक रूप से, जो पुस्तकालय आप उपयोग करना चाहते हैं)।

उदाहरण के लिए, यदि आप हास्केल '98 सरणियों के साथ काम करते हैं, तो किसी भी प्रकार के संलयन की उम्मीद न करें। लेकिन मैं समझता हूं कि vectorपुस्तकालय में व्यापक संलयन क्षमताएं हैं। यह सभी पुस्तकालयों के बारे में है; संकलक सिर्फ RULESप्रज्ञा प्रदान करता है । (जो बेहद शक्तिशाली है, वैसे। एक पुस्तकालय लेखक के रूप में, आप इसका उपयोग ग्राहक कोड को फिर से लिखने के लिए कर सकते हैं!)


मेटा:

  • मैं "कोड पहले, प्रोफ़ाइल दूसरे, तीसरे का अनुकूलन" कहने वाले लोगों से सहमत हूं।

  • मैं यह कहते हुए भी लोगों से सहमत हूं कि "किसी दिए गए डिज़ाइन के निर्णय की लागत कितनी है, इसके लिए एक मानसिक मॉडल होना उपयोगी है"।

सभी चीजों में संतुलन, और वह सब ...


9
it's something guaranteed by the language specification ... work is not performed until you "do something" with the result.- बिल्कुल नहीं। भाषा कल्पना गैर-सख्त शब्दार्थ का वादा करती है ; यह इस बारे में कुछ भी वादा नहीं करता है कि क्या शानदार काम किया जाएगा या नहीं।
डैन बर्टन

1
@DanBurton ज़रूर। लेकिन कुछ वाक्यों में समझाना आसान नहीं है। इसके अलावा, चूंकि जीएचसी लगभग एकमात्र हस्केल कार्यान्वयन है, इसलिए तथ्य यह है कि जीएचसी आलसी है ज्यादातर लोगों के लिए पर्याप्त है।
MathematicalOrchid

@MathematicalOrchid: सट्टा मूल्यांकन एक दिलचस्प प्रतिधारण है, हालांकि मैं सहमत हूँ कि यह शायद एक शुरुआत के लिए बहुत अधिक है।
बेन मिलवुड ऑक्ट

5
सीएसई के बारे में: मेरी धारणा यह है कि यह लगभग कभी नहीं किया जाता है, क्योंकि यह अवांछित साझाकरण और इसलिए स्पेसकॉल का परिचय दे सकता है।
जोआचिम ब्रेइटनर

2
क्षमा करें (a) अब से पहले उत्तर नहीं दे रहा है और (b) आपके उत्तर को स्वीकार नहीं कर रहा है। जो लंबा और प्रभावशाली है, लेकिन उस क्षेत्र को कवर नहीं करता है जो मैं चाहता था। मैं निम्न-स्तरीय परिवर्तनों की एक सूची बनाना चाहता हूं, जैसे लैम्ब्डा / लेट / केस-फ्लोटिंग, टाइप / कंस्ट्रक्टर / फंक्शन तर्क विशेषज्ञता, सख्ती विश्लेषण और अनबॉक्सिंग (जिसका आप उल्लेख करते हैं), वर्कर / रैपर, और जो भी आरएचसीएच, के साथ करता है। इनपुट और आउटपुट कोड के स्पष्टीकरण और उदाहरणों के साथ, और आदर्श रूप से उनके संयुक्त प्रभाव और उन लोगों के उदाहरण जहां रूपांतरण नहीं होते हैं। मुझे लगता है कि मुझे एक इनाम देना चाहिए?
glaebhoerl

8

यदि केवल एक ही स्थान पर v = rhs का उपयोग किया जाता है, तो आप इसे इनलाइन करने के लिए संकलक पर भरोसा कर सकते हैं, भले ही rhs बड़ा हो।

अपवाद (जो वर्तमान प्रश्न के संदर्भ में लगभग एक नहीं है) काम के दोहराव को लांबडास करना है। विचार करें:

let v = rhs
    l = \x-> v + x
in map l [1..100]

वहाँ inlining v खतरनाक होगा क्योंकि एक (वाक्यात्मक) का उपयोग 99 अतिरिक्त मूल्यांकन में rhs में होगा। हालाँकि, इस मामले में, आप इसे मैन्युअल रूप से या तो इनलाइन नहीं करना चाहते हैं। तो अनिवार्य रूप से आप नियम का उपयोग कर सकते हैं:

यदि आप एक ऐसे नाम पर विचार कर रहे हैं जो केवल एक बार दिखाई देता है, तो कंपाइलर इसे वैसे भी करेगा।

एक सुखी कोरोलरी के रूप में, एक लंबे समय के कथन (स्पष्टता प्राप्त करने की आशा के साथ) को अनिवार्य रूप से मुक्त करने के लिए एक बाइंडिंग का उपयोग करें।

यह समुदाय से आता है ।haskell.org/~simonmar/papers/inline.pdf जिसमें इनलाइनिंग के बारे में बहुत अधिक जानकारी शामिल है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.