2017-08-13 50 views
6
type Expr = 
    | Lit of int 
    | Add of Expr * Expr 

let rec intr = function 
    | Lit _ as x -> x 
    | Add(Lit a,Lit b) -> Lit <| a + b 
    | Add(a,b) -> intr <| Add(intr a, intr b) 

let rec intr_cps x ret = 
    match x with 
    | Lit _ as x -> ret x 
    | Add(Lit a,Lit b) -> Lit (a + b) |> ret 
    | Add(a,b) -> 
     intr_cps a <| fun a -> 
      intr_cps b <| fun b -> 
       intr_cps (Add(a, b)) ret 

let rec add n = 
    if n > 1 then Add(Lit 1, add (n-1)) 
    else Lit 1 

open System.Threading 

let mem = 1024*1024*512 // ~536mb 
// It stack overflows without being spun on a separate thread. 
// By default, the program only has a few mb of stack memory at its disposal. 
let run f = Thread(ThreadStart f,mem).Start() 

run <| fun _ -> 
    let f n = 
     let x = add n 
     let stopwatch = System.Diagnostics.Stopwatch.StartNew() 
     printfn "%A" (intr x) 
     printfn "n_%i_std = %A" n stopwatch.Elapsed 

     stopwatch.Restart() 
     printfn "%A" (intr_cps x id) 
     printfn "n_%i_cps = %A" n stopwatch.Elapsed 
    f <| 1000*1000/2 
    f <| 1000*1000 
    f <| 1000*1000*2 

//Lit 500000 
//n_500000_std = 00:00:00.7764730 
//Lit 500000 
//n_500000_cps = 00:00:00.0800371 
//Lit 1000000 
//n_1000000_std = 00:00:02.9531043 
//Lit 1000000 
//n_1000000_cps = 00:00:00.1941828 
//Lit 2000000 
//n_2000000_std = 00:00:13.7823780 
//Lit 2000000 
//n_2000000_cps = 00:00:00.2767752 

मेरे पास एक बहुत बड़ा दुभाषिया है जिसका प्रदर्शन व्यवहार मैं बेहतर समझने की कोशिश कर रहा हूं इसलिए मैंने उपर्युक्त बनाया। मुझे निश्चित रूप से यकीन है कि कुछ उदाहरणों पर मैं उस सुपरलाइनर स्केलिंग को देख रहा हूं, जिस तरह से यह स्टैक का उपयोग करता है, लेकिन मुझे यकीन नहीं है कि यह क्यों हो रहा है इसलिए मैं यहां पूछना चाहता था।स्टैक का गहरा उपयोग क्यों एक साधारण दुभाषिया के लिए superlinear समय व्यवहार का कारण बनता है?

जैसा कि आप देख सकते हैं, क्योंकि मैं n 2x से भिन्न होता हूं, समय उस समय से कहीं अधिक भिन्न होता है, और ऐसा लगता है कि स्केलिंग घातीय है जो मेरे लिए आश्चर्यजनक है। यह भी आश्चर्य की बात है कि सीपीएसड दुभाषिया ढेर आधारित एक से तेज है। ऐसा क्यों है?

मैं यह भी पूछना चाहता हूं कि क्या मैं उपरोक्त के बराबर कोड को गैर .NET भाषा या यहां तक ​​कि सी में कोडित करता हूं?

उत्तर

6

ऐसा लगता है कि आप जो माप रहे हैं, वह डेटा संरचना का निर्माण कर रहा है। बाहर कारक

let data = add n 
समय माप के बाहर

(और data अंदर के साथ add n की जगह) और सीपीएस रैखिक चला जाता है।

मुझे बाकी हिस्सों को समझाने के लिए बड़े ढेर और मेमोरी प्रदर्शन वाले धागे के बारे में पर्याप्त जानकारी नहीं है, और किसी भी अनुभव को पाने के लिए स्मृति को प्रोफाइल नहीं किया है।

+1

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

0

मैंने कुछ जासूसी कार्य किया और जवाब दे सकता है कि स्टैक आधारित दुभाषिया के लिए अत्यधिक लंबे समय तक चलने का कारण जीसी है।

Lit 500000 
n_500000_std = 00:00:00.3964533 
Lit 500000 
n_500000_cps = 00:00:00.0945109 
Lit 1000000 
n_1000000_std = 00:00:01.6021848 
Lit 1000000 
n_1000000_cps = 00:00:00.2143892 
Lit 2000000 
n_2000000_std = 00:00:08.0540017 
Lit 2000000 
n_2000000_cps = 00:00:00.3823931 

आप देख सकते हैं, ढेर आधारित दुभाषिया 2x तेजी से 64-बिट की तुलना में है: पहली बात मैं 32-बिट मोड में कार्यक्रम संकलन किया गया था की कोशिश की और पता लगाने के लिए कि मैं इन समय मिल गया हैरान था मोड। मैंने बेंचमार्क से सीपीएसड दुभाषिया को हटा दिया और PerfView tool के साथ कार्यक्रम चलाया। मेरी प्रारंभिक परिकल्पना यह थी कि अत्यधिक चलने वाले समय जीसी के कारण होते हैं।

CommandLine: "IntepreterBenchmark.exe" 
Runtime Version: V 4.0.30319.0 (built on 6/6/2017 10:30:00 PM) 
CLR Startup Flags: CONCURRENT_GC 
Total CPU Time: 19,306 msec 
Total GC CPU Time: 17,436 msec 
Total Allocs : 202.696 MB 
GC CPU MSec/MB Alloc : 86.020 MSec/MB 
Total GC Pause: 17,421.9 msec 
% Time paused for Garbage Collection: 90.2% 
% CPU Time spent Garbage Collecting: 90.3% 

यह वास्तव में सही था। मैंने पढ़ा है कि जीसी को प्रत्येक संग्रह से पहले ढेर चलना पड़ता है और जिस तरह से प्रोग्राम को .NET में संरचित किया जाना चाहिए, उसके लिए मजबूत प्रभाव पड़ता है, लेकिन मुझे यह समझने के लिए पर्याप्त नहीं है कि डाटाटाइप अकेले ही क्यों रहें।

उपर्युक्त माप 32-बिट मोड के लिए है। PerfView टूल के साथ, 64-बिट माप टूटा हुआ है और अज्ञात कारण के लिए समाप्त होने में 15 गुना लंबा लगा।

मैं यह भी समझा नहीं सकता कि 32-बिट मोड मूल बेंचमार्क पर 2x तेज क्यों है क्योंकि यह स्टैक की तरह नहीं है 64-बिट मोड की तुलना में 2x बड़ा होगा।

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