2012-02-17 2 views
15

작은 테스트 프레임 워크가 있습니다. 다음을 수행하는 루프를 실행합니다.runhaskell 가속화

  1. 작은 하스켈 소스 파일을 생성합니다.

  2. runhaskell으로 실행하십시오. 이 프로그램은 다양한 디스크 파일을 생성합니다.

  3. 방금 ​​생성 된 디스크 파일을 처리합니다.

이것은 수십 번 발생합니다. runhaskell이 프로그램 실행 시간의 대부분을 차지하고있는 것으로 나타났습니다.

한편, runhaskell은 디스크에서 파일을로드하고, 토큰 화하고, 구문 분석하고, 종속성 분석을 수행하고, 디스크에서 20KB 이상의 텍스트를로드하고, tokenise를 처리하고, 전체 유형 유추를 수행하고, 확인합니다. 유형, 코어에 desugar, 컴파일 된 기계 코드에 대한 링크, 통역사에서 물건을 실행, 벽 시간의 2 초 안에 모두 실제로 당신이 그것에 대해 생각할 때 꽤 인상적이다. 다른 한편으로는, 나는 그것을 더 빨리 진행시키고 싶다. ;-)

테스터 (위의 루프를 실행하는 프로그램)를 컴파일하면 약간의 성능 차이가 발생합니다. 스크립트가 링크하는 20KB의 라이브러리 코드를 컴파일하면 눈에 띄게 개선되었습니다. 하지만 호출 당 약 1 초가 걸린다. runhaskell.

생성 된 Haskell 파일은 각각 1KB를 넘지 만 파일의 한 부분 만 실제로 변경됩니다. 아마도 파일을 컴파일하고 GHC의 -e 스위치를 사용하는 것이 더 빠릅니까?

아니면 OS를 느리게 만드는 많은 OS 프로세스를 반복적으로 만들고 파괴하는 오버 헤드일까요? runhaskell을 호출 할 때마다 OS가 시스템 검색 경로를 탐색하고 필요한 이진 파일을 찾은 다음 메모리에로드합니다. (물론이 파일은 이미 디스크 캐시에 있습니다.) 모든 DLL에 링크하고 실행하십시오. OS 프로세스를 지속적으로 생성하고 파괴하지 않고 GHC의 한 인스턴스를 (쉽게) 유지할 수있는 방법이 있습니까?

궁극적으로 항상 GHC API가 있다고 가정합니다. 그러나 그것을 이해함에있어서, 악몽 같은 사용하기 어렵고, 문서화가 잘되어 있지 않으며, GHC의 모든 작은 포인트 릴리즈에서 급진적 인 변화를하는 경향이 있습니다. 내가 수행하려고하는 작업은 매우 간단하므로 필요한 것보다 더 복잡한 작업을하고 싶지는 않습니다.

제안 사항?

업데이트 : 전환 GHC -e에 (즉, 지금 모든가 하나의 표현식이 실행되는 것을 제외하고 컴파일) 측정 가능한 성능 차이를하지 않았다. 이 시점에서 모든 OS 오버 헤드가 매우 명확 해 보입니다. 나는 어쩌면 테스터에서 GHCi로 파이프를 만들어 단 하나의 OS 프로세스 만 사용할 수 있을지 궁금해 ...

+0

전체 워크 플로가 정확하게 성능 목표로 보이지 않습니까? 그렇습니다. 왜 하스켈 코드를 만들어야합니까? – leftaroundabout

+3

분명히 GHC 데몬이 필요합니다! : p (부팅 중에 grep을 계속해서 호출하는 오버 헤드를 피하기 위해 grep 데몬을 만드는 것에 대해 농담하는 사람들이 많습니다.) – ivanm

+1

+1에 대한 정당하고 잘 실행 된 시도가 있습니다. – delnan

답변

9

좋아, I는 솔루션을 가지고 I 단일 GHCi 프로세스를 생성 및 stdin 파이프에 연결된 I 수 있도록 대화식으로 평가할 표현식을 보내십시오.

상당수의 프로그램 리팩토링이 있으며, 전체 테스트 스위트는 48 초가 아닌 대략 8 초 정도 걸립니다. 그게 나를 위해 할거야! - D

(다른 사람이 일을하려고에 하나님의 사랑을 위해 을 GHCi에 -v0 스위치를 통과하는 기억, 또는 당신은 당신이 대화 형 GHCi를 실행하는 경우 GHCi가, 이상하게 배너를 환영거야! 파이프에 연결된 경우, 명령 프롬프트가 계속 표시도 -v0으로하지만, 명령 프롬프트는 사라, 나는이 도움이 디자인 기능보다는 무작위 우연이 추정하고 있습니다) 물론


, 절반. 내가이 이상한 길을가는 이유는 파일에 stdoutstderr을 붙잡고 싶다는 것입니다. RunHaskell을 사용하면 매우 쉽습니다. 자식 프로세스를 생성 할 때 적절한 옵션을 전달하십시오. 하지만 이제 모든 개의 테스트 케이스가 단일 OS 프로세스에서 실행되므로 stdinstdout을 리디렉션 할 수있는 확실한 방법은 없습니다.

나는 모든 테스트 결과를 하나의 파일로 보내고 테스트 사이에는 GHCi가 테스트 출력에 나타나지 않는 마술 문자열을 출력했다. 그런 다음 GHCi를 종료하고 파일을 스 루핑하고 매직 문자열을 찾아 파일을 적절한 청크로 스니핑 할 수 있습니다.

+0

테스트 함수를 변경하여 stdout 및 stderr에 직접 쓰는 대신 출력 및 오류를 처리 할 수 ​​있습니까? – Alex

2

대다수의 소스 파일이 변경되지 않으면 GHC의 -fobject-code (아마도 with -outputdir) 플래그를 사용하여 일부 라이브러리 파일을 컴파일합니다.

+0

내가 말했듯이, 나는 이미 20KB의 라이브러리 코드를 컴파일했다. 실행 시간이 2 초에서 1 초로 단축되었습니다. 그러나 그렇게하기 쉬운 방법이 있다면 이것을 더 줄이고 싶습니다. – MathematicalOrchid

+0

@MathematicalOrchid 오, 미안 해요. 미안 해요 : – ivanm

0

runhaskell에 전화를 걸면 너무 오래 걸릴 것입니다. 완전히 제거해야합니다.

정말로 하스켈 코드를 변경해야한다면 다음을 시도해보십시오.

  1. 필요에 따라 다양한 내용으로 모듈 세트를 만듭니다.
  2. 각 모듈은 주 함수를 내 보내야합니다.
  3. 추가 래퍼 모듈은 입력 인수를 기반으로 세트에서 올바른 모듈을 실행해야합니다. 단일 테스트를 실행할 때마다 다른 인수를 사용합니다.
  4. 전체 프로그램 정적 컴파일

예시적인 모듈 :

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

그건별로 효과가 없을 것 같습니다. 일부 테스트 프로그램이 중단 될 수 있습니다. 모든 것이 하나의 커다란 프로그램이라면 실행을 멈출 것입니다. 또한 각 테스트에서'stdout'과'stderr'을 포착하여 파일에 기록하려고합니다. 그것이 아니라면, 그렇습니다. 나는 전체를 하나의 거대한 하스켈 프로그램으로 생성 할 수있었습니다. 훨씬 쉬워 질 것입니다 ... – MathematicalOrchid

+0

@ MathematicalOrchid : 모든 테스트가 끝나면 프로그램을 다시 실행하기 때문에 모든 것이 컴파일되면 괜찮을 것입니다. 리다이렉션 (redirection) :'./testRunner testNumber123 2> stderr.txt 1> stdout.txt'의 무엇이 잘못 되었습니까? – Tener

+0

"충돌"의 의미는 무엇입니까? 모든 테스트를 하나의 프로그램에 통합 할 수 있어야하며,'stdout'과'stderr'를 리디렉션하고 충돌로부터 복구하는 최상위 테스트 러너와 함께 테스트를 수행 할 수 있어야합니다. – pat

0

테스트가 서로 잘 분리 된 경우 모든 테스트 코드를 단일 프로그램에 넣고 runhaskell을 한 번 호출 할 수 있습니다. 일부 테스트가 다른 테스트의 결과를 기반으로 작성되거나 일부 테스트가 unsafeCrash을 호출하는 경우이 작업이 작동하지 않을 수 있습니다.

나는이

module Main where 
boilerplate code 
main = do_something_for_test_3 

당신은 하나 개의 파일에 모든 테스트의 코드를 넣을 수 있습니다처럼 생성 된 코드가 보인다 추정. 각 테스트 코드 생성기는 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 
    , ... 
    ] 

이제는 runhaskell에 대한 단일 호출의 오버 헤드 만 발생합니다.

3

TBC에서 유용한 코드를 찾을 수 있습니다.여기에는 완전히 다른 컴파일러가 아닌 컴파일러 테스트 프로젝트를 테스트하는 등 여러 가지 야망이 있지만 워치 독 기능으로 확장 할 수 있습니다. 테스트는 GHCi에서 실행되지만 cabal에 의해 성공적으로 빌드 된 객체 ("runghc Setup build")가 사용됩니다.

복잡한 유형의 해커가있는 EDSL을 테스트하기 위해이를 개발했습니다. 즉, 무거운 컴퓨팅 리프팅이 다른 라이브러리에서 수행되는 곳입니다.

현재 최신 Haskell 플랫폼으로 업데이트하고 의견이나 패치를 환영합니다.