2017-08-13 13 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 

Tôi có một thông dịch viên lớn hơn nhiều, có hành vi hiệu suất mà tôi đang cố hiểu rõ hơn vì vậy tôi đã thực hiện ở trên. Bây giờ tôi chắc chắn rằng quy mô siêu thời gian tôi thấy trong một số ví dụ liên quan đến cách nó sử dụng chồng, nhưng tôi không chắc tại sao điều này lại xảy ra nên tôi muốn hỏi ở đây.Tại sao việc sử dụng sâu của ngăn xếp gây ra hành vi thời gian siêu tuyến tính cho một thông dịch viên đơn giản?

Như bạn có thể thấy, khi tôi thay đổi n x 2 lần, thời gian thay đổi nhiều hơn thế và có vẻ như tỷ lệ là số mũ gây ngạc nhiên cho tôi. Ngoài ra nó là đáng ngạc nhiên rằng các thông dịch viên CPS'd là nhanh hơn so với stack dựa trên một. Tại sao vậy?

Tôi cũng muốn hỏi xem liệu tôi có thể thấy hành vi tương tự này nếu tôi mã hóa tương đương với ở trên bằng ngôn ngữ không phải .NET hoặc thậm chí là C không?

Trả lời

6

Có vẻ như hầu hết những gì bạn đang đo lường đang tạo cấu trúc dữ liệu. Yếu tố ra

let data = add n 

ngoài đo lường thời gian (và thay thế add n với data bên trong) và CPS đi tuyến tính.

Tôi không biết đủ về các chủ đề có ngăn xếp lớn và hiệu suất bộ nhớ để giải thích phần còn lại một cách tự nhiên và không ghi lại bộ nhớ để cảm nhận.

+1

Chà, điều này cũng được phát hiện. Tôi chắc chắn không có ý định đo tòa nhà của cây. Tôi rất vui vì tôi đã đăng câu hỏi này ngay bây giờ. Ngoài ra tôi rất vui khi nhận ra rằng tôi có thể đạt được hiệu suất tốt hơn nhiều bằng cách chuyển đổi thông dịch viên của tôi sang CPS. Các câu hỏi về lý do tại sao phiên bản stack mất quá lâu vẫn còn cho lấy. –

0

Tôi đã làm một số công việc thám tử và có thể trả lời rằng lý do cho thời gian chạy quá lâu cho trình thông dịch dựa trên stack là GC. Điều đầu tiên tôi đã cố gắng đã được biên soạn chương trình trong chế độ 32-bit và rất ngạc nhiên khi phát hiện ra rằng tôi có những timings:

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 

Như bạn có thể thấy, người phiên dịch ngăn xếp dựa được 2x nhanh hơn so với 64-bit chế độ. Tôi đã xóa thông dịch viên của CPS'd khỏi điểm chuẩn và chạy chương trình với PerfView tool. Giả thuyết ban đầu của tôi là thời gian chạy quá mức là do GC.

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% 

Thực tế là đúng. Tôi đọc rằng GC phải đi bộ ngăn xếp trước mỗi bộ sưu tập và có ý nghĩa mạnh mẽ cho chương trình nên được cấu trúc trong .NET, nhưng tôi không hiểu GC đủ tốt để bình luận về lý do phụ thuộc giữa các kiểu dữ liệu bị bỏ lại một mình.

Phép đo ở trên dành cho chế độ 32 bit. Với công cụ PerfView, các phép đo 64 bit bị hỏng và mất gấp 15 lần để hoàn thành vì lý do không xác định.

Tôi cũng không thể giải thích tại sao chế độ 32 bit nhanh gấp 2 lần so với điểm chuẩn ban đầu vì nó không giống như ngăn xếp sẽ lớn gấp 2 lần so với chế độ 64 bit.

Các vấn đề liên quan