2010-07-13 5 views
9

F # 2.0이 VS2010의 일부가 되었기 때문에 F #에 관심이 있습니다. 나는 그것을 사용하는 것이 무엇인지 궁금해했다. 조금 읽었을 때 함수 호출을 측정하기위한 벤치 마크를 만들었습니다. 나는 ACKERMANN의 기능 :.Net 함수 호출 (C# F #) VS C++

C#을

sealed class Program 
{ 
    public static int ackermann(int m, int n) 
    { 
     if (m == 0) 
      return n + 1; 
     if (m > 0 && n == 0) 
     { 
      return ackermann(m - 1, 1); 
     } 
     if (m > 0 && n > 0) 
     { 
      return ackermann(m - 1, ackermann(m, n - 1)); 
     } 
     return 0; 
    } 

    static void Main(string[] args) 
    { 

     Stopwatch stopWatch = new Stopwatch(); 

     stopWatch.Start(); 
     Console.WriteLine("C# ackermann(3,10) = " + Program.ackermann(3, 10)); 
     stopWatch.Stop(); 

     Console.WriteLine("Time required for execution: " + stopWatch.ElapsedMilliseconds + "ms"); 
     Console.ReadLine(); 
    } 
} 

C++

class Program{ 
public: 
static inline int ackermann(int m, int n) 
{ 
    if(m == 0) 
     return n + 1; 
    if (m > 0 && n == 0) 
    { 
     return ackermann(m - 1, 1); 
    } 
    if (m > 0 && n > 0) 
    { 
     return ackermann(m - 1, ackermann(m, n - 1)); 
    } 
    return 0; 
} 
}; 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
clock_t start, end; 

    start = clock(); 
std::cout << "CPP: ackermann(3,10) = " << Program::ackermann(3, 10) << std::endl; 
end = clock(); 
std::cout << "Time required for execution: " << (end-start) << " ms." << "\n\n"; 
int i; 
std::cin >> i; 
return 0; 
} 

F 번호

// Ackermann 
let rec ackermann m n = 
    if m = 0 then n + 1 
    elif m > 0 && n = 0 then ackermann (m - 1) 1 
    elif m > 0 && n > 0 then ackermann (m - 1) (ackermann m (n - 1)) 
    else 0 

open System.Diagnostics; 
let stopWatch = Stopwatch.StartNew() 
let x = ackermann 3 10 
stopWatch.Stop(); 

printfn "F# ackermann(3,10) = %d" x 
printfn "Time required for execution: %f" stopWatch.Elapsed.TotalMilliseconds 

자바

public class Main 
{ 
public static int ackermann(int m, int n) 
{ 
if (m==0) 
    return n + 1; 
if (m>0 && n==0) 
{ 
return ackermann(m - 1,1); 
} 
if (m>0 && n>0) 
{ 
    return ackermann(m - 1,ackermann(m,n - 1)); 
} 
return 0; 
} 

    public static void main(String[] args) 
    { 
    System.out.println(Main.ackermann(3,10)); 
    } 
} 
,691을 사용했다 363,210

다음
C#을 = 510ms
C++ = 130ms 동안
F 번호 = 185ms
자바 = 유래 :

우리가 사용하고자하는 경우는 (코드의 작은 금액 제외) F 번호의 힘 닷넷과 약간 빠른 실행을 얻으시겠습니까? 이러한 코드 (특히 F #)를 최적화 할 수 있습니까?

업데이트. Console.WriteLine을 제거하고 디버거없이 C# 코드를 실행합니다. C# = 400ms

+9

벤치 마크를 실행하기 전에 한 번 방법을 실행하십시오. 시간에는 중간 언어를 JIT하는 데 소요 된 시간이 포함됩니다. 또한, Console.WriteLine()과 같은 메소드를 벤치 마크 밖에서 사용하십시오. 왜냐하면 심각하게 느리기 때문입니다. –

+1

"벤치 마크를 실행하기 전에 한 번 방법을 실행하십시오. 중간 언어를 JIT하는 데 걸리는 시간이 시간에 포함되어 있습니다." ? 나는 dd = ackermann 3 10을 더하고 7ms를 여분으로 준다. C#에 대한 변경 사항이 없습니다. "Console.WriteLine()과 같은 메소드를 벤치 마크 밖에서 사용하면 속도가 느려질 수 있습니다." 좋은 생각이지만 속도가 향상되지 않았습니다. –

+0

예, F # 및 C# 모두 JIT 컴파일 됨 - 메소드를 두 번 실행하고 두 번째 벤치 마크를 사용하십시오. 그 이유는 JIT 컴파일러가 프로세서의 특정 기능 (CISC 컴퓨팅 우수성)에 맞게 컴퓨터 코드를 최적화하기 때문입니다. – Aaronontheweb

답변

14

이 경우 C#과 F #의 차이점은 꼬리 호출 최적화 덕분이라고 생각합니다.

꼬리말 호출은 함수에서 "마지막 것"으로 수행되는 재귀 호출이있는 경우입니다. 이 경우 F #은 실제로 호출 명령어를 생성하지 않고 대신 코드를 루프로 컴파일합니다 (호출이 "마지막 작업"이기 때문에 현재 스택 프레임을 재사용하고 메소드의 시작 부분으로 건너 뛸 수 있음) .

ackermann 구현에서 두 개의 호출은 마무리 호출이며 그 중 하나만 (결과가 다른 ackermann 호출의 인수로 전달되는 호출이 아닙니다). 이것은 F # 버전이 실제로 "call"명령어 (많이?)를 자주 호출하지 않는다는 것을 의미합니다.

일반적으로 성능 프로필은 C#의 성능 프로필과 거의 같습니다. 여기에 C#을 성능 대 F 번호에 관한 꽤 많은 게시물이 있습니다

+2

저는 C#에서 재귀 적 파서 파서를 작성해 왔으며 종종 F # 꼬리 호출 최적화가 부족합니다. – ChaosPandion

+0

많이 있습니다. C++은 어떨까요? –

+6

ChaosPandion : C#에서 파서를 작성하고 있는데 F #을 사용하지 않는 것을 유감스럽게 생각하는 이유는 꼬리 호출을 최적화하지 않는다는 것입니다. 정말? 그것이 유일한 이유입니까? – Gabe

1

이 F 번호는 인라인 키워드를 시도 할 수 있습니다.

이전 포스터에서 언급했듯이 C# 및 F # 버전은 Console.WriteLine 문으로 인해 다릅니다.

+2

함수는 인라인 될 수 없으며 rec –

+3

어떻게 재귀 함수를 인라인할까요? – Gabe

+0

재귀 매듭을 풀고'inline'을 사용하여 언롤하십시오. –

6

이것은 함수 호출을 줄이는 일반적인 방법이므로 관련 함수 호출의 종류입니다.

이 유형의 재귀 함수를 메모 (캐싱) 방식으로 빠르게 만들 수 있습니다. C# (example.)에서이 작업을 수행 할 수도 있습니다. 18ms가 있습니다.

open System.Collections.Generic 

let memoize2 f = 
    let cache = Dictionary<_, _>() 
    fun x y -> 
     if cache.ContainsKey (x, y) then 
      cache.[(x, y)] 
     else 
      let res = f x y 
      cache.[(x, y)] <- res 
      res 

// Ackermann 
let rec ackermann = 
    memoize2 (fun m n -> 
     if m = 0 then n + 1 
     elif m > 0 && n = 0 then ackermann (m - 1) 1 
     elif m > 0 && n > 0 then ackermann (m - 1) (ackermann m (n - 1)) 
     else 0 
    ) 
4

직접 질문에 관련되지 있지만, 언급 할만큼 흥미 : 내 PC에이 버전

let rec ackermann2 m n = 
    match m,n with 
    | 0,0 -> 0 
    | 0,n -> n+1 
    | m,0 -> ackermann2 (m-1) 1 
    | m,n -> ackermann2 (m-1) (ackermann2 m (n-1)) 

시도 (VS2010는 F # 대화 형은)는 ACKERMANN 계산할 때 당신의 버전보다 약 50 % 더 빠르다 3 12.

성능 차이가 나는 이유를 설명 할 수 없습니다. 필자는 F # 컴파일러가 match 문을 일련의 if 문 대신 switch 문으로 변환하는 것과 관련이 있다고 생각합니다. (m = 0, n = 0) 테스트를 먼저 수행하는 것이 도움이되었을 수 있습니다.

+1

패턴 일치 컴파일러는 중복성을 최소화하기 위해 테스트를 재구성해야하며 원본 코드는'm> 0' 테스트를 두 번 수행했습니다. –