2012-02-17 49 views
15

Tôi có một khung kiểm tra nhỏ. Nó thực hiện một vòng lặp mà thực hiện như sau:Tăng tốc độ runhaskell

  1. Tạo một tệp nguồn Haskell nhỏ.

  2. Thực hiện việc này với runhaskell. Chương trình tạo ra các tệp đĩa khác nhau.

  3. Xử lý các tệp đĩa vừa tạo.

Điều này xảy ra vài chục lần. Nó chỉ ra rằng runhaskell chiếm phần lớn thời gian thực hiện của chương trình.

Một mặt, thực tế là runhaskell quản lý để tải tệp từ đĩa, mã hóa nó, phân tích cú pháp, phân tích phụ thuộc, tải thêm 20KB văn bản từ đĩa, tokenise và phân tích tất cả điều này, thực hiện suy luận kiểu đầy đủ, kiểm tra loại, desugar để Core, liên kết với mã máy biên dịch, và thực hiện điều trong một thông dịch viên, tất cả bên trong 2 giây của thời gian tường, thực sự là khá damned ấn tượng khi bạn nghĩ về nó. Mặt khác, tôi vẫn muốn làm cho nó đi nhanh hơn. ;-)

Biên dịch trình kiểm tra (chương trình chạy vòng lặp trên) đã tạo ra sự khác biệt nhỏ về hiệu suất. Biên dịch 20KB mã thư viện mà các tập lệnh liên kết chống lại việc tạo ra một cải tiến khá đáng chú ý hơn. Nhưng nó vẫn mất khoảng 1 giây cho mỗi yêu cầu của runhaskell.

Tệp Haskell được tạo chỉ hơn 1KB mỗi tệp, nhưng chỉ một phần của tệp thực sự thay đổi. Có lẽ biên dịch tập tin và sử dụng chuyển đổi -e của GHC sẽ nhanh hơn?

Ngoài ra, có thể đó là phí tổn liên tục tạo và phá hủy nhiều quá trình OS đang làm chậm quá trình này? Mọi lời gọi của runhaskell có thể làm cho hệ điều hành khám phá đường dẫn tìm kiếm hệ thống, định vị tệp nhị phân cần thiết, tải nó vào bộ nhớ (chắc chắn điều này đã có trong bộ đệm đĩa?), Liên kết nó với bất kỳ tệp DLL nào và kích hoạt nó. Có cách nào tôi có thể (dễ dàng) giữ một ví dụ của GHC đang chạy, thay vì phải liên tục tạo và phá hủy quá trình hệ điều hành?

Cuối cùng, tôi cho rằng luôn có API GHC. Nhưng như tôi đã hiểu, điều đó thật khó khăn để sử dụng, rất không có giấy tờ, và dễ bị thay đổi triệt để ở mọi phát hành điểm nhỏ của GHC. Nhiệm vụ tôi đang cố gắng thực hiện chỉ rất đơn giản, vì vậy tôi không thực sự muốn mọi thứ trở nên phức tạp hơn mức cần thiết.

Đề xuất?

Cập nhật: Switching để GHC -e (ví dụ, bây giờ tất cả mọi thứ được biên dịch, ngoại trừ các biểu hiện một được thực hiện) đã có sự khác biệt hiệu suất đo lường được. Nó có vẻ khá rõ ràng vào thời điểm này rằng đó là tất cả các chi phí hệ điều hành. Tôi tự hỏi liệu tôi có thể tạo một đường ống từ người thử nghiệm cho GHCi và do đó chỉ sử dụng một quy trình OS ...

+0

Toàn bộ quy trình làm việc của bạn không nhìn chính xác theo mục tiêu hiệu suất, phải không? Tại sao bạn phải tạo mã Haskell? – leftaroundabout

+3

Rõ ràng là bạn cần một daemon GHC! : p (một số người tôi biết sử dụng để nói đùa về việc tạo ra một daemon grep để tránh chi phí liên tục gọi grep trong khi khởi động, vv) – ivanm

+1

+1 cho một nỗ lực hợp lý và được thực hiện tốt ở tối ưu hóa. – delnan

Trả lời

9

Được rồi, tôi có một giải pháp: Tôi tạo ra một quá trình GHCi đơn và kết nối của nó stdin đến một đường ống, để tôi có thể gửi biểu thức để đánh giá tương tác.

Một số chương trình tái cấu trúc chương trình khá lớn sau đó và toàn bộ bộ thử nghiệm hiện mất khoảng 8 giây để thực thi, thay vì 48 giây. Điều đó sẽ làm cho tôi! :-D

(Để bất cứ ai khác cố gắng để làm điều này: Đối với tình yêu của Thiên Chúa, hãy nhớ để vượt qua -v0 chuyển sang GHCi, hoặc bạn sẽ nhận được một GHCi chào đón biểu ngữ thật là thú vị, nếu bạn chạy GHCi tương tác! , ngay cả với -v0 dấu nhắc lệnh vẫn xuất hiện, nhưng khi kết nối với một ống command prompt biến mất; tôi giả sử đây là một tính năng thiết kế hữu ích chứ không phải là một tai nạn ngẫu nhiên)


Tất nhiên, một nửa. lý do tôi đi xuống con đường kỳ lạ này là tôi muốn chụp stdoutstderr vào một tệp. Sử dụng RunHaskell, điều đó khá dễ dàng; chỉ cần chuyển các tùy chọn thích hợp khi tạo quy trình con. Nhưng bây giờ tất cả các trường hợp thử nghiệm đang được chạy bởi một quá trình hệ điều hành duy nhất, vì vậy không có cách nào rõ ràng để chuyển hướng stdinstdout.

Giải pháp tôi đưa ra là hướng tất cả đầu ra thử nghiệm tới một tệp duy nhất và giữa các thử nghiệm có GHCi in ra chuỗi ma thuật (tôi hy vọng!) Sẽ không xuất hiện trong đầu ra thử nghiệm. Sau đó, bỏ GHCi, slurp lên các tập tin, và tìm kiếm các chuỗi ma thuật vì vậy tôi có thể snip các tập tin vào khối thích hợp.

+0

Bạn có thể thay đổi các chức năng kiểm tra của mình để chúng xử lý lỗi và lỗi thay vì viết trực tiếp lên stdout và stderr không? – Alex

2

Nếu phần lớn các tệp nguồn vẫn không thay đổi, bạn có thể sử dụng GHC's -fobject-code (có thể cùng với -outputdir) gắn cờ để biên dịch một số tệp thư viện.

+0

Như tôi đã nói, tôi đã _already_ biên soạn 20KB mã thư viện. Điều đó làm giảm thời gian chạy từ 2 giây xuống còn 1 giây. Nhưng tôi muốn giảm hơn nữa nếu có một cách dễ dàng để làm như vậy. – MathematicalOrchid

+0

@MathematicalOrchid Oh, bỏ lỡ bit đó xin lỗi: s – ivanm

0

Nếu gọi runhaskell mất quá nhiều thời gian thì có lẽ bạn nên loại bỏ hoàn toàn?

Nếu bạn thực sự cần phải làm việc với việc thay đổi mã Haskell thì bạn có thể thử cách sau.

  1. Tạo bộ mô-đun có nội dung khác nhau khi cần.
  2. Mỗi mô-đun sẽ xuất chức năng chính của nó
  3. Mô-đun trình bao bọc bổ sung phải thực thi đúng mô-đun từ bộ dựa trên các đối số đầu vào. Mỗi khi bạn muốn thực hiện một thử nghiệm duy nhất, bạn sẽ sử dụng một đối số khác nhau.
  4. Toàn bộ chương trình được biên dịch tĩnh

mô-đun Ví dụ:

module Tester where 

import Data.String.Interpolation -- package Interpolation 

submodule nameSuffix var1 var2 = [str| 
module Sub$nameSuffix$ where 

someFunction x = $var1$ * x 
anotherFunction v | v == $var2$ = v 
        | otherwise = error ("anotherFunction: argument is not " ++ $:var2$) 

|] 

modules = [ let suf = (show var1 ++ "_" ++ show var2) in (suf,submodule suf var1 var2) | var1 <- [1..10], var2 <- [1..10]] 

writeModules = mapM_ (\ (file,what) -> writeFile file what) modules 
+0

Điều đó không thực sự hoạt động. Một số chương trình thử nghiệm có thể gặp sự cố; nếu toàn bộ sự việc là một chương trình khổng lồ, điều đó sẽ ngăn nó chạy. Ngoài ra, tôi muốn nắm bắt 'stdout' và' stderr' từ mỗi bài kiểm tra và ghi chúng vào tệp. Nếu không, thì có, tôi chỉ có thể tạo ra toàn bộ thứ như một chương trình Haskell khổng lồ duy nhất. Điều đó sẽ dễ dàng hơn nhiều ... – MathematicalOrchid

+0

@MathematicalOrchid: Bạn thực hiện lại chương trình cho mỗi bài kiểm tra, miễn là mọi thứ biên dịch bạn sẽ ổn. Về chuyển hướng: có gì sai với './testRunner testNumber123 2> stderr.txt 1> stdout.txt'? – Tener

+0

"crash" nghĩa là gì? Bạn sẽ có thể tích hợp tất cả các bài kiểm tra của bạn vào một chương trình duy nhất, và gọi chúng với một Á hậu thử nghiệm cấp cao nhất đề cập đến việc chuyển hướng 'stdout' và' stderr' và phục hồi từ các sự cố. – pat

0

Nếu các thử nghiệm được tách biệt hoàn toàn với nhau, bạn có thể đặt tất cả mã thử nghiệm vào một chương trình và gọi runhaskell một lần. Điều này có thể không hoạt động nếu một số thử nghiệm được tạo dựa trên kết quả của những người khác hoặc nếu một số thử nghiệm gọi unsafeCrash.

Tôi đoán mã được tạo của bạn trông như thế này

module Main where 
boilerplate code 
main = do_something_for_test_3 

Bạn có thể đặt mã của tất cả các cuộc thử nghiệm vào một tập tin. Mỗi trình tạo mã thử nghiệm có trách nhiệm viết do_something_for_test_N.

module Main where 
boilerplate code 

-- Run each test in its own directory 
withTestDir d m = do 
    cwd <- getCurrentDirectory 
    createDirectory d 
    setCurrentDirectory d 
    m 
    setCurrentDirectory cwd 

-- ["test1", "test2", ...] 
dirNames = map ("test"++) $ map show [1..] 
main = zipWithM withTestDir dirNames tests 

-- Put tests here 
tests = 
    [ do do_something_for_test_1 
    , do do_something_for_test_2 
    , ... 
    ] 

Bây giờ bạn chỉ phải chịu những phí của một cuộc gọi duy nhất để runhaskell.

3

Bạn có thể tìm thấy một số mã hữu ích trong TBC.Nó có những tham vọng khác nhau - đặc biệt là để thử nghiệm các bản thử nghiệm và các dự án thử nghiệm có thể không biên dịch hoàn toàn - nhưng nó có thể được mở rộng với một tính năng thư mục xem. Các thử nghiệm được chạy trong GHCi nhưng các đối tượng được xây dựng thành công bằng cabal ("runghc Setup build") được sử dụng.

Tôi đã phát triển nó để kiểm tra EDSL với kiểu tấn công phức tạp, tức là khi các thư viện khác thực hiện việc nâng tính toán nặng.

Tôi hiện đang cập nhật nó lên Nền tảng Haskell mới nhất và chào đón bất kỳ nhận xét hoặc bản vá nào.

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