2017-11-06 27 views
7

मैंने फ़ंक्शन संरचना और एप्लिकेशन और इसे करने के विभिन्न तरीकों के बीच समानताएं और अंतर के बारे में कुछ सवाल देखा, लेकिन एक चीज जिसने मुझे थोड़ा पहेली शुरू कर दिया है (और जहां तक ​​मैंने खोजा नहीं है) के बारे में है प्रदर्शन में अंतर।हास्केल प्रदर्शन: संरचना बनाम आवेदन?

जब मैंने एफ # सीखा तो मुझे पाइप ऑपरेटर |> से प्यार हो गया, जिसकी हैशेल के रिवर्स एप्लिकेशन & में बराबर है। लेकिन एफ # संस्करण मेरी राय में अधिक सुन्दर रूप से अधिक सुंदर है (और मुझे नहीं लगता कि मैं अकेला हूं)।

अब, एक आसानी से Haskell में पाइप ऑपरेटर हैक कर सकते हैं:

(|>) x f = f x 

और यह एक आकर्षण की तरह काम करता है! समस्या सुलझ गयी!

पाइप (एफ # और हमारे हैकेल चाल दोनों में) के बीच बड़ा अंतर यह है कि यह फ़ंक्शंस तैयार नहीं करता है, यह फ़ंक्शन एप्लिकेशन पर आधारित है। यह बाईं ओर मान लेता है और इसे दाईं ओर फ़ंक्शन में पास करता है, क्योंकि संरचना के विपरीत, जो 2 फ़ंक्शन लेता है और एक और फ़ंक्शन देता है, जिसे तब किसी भी नियमित फ़ंक्शन के रूप में उपयोग किया जा सकता है।

यह, कम से कम मेरे लिए, कोड को बहुत सुंदर बनाता है क्योंकि आप सूचना के प्रवाह को निर्देशित करने के लिए केवल एक ऑपरेटर का उपयोग करते हैं, जो अंतिम संरचना (या >>>) के साथ तर्कों से अंतिम कार्य तक होता है, क्योंकि आप मूल संरचना (या >>>) नहीं कर सकते बाएं तरफ एक मूल्य डालें ताकि इसे "चेन" को पार कर लिया जा सके।

लेकिन एक प्रदर्शन के दृष्टिकोण से, इन सामान्य विकल्पों को देखते हुए, परिणाम ठीक उसी होना चाहिए:

f x = x |> func1 |> func2 |> someLambda |> someMap |> someFold |> show 

f x = x & (func1 >>> func2 >>> someLambda >>> someMap >>> someFold >>> show) 

f x = (func1 >>> func2 >>> someLambda >>> someMap >>> someFold >>> show) x 

कौन सा सबसे तेजी से हो सकता है, एक बार-बार आवेदन या एक (रों) के आधार पर संरचना और एक आवेदन पर आधार?

+5

आप परीक्षण कर देखने के लिए जो तेजी से होता है की कोशिश की है? – AJFarmar

+0

@AJFarmar मैंने इसके बारे में सोचा लेकिन क्या हैकेल में निष्पादन समय मापने का कोई तरीका है? –

+3

यदि हैकेल में निष्पादन समय को मापने का कोई तरीका नहीं है, तो आपका प्रश्न "इनमें से कौन सा तेज़ है" असंभव होगा। – amalloy

उत्तर

9

(|>) और (>>>) को रेखांकित होने तक कोई अंतर नहीं होना चाहिए।

==================== Tidy Core ==================== 
Result size of Tidy Core = {terms: 82, types: 104, coercions: 0} 

-- RHS size: {terms: 9, types: 11, coercions: 0} 
>>>_rqe 
>>>_rqe = 
    \ @ a_a1cE @ b_a1cF @ c_a1cG f_aqr g_aqs x_aqt -> 
    g_aqs (f_aqr x_aqt) 

-- RHS size: {terms: 2, types: 0, coercions: 0} 
$trModule1_r1gR 
$trModule1_r1gR = TrNameS "main"# 

-- RHS size: {terms: 2, types: 0, coercions: 0} 
$trModule2_r1h6 
$trModule2_r1h6 = TrNameS "Main"# 

-- RHS size: {terms: 3, types: 0, coercions: 0} 
$trModule 
$trModule = Module $trModule1_r1gR $trModule2_r1h6 

-- RHS size: {terms: 58, types: 73, coercions: 0} 
main 
main = 
    >> 
    $fMonadIO 
    (>>= 
     $fMonadIO 
     getLine 
     (. putStrLn 
      (>>>_rqe 
      (>>>_rqe (filter isUpper) (length $fFoldable[])) 
      (show $fShowInt)))) 
    (>> 
     $fMonadIO 
     (>>= 
      $fMonadIO 
      getLine 
      (. putStrLn 
      (\ x_a10M -> 
       show $fShowInt (length $fFoldable[] (filter isUpper x_a10M))))) 
     (>> 
      $fMonadIO 
      (>>= 
      $fMonadIO 
      getLine 
      (. putStrLn 
       (. (show $fShowInt) (. (length $fFoldable[]) (filter isUpper))))) 
      (>>= 
      $fMonadIO 
      getLine 
      (. putStrLn 
       (\ x_a10N -> 
        show $fShowInt (length $fFoldable[] (filter isUpper x_a10N))))))) 

-- RHS size: {terms: 2, types: 1, coercions: 0} 
main 
main = runMainIO main 

तो >>> नहीं करता है: हम हम मिल -ddump-simpl -dsuppress-all -O0 के साथ हमारे कोड संकलन तो

import Data.Char (isUpper) 

{-# INLINE (|>) #-} 
(|>) :: a -> (a -> b) -> b 
(|>) x f = f x 

{-# INLINE (>>>) #-} 
(>>>) :: (a -> b) -> (b -> c) -> a -> c 
(>>>) f g x = g (f x) 

compositionF :: String -> String 
compositionF = filter isUpper >>> length >>> show 

applicationF :: String -> String 
applicationF x = x |> filter isUpper |> length |> show 

compositionH :: String -> String 
compositionH = show . length . filter isUpper 

applicationH :: String -> String 
applicationH x = show $ length $ filter isUpper $ x 

main :: IO() 
main = do 
    getLine >>= putStrLn . compositionF -- using the functions 
    getLine >>= putStrLn . applicationF -- to make sure that 
    getLine >>= putStrLn . compositionH -- we actually get the 
    getLine >>= putStrLn . applicationH -- corresponding GHC core 

: चलो एक उदाहरण के चार अलग-अलग काम करता है, एफ # शैली में दो, और हास्केल शैली में दो का उपयोग करता है लिखते हैं अगर हम अनुकूलन सक्षम नहीं करते हैं तो इनलाइन नहीं किया जाएगा। अगर हम अनुकूलन सक्षम करते हैं, तो आपको >>> या (.) बिल्कुल नहीं दिखाई देगा। हमारे कार्य थोड़ा अलग हैं, क्योंकि (.) उस चरण में रेखांकित नहीं होते हैं, लेकिन यह कुछ हद तक अपेक्षित है।

हम अपने कार्यों के लिए {-# NOINLINE … #-} जोड़ सकते हैं और optimiziations हम देखते हैं कि चार कार्य बिल्कुल अलग नहीं होगा सक्षम करते हैं:

$ ghc -ddump-simpl -dsuppress-all -O2 Example.hs 
[1 of 1] Compiling Main    (Example.hs, Example.o) 

==================== Tidy Core ==================== 
Result size of Tidy Core = {terms: 261, types: 255, coercions: 29} 

-- RHS size: {terms: 2, types: 0, coercions: 0} 
$trModule2 
$trModule2 = TrNameS "main"# 

-- RHS size: {terms: 2, types: 0, coercions: 0} 
$trModule1 
$trModule1 = TrNameS "Main"# 

-- RHS size: {terms: 3, types: 0, coercions: 0} 
$trModule 
$trModule = Module $trModule2 $trModule1 

Rec { 
-- RHS size: {terms: 29, types: 20, coercions: 0} 
$sgo_r574 
$sgo_r574 = 
    \ sc_s55y sc1_s55x -> 
    case sc1_s55x of _ { 
     [] -> I# sc_s55y; 
     : y_a2j9 ys_a2ja -> 
     case y_a2j9 of _ { C# c#_a2hF -> 
     case {__pkg_ccall base-4.9.1.0 u_iswupper Int# 
            -> State# RealWorld -> (# State# RealWorld, Int# #)}_a2hE 
       (ord# c#_a2hF) realWorld# 
     of _ { (# ds_a2hJ, ds1_a2hK #) -> 
     case ds1_a2hK of _ { 
      __DEFAULT -> $sgo_r574 (+# sc_s55y 1#) ys_a2ja; 
      0# -> $sgo_r574 sc_s55y ys_a2ja 
     } 
     } 
     } 
    } 
end Rec } 

-- RHS size: {terms: 15, types: 14, coercions: 0} 
applicationH 
applicationH = 
    \ x_a12X -> 
    case $sgo_r574 0# x_a12X of _ { I# ww3_a2iO -> 
    case $wshowSignedInt 0# ww3_a2iO [] 
    of _ { (# ww5_a2iS, ww6_a2iT #) -> 
    : ww5_a2iS ww6_a2iT 
    } 
    } 

Rec { 
-- RHS size: {terms: 29, types: 20, coercions: 0} 
$sgo1_r575 
$sgo1_r575 = 
    \ sc_s55r sc1_s55q -> 
    case sc1_s55q of _ { 
     [] -> I# sc_s55r; 
     : y_a2j9 ys_a2ja -> 
     case y_a2j9 of _ { C# c#_a2hF -> 
     case {__pkg_ccall base-4.9.1.0 u_iswupper Int# 
            -> State# RealWorld -> (# State# RealWorld, Int# #)}_a2hE 
       (ord# c#_a2hF) realWorld# 
     of _ { (# ds_a2hJ, ds1_a2hK #) -> 
     case ds1_a2hK of _ { 
      __DEFAULT -> $sgo1_r575 (+# sc_s55r 1#) ys_a2ja; 
      0# -> $sgo1_r575 sc_s55r ys_a2ja 
     } 
     } 
     } 
    } 
end Rec } 

-- RHS size: {terms: 15, types: 15, coercions: 0} 
compositionH 
compositionH = 
    \ x_a1jF -> 
    case $sgo1_r575 0# x_a1jF of _ { I# ww3_a2iO -> 
    case $wshowSignedInt 0# ww3_a2iO [] 
    of _ { (# ww5_a2iS, ww6_a2iT #) -> 
    : ww5_a2iS ww6_a2iT 
    } 
    } 

Rec { 
-- RHS size: {terms: 29, types: 20, coercions: 0} 
$sgo2_r576 
$sgo2_r576 = 
    \ sc_s55k sc1_s55j -> 
    case sc1_s55j of _ { 
     [] -> I# sc_s55k; 
     : y_a2j9 ys_a2ja -> 
     case y_a2j9 of _ { C# c#_a2hF -> 
     case {__pkg_ccall base-4.9.1.0 u_iswupper Int# 
            -> State# RealWorld -> (# State# RealWorld, Int# #)}_a2hE 
       (ord# c#_a2hF) realWorld# 
     of _ { (# ds_a2hJ, ds1_a2hK #) -> 
     case ds1_a2hK of _ { 
      __DEFAULT -> $sgo2_r576 (+# sc_s55k 1#) ys_a2ja; 
      0# -> $sgo2_r576 sc_s55k ys_a2ja 
     } 
     } 
     } 
    } 
end Rec } 

-- RHS size: {terms: 15, types: 15, coercions: 0} 
compositionF 
compositionF = 
    \ x_a1jF -> 
    case $sgo2_r576 0# x_a1jF of _ { I# ww3_a2iO -> 
    case $wshowSignedInt 0# ww3_a2iO [] 
    of _ { (# ww5_a2iS, ww6_a2iT #) -> 
    : ww5_a2iS ww6_a2iT 
    } 
    } 

Rec { 
-- RHS size: {terms: 29, types: 20, coercions: 0} 
$sgo3_r577 
$sgo3_r577 = 
    \ sc_s55d sc1_s55c -> 
    case sc1_s55c of _ { 
     [] -> I# sc_s55d; 
     : y_a2j9 ys_a2ja -> 
     case y_a2j9 of _ { C# c#_a2hF -> 
     case {__pkg_ccall base-4.9.1.0 u_iswupper Int# 
            -> State# RealWorld -> (# State# RealWorld, Int# #)}_a2hE 
       (ord# c#_a2hF) realWorld# 
     of _ { (# ds_a2hJ, ds1_a2hK #) -> 
     case ds1_a2hK of _ { 
      __DEFAULT -> $sgo3_r577 (+# sc_s55d 1#) ys_a2ja; 
      0# -> $sgo3_r577 sc_s55d ys_a2ja 
     } 
     } 
     } 
    } 
end Rec } 

-- RHS size: {terms: 15, types: 14, coercions: 0} 
applicationF 
applicationF = 
    \ x_a12W -> 
    case $sgo3_r577 0# x_a12W of _ { I# ww3_a2iO -> 
    case $wshowSignedInt 0# ww3_a2iO [] 
    of _ { (# ww5_a2iS, ww6_a2iT #) -> 
    : ww5_a2iS ww6_a2iT 
    } 
    } 
... 

सभी go कार्यों बिल्कुल वैसा ही (बिना चर नाम) हैं, और application* है composition* जैसा ही है। तो आगे बढ़ें और हास्केल में अपना स्वयं का एफ # प्रस्ताव बनाएं, कोई प्रदर्शन समस्या नहीं होनी चाहिए।

5

मेरा उत्तर एफ # के बारे में है।

ज्यादातर मामलों में एफ # संकलक एक ही कोड में पाइपलाइनों का अनुकूलन करने में सक्षम है:

let f x = x |> (+) 1 |> (*) 2 |> (+) 2 
let g x = x |> ((+) 1 >> (*) 2 >> (+) 2) 

Decompiling f और g हम देखते हैं कि संकलक एक ही परिणाम तक पहुँच जाता है:

public static int f(int x) 
{ 
    return 2 + 2 * (1 + x); 
} 
public static int g(int x) 
{ 
    return 2 + 2 * (1 + x); 
} 

लेकिन यह

let f x = x |> Array.map add1 |> Array.map mul2 |> Array.map add2 |> Array.reduce (+) 
let g x = x |> (Array.map add1 >> Array.map mul2 >> Array.map add2 >> Array.reduce (+)) 
0: हमेशा धारण करने के लिए के रूप में हम थोड़ा और अधिक उन्नत पाइपलाइनों के साथ देख सकते हैं नहीं लगता है

Decompiling कुछ मतभेद पता चलता है:

public static int f(int[] x) 
{ 
    FSharpFunc<int, FSharpFunc<int, int>> arg_25_0 = new [email protected](); 
    if (x == null) 
    { 
    throw new ArgumentNullException("array"); 
    } 
    int[] array = new int[x.Length]; 
    FSharpFunc<int, FSharpFunc<int, int>> fSharpFunc = arg_25_0; 
    for (int i = 0; i < array.Length; i++) 
    { 
    array[i] = x[i] + 1; 
    } 
    FSharpFunc<int, FSharpFunc<int, int>> arg_6C_0 = fSharpFunc; 
    int[] array2 = array; 
    if (array2 == null) 
    { 
    throw new ArgumentNullException("array"); 
    } 
    array = new int[array2.Length]; 
    fSharpFunc = arg_6C_0; 
    for (int i = 0; i < array.Length; i++) 
    { 
    array[i] = array2[i] * 2; 
    } 
    FSharpFunc<int, FSharpFunc<int, int>> arg_B3_0 = fSharpFunc; 
    int[] array3 = array; 
    if (array3 != null) 
    { 
    array2 = new int[array3.Length]; 
    fSharpFunc = arg_B3_0; 
    for (int i = 0; i < array2.Length; i++) 
    { 
     array2[i] = array3[i] + 2; 
    } 
    return ArrayModule.Reduce<int>(fSharpFunc, array2); 
    } 
    throw new ArgumentNullException("array"); 
} 

public static int g(int[] x) 
{ 
    FSharpFunc<int[], int[]> f = new [email protected](); 
    FSharpFunc<int[], int[]> fSharpFunc = new [email protected](f); 
    FSharpFunc<int, FSharpFunc<int, int>> reduction = new [email protected](); 
    int[] array = fSharpFunc.Invoke(x); 
    return ArrayModule.Reduce<int>(reduction, array); 
} 

लिए f एफ # फाइनल के लिए कम करने को छोड़कर पाइपलाइन inlines।

g के लिए पाइप लाइन का निर्माण किया है और उसके बाद उठे। इसका मतलब है कि g कुछ हद तक धीमी है और कुछ हद तक अधिक स्मृति f से अधिक गहन है।

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

तो महत्वपूर्ण प्रदर्शन आप के लिए महत्वपूर्ण है मैं एक अच्छा decompiler उपकरण सुनिश्चित करें कि उत्पन्न कोड अप्रत्याशित ओवरहेड शामिल नहीं है बनाने के लिए हो रही करने की सलाह देते है। अन्यथा आप शायद किसी भी दृष्टिकोण के साथ ठीक हैं।

+1

क्या वे सभी बदसूरत 'null' चेक इनलाइन 'Array.map' फ़ंक्शन से हैं, या वे केवल पाइपलाइन द्वारा उत्पन्न होते हैं? – leftaroundabout

+2

'यदि आपके लिए महत्वपूर्ण प्रदर्शन महत्वपूर्ण है, तो मैं यह सुनिश्चित करने के लिए एक अच्छा डिकंपेलर टूल प्राप्त करने की अनुशंसा करता हूं कि जेनरेट किए गए कोड में अप्रत्याशित ओवरहेड न हो'- और उसके बाद एक स्पष्ट टिप्पणी दें कि कोड ऐसा तरीका क्यों है जिससे कोई' टी अतिरंजक इसे refactor। – scrwtp

+0

@leftaroundabout वे 'मॉड्यूल Array' से inlined कार्यों का उपयोग करने वाले' checkNonNull "सरणी" से हो रहा है कई कार्यों में array'। एक बेहतर संकलित यह पता लगा सकता है कि इनमें से कई चेक अनावश्यक हैं और उन्हें हटा दें। यदि हम भाग्यशाली हैं तो जिटर उन्हें हमारे लिए हटा देता है। – FuleSnabel

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