2017-02-11 12 views
19

यह मेरे previous question पर एक फॉलो-अप है जिस पर मैंने पूछा कि क्यों स्ट्रीम संलयन एक निश्चित कार्यक्रम में लात मार रहा था। समस्या को हल करता था कि कुछ कार्यों को रेखांकित नहीं किया गया था, और INLINE ध्वज ने प्रदर्शन को 17x (जो इनलाइनिंग के महत्व को दिखाता है!) द्वारा प्रदर्शन में सुधार किया।क्या रिकर्सिव फ़ंक्शन इनलाइन करने का कोई तरीका है?

अब, ध्यान दें कि, मूल प्रश्न पर, मैंने 64incAll की कॉल को एक बार में हार्डकोड किया। अब, मान लीजिए कि, बजाय, मैं एक nTimes समारोह है, जो बार-बार एक समारोह कॉल बनाने के लिए:

module Main where 

import qualified Data.Vector.Unboxed as V 

{-# INLINE incAll #-} 
incAll :: V.Vector Int -> V.Vector Int 
incAll = V.map (+ 1) 

{-# INLINE nTimes #-} 
nTimes :: Int -> (a -> a) -> a -> a 
nTimes 0 f x = x 
nTimes n f x = f (nTimes (n-1) f x) 

main :: IO() 
main = do 
    let size = 100000000 :: Int 
    let array = V.replicate size 0 :: V.Vector Int 
    print $ V.sum (nTimes 64 incAll array) 

इस मामले में, बस nTimes के लिए एक INLINE pragma जोड़ने में मदद नहीं करेगा, क्योंकि AFAIK GHC पुनरावर्ती इनलाइन नहीं है कार्य करता है। संकलन समय पर nTimes का विस्तार करने के लिए जीएचसी को मजबूर करने के लिए कोई चाल है और इस प्रकार, अपेक्षित प्रदर्शन को पुनर्प्राप्त कर सकते हैं?

+2

आप बार-बार एप्लिकेशन का विस्तार करने के लिए वाक्यविन्यास शुरू करने के लिए टेम्पलेट हास्केल का उपयोग कर सकते हैं। –

+1

@ जोचिमब्रेटनर ने बस ऐसा करना समाप्त कर दिया। टेम्पलेट हास्केल सीखना था। अभी भी मेरे जवाब का परीक्षण कर रहा है, लेकिन यह बहुत तेज लगता है (दूसरे प्रश्न के समान)। – Zeta

उत्तर

26

नहीं, लेकिन आप बेहतर कार्यों का उपयोग कर सकते हैं। मैं V.map (+64) के बारे में बात नहीं कर रहा हूं, जो चीजों को निश्चित रूप से बहुत तेज बना देगा, लेकिन लगभग nTimes

{-# INLINE nTimesFoldr #-} 
nTimesFoldr :: Int -> (a -> a) -> a -> a  
nTimesFoldr n f x = foldr (.) id (replicate n f) $ x 

{-# INLINE nTimesIterate #-} 
nTimesIterate :: Int -> (a -> a) -> a -> a  
nTimesIterate n f x = iterate f x !! n 

{-# INLINE nTimesTail #-} 
nTimesTail :: Int -> (a -> a) -> a -> a  
nTimesTail n f = go n 
    where 
    {-# INLINE go #-} 
    go n x | n <= 0 = x 
    go n x   = go (n - 1) (f x) 

सभी संस्करणों लगभग 8 सेकंड लग गया, 40 सेकंड अपने संस्करणों लेने की तुलना में: हम तीन उम्मीदवारों कि पहले से ही क्या nTimes करता है है। जोआचिम के संस्करण में भी 8 सेकंड लगते हैं। ध्यान दें कि iterate संस्करण मेरे सिस्टम पर अधिक स्मृति लेता है। जबकि जीएचसी के लिए unroll plugin है, यह पिछले पांच वर्षों में अपडेट नहीं किया गया है (यह कस्टम एएनोटेशन का उपयोग करता है)।

कोई भी अनलॉक नहीं है?

हालांकि, इससे पहले कि हम निराशा से पहले, जीएचसी वास्तव में सबकुछ इनलाइन करने का प्रयास कैसे करता है? के nTimesTail और nTimes 1 उपयोग करते हैं:

module Main where 
import qualified Data.Vector.Unboxed as V 

{-# INLINE incAll #-} 
incAll :: V.Vector Int -> V.Vector Int 
incAll = V.map (+ 1) 

{-# INLINE nTimes #-} 
nTimes :: Int -> (a -> a) -> a -> a  
nTimes n f = go n 
    where 
    {-# INLINE go #-} 
    go n x | n <= 0 = x 
    go n x   = go (n - 1) (f x) 

main :: IO() 
main = do 
    let size = 100000000 :: Int 
    let array = V.replicate size 0 :: V.Vector Int 
    print $ V.sum (nTimes 1 incAll array) 
$ stack ghc --package vector -- -O2 -ddump-simpl -dsuppress-all SO.hs 
main2 = 
    case (runSTRep main3) `cast` ... 
    of _ { Vector ww1_s9vw ww2_s9vx ww3_s9vy -> 
    case $wgo 1 ww1_s9vw ww2_s9vx ww3_s9vy 
    of _ { (# ww5_s9w3, ww6_s9w4, ww7_s9w5 #) -> 

हम वहीं रोक सकता है। $wgo ऊपर परिभाषित go है। यहां तक ​​कि 1 जीएचसी लूप को अनलोल नहीं करता है। 1 एक स्थिर है क्योंकि यह परेशान है। बचाव

को

टेम्पलेट्स लेकिन अफसोस, यह शून्य के लिए सब कुछ नहीं है। यदि सी ++ प्रोग्रामर संकलन समय स्थिरांक के लिए निम्नलिखित करने में सक्षम हैं, तो क्या हमें सही चाहिए?

template <int N> 
struct Call{ 
    template <class F, class T> 
    static T call(F f, T && t){ 
     return f(Call<N-1>::call(f,std::forward<T>(t))); 
    } 
}; 
template <> 
struct Call<0>{ 
    template <class F, class T> 
    static T call(F f, T && t){ 
     return t; 
    } 
}; 

और यकीन है कि पर्याप्त, हम कर सकते हैं, TemplateHaskell* साथ:

-- Times.sh 
{-# LANGUAGE TemplateHaskell #-} 
module Times where 

import Control.Monad (when) 
import Language.Haskell.TH 

nTimesTH :: Int -> Q Exp 
nTimesTH n = do 
    f <- newName "f" 
    x <- newName "x" 

    when (n <= 0) (reportWarning "nTimesTH: argument non-positive") 

    let go k | k <= 0 = VarE x 
     go k   = AppE (VarE f) (go (k - 1)) 
    return $ LamE [VarP f,VarP x] (go n) 

क्या nTimesTH क्या करता है? यह एक नया फ़ंक्शन बनाता है जहां पहला नाम f कुल पर दूसरे n बार के लिए लागू किया जा रहा है।

$(nTimesTH 0) = \f x -> x 
$(nTimesTH 1) = \f x -> f x 
$(nTimesTH 2) = \f x -> f (f x) 
$(nTimesTH 3) = \f x -> f (f (f x)) 
... 

यह काम करता है: n अब एक संकलन समय निरंतर है, जो हमें सूट, के बाद से पाश-unrolling संकलन समय स्थिरांक के साथ संभव है की जरूरत है? और क्या यह तेज़ है? nTimes की तुलना में कितनी तेजी से? उस के लिए एक और main कोशिश करते हैं:

-- SO.hs 
{-# LANGUAGE TemplateHaskell #-} 
module Main where 
import Times 
import qualified Data.Vector.Unboxed as V 

{-# INLINE incAll #-} 
incAll :: V.Vector Int -> V.Vector Int 
incAll = V.map (+ 1) 

{-# INLINE nTimes #-} 
nTimes :: Int -> (a -> a) -> a -> a  
nTimes n f = go n 
    where 
    {-# INLINE go #-} 
    go n x | n <= 0 = x 
    go n x   = go (n - 1) (f x) 

main :: IO() 
main = do 
    let size = 100000000 :: Int 
    let array = V.replicate size 0 :: V.Vector Int 
    let vTH = V.sum ($(nTimesTH 64) incAll array) 
    let vNorm = V.sum (nTimes 64 incAll array) 
    print $ vTH == vNorm 
stack ghc --package vector -- -O2 SO.hs && SO.exe +RTS -t 
True 
<<ghc: 52000056768 bytes, 66 GCs, 400034700/800026736 avg/max bytes residency (2 samples), 1527M in use, 0.000 INIT (0.000 elapsed), 8.875 MUT (9.119 elapsed), 0.000 GC (0.094 elapsed) :ghc>> 

यह सही परिणाम अर्जित करता है। यह कितना तेज़ है? चलो एक और main फिर से उपयोग करते हैं:

main :: IO() 
main = do 
    let size = 100000000 :: Int 
    let array = V.replicate size 0 :: V.Vector Int 
    print $ V.sum ($(nTimesTH 64) incAll array) 
 800,048,112 bytes allocated in the heap           
      4,352 bytes copied during GC            
      42,664 bytes maximum residency (1 sample(s))        
      18,776 bytes maximum slop             
      764 MB total memory in use (0 MB lost due to fragmentation)    

            Tot time (elapsed) Avg pause Max pause   
    Gen 0   1 colls,  0 par 0.000s 0.000s  0.0000s 0.0000s   
    Gen 1   1 colls,  0 par 0.000s 0.049s  0.0488s 0.0488s   

    INIT time 0.000s ( 0.000s elapsed)           
    MUT  time 0.172s ( 0.221s elapsed)           
    GC  time 0.000s ( 0.049s elapsed)           
    EXIT time 0.000s ( 0.049s elapsed)           
    Total time 0.188s ( 0.319s elapsed)           

    %GC  time  0.0% (15.3% elapsed)           

    Alloc rate 4,654,825,378 bytes per MUT second         

    Productivity 100.0% of total user, 58.7% of total elapsed   

ठीक है, की तुलना 8s करने के लिए कि। तो टीएल के लिए; डीआर: यदि आपके पास संकलन-समय स्थिरांक हैं, और आप उस स्थिरांक के आधार पर अपना कोड बनाना और/या संशोधित करना चाहते हैं, तो टेम्पलेट हास्केल पर विचार करें।

* कृपया ध्यान दें कि यह मेरा पहला टेम्पलेट हास्केल कोड है जिसे मैंने कभी लिखा है। देखभाल के साथ प्रयोग करें। बहुत बड़े n का उपयोग न करें, या आप एक गड़बड़ समारोह के साथ समाप्त हो सकता है।

+2

नोट: समाधान [कोड समीक्षा के लिए ऊपर है] (https://codereview.stackexchange.com/questions/155144/execute-a-function-n-times-where-n-is-known-at-compile-time)। – Zeta

+0

अरे बस आपको यह बताने के लिए वापस आ रहा है कि यह अधिकांश पहलुओं में एक शानदार जवाब है, धन्यवाद। – MaiaVictor

4

सं

आप

{-# INLINE nTimes #-} 
nTimes :: Int -> (a -> a) -> a -> a 
nTimes n f x = go n 
    where go 0 = x 
     go n = f (go (n-1)) 

लिख सकता है और GHC nTimes इनलाइन होता है, और संभावना पुनरावर्ती go अपने विशेष तर्क incAll और array के विशेषज्ञ है, लेकिन यह पाश नहीं उतारना होगा।

+0

आह जो बेकार है, धन्यवाद। – MaiaVictor

14

एक छोटी सी चाल है जो एंड्रेस ने मुझे बताया है कि आप वास्तव में टाइप कक्षाओं का उपयोग करके रिकर्सिव कार्यों को इनलाइन करने के लिए जीएचसी कहां प्राप्त कर सकते हैं।

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

जीएचसी प्रत्येक रिकर्सिव कॉल को खुशी से रेखांकित करेगा और कुशल कोड तैयार करेगा क्योंकि प्रत्येक रिकर्सिव कॉल एक अलग प्रकार पर है।

मैंने इसे बेंचमार्क नहीं किया है या कोर को देखा है लेकिन यह काफी तेज़ है।

{-# LANGUAGE DataKinds #-} 
{-# LANGUAGE KindSignatures #-} 
{-# LANGUAGE PolyKinds #-} 
{-# LANGUAGE ScopedTypeVariables #-} 
module Main where 

import qualified Data.Vector.Unboxed as V 

data Proxy a = Proxy 

{-# INLINE incAll #-} 
incAll :: V.Vector Int -> V.Vector Int 
incAll = V.map (+ 1) 

oldNTimes :: Int -> (a -> a) -> a -> a 
oldNTimes 0 f x = x 
oldNTimes n f x = f (oldNTimes (n-1) f x) 

-- New definition 

data N = Z | S N 

class Unroll (n :: N) where 
    nTimes :: Proxy n -> (a -> a) -> a -> a 

instance Unroll Z where 
    nTimes _ f x = x 

instance Unroll n => Unroll (S n) where 
    nTimes p f x = 
     let Proxy :: Proxy (S n) = p 
     in f (nTimes (Proxy :: Proxy n) f x) 

main :: IO() 
main = do 
    let size = 100000000 :: Int 
    let array = V.replicate size 0 :: V.Vector Int 
    print $ V.sum (nTimes (Proxy :: Proxy (S (S (S (S (S (S (S (S (S (S (S Z)))))))))))) incAll array) 
    print $ V.sum (oldNTimes 11 incAll array) 
+0

अच्छा, हालांकि यदि आप 'nTimes 64' का उपयोग करना चाहते हैं, तो शब्द 'प्रॉक्सी :: प्रॉक्सी (एस (एस (एस (एस ... (एसजेड) ...)' लिखने के लिए दिलचस्प होगा ... मैं इसका उपयोग करूंगा हालांकि, स्तर अंकगणित टाइप करें।'प्रॉक्सी (दस: *: छः: +: चार) 'की तरह दिख रहा है। – Zeta

+0

मुझे अभी भी उन टाइपक्लास प्रोग्रामिंग शेंगेनियां नहीं मिल सकती हैं, जो कोई भी मेरे लिए स्पष्ट रूप से एक जादूगर है। – MaiaVictor

संबंधित मुद्दे