2014년 6월 8일 일요일

Swift 루프 퍼포먼스 테스트

Swift는 Objective-C를 대체하기 위해 나온 언어라서 그런지 둘 간의 퍼포먼스 벤치마크가 약간씩 나오고 있는 것 같다. 물론 Objective-C가 좀 더 low-level 이라서 퍼포먼스가 잘 나오는건 당연한거라 생각되지만, 어쩌면 Swift 코드를 잘못 짜서 결과가 더 벌어지는게 아닐까 하는 생각이 들 때도 있었다.

퍼포먼스 비교 시 루프를 이용해 특정 횟수로 테스트를 하는건 일반적이다. 하지만 루프를 어떻게 짜느냐에 따라 속도도 차이가 벌어질 수 있다. 그래서 확인 할 겸 Swift로 몇 가지 루프 코드를 짜서 실행 속도를 비교해 봤다.

우선 조금이라도 정확한 비교를 하기 위해 iOS 프로젝트가 아니라 OS X Cocoa 앱 프로젝트로 만들고 네이티브로 빌드해서 바로 실행시키는 형태를 가정한다.

작성한 코드는 아래와 같다.
func bench(benchFunc: () -> ()) {
    let startTime = CFAbsoluteTimeGetCurrent()
    benchFunc()
    let processTime = CFAbsoluteTimeGetCurrent() - startTime
    println("Process Time = \(processTime)")
}

func test1() {
    for i in 1...100000 {
    }
}

func test2() {
    for var i=0; i < 100000; ++i {
    }
}

func test3() {
    var i = 0
    while i < 100000 {
        ++i
    }
}

func test4() {
    var i = 0
    do {
        ++i
    } while i < 100000
}

// -------- Running Code -----------
bench(test1)
bench(test2)
bench(test3)
bench(test4)
코드를 한번에 늘어놔서 마치 플레이그라운드에서 테스트 한 것이 아닐까 느껴 질 수도 있는데, 플레이그라운드는 속도가 훨신 느리다. 위 코드 중 함수들은 AppDelegate 파일에 넣고 실제로 동작시키는 코드는 AppDelegate.applicationDidFinishLaunching 에 넣었다.

빌드 시 최적화 옵션은 아래와 같이 세팅했다.
LLVM 6.0 - Code  Generation 항목의 Optimization Level은 일단 가장 빨라보이는 녀석으로 설정해서 고정해 두고 그 아래 Swift Compiler - Code Generation 항목의 Optimization Level을 고쳐가며 테스트 해 봤다.

아래는 Swift Compiler의 Code Generation Optimization Level을 바꿔가며 테스트 한 결과이다.

Optimize None
test1 Process Time = 0.0609999895095825
test2 Process Time = 0.001475989818573
test3 Process Time = 0.00126999616622925
test4 Process Time = 0.00131899118423462

Optimize Fastest(-O)
test1 Process Time = 6.09755516052246e-05
test2 Process Time = 5.89489936828613e-05
test3 Process Time = 5.90085983276367e-05
test4 Process Time = 5.90085983276367e-05

Optimize Fastest, Unchecked(-Ofast)
test1 Process Time = 0.000128030776977539
test2 Process Time = 0.0
test3 Process Time = 0.0
test4 Process Time = 0.0

두 번째 Fastest 세팅의 결과가 좀 의아하다. 뭔가 문제가 있거나 혹은 위의 LLVM 코드 제너레이션 최적화와 관련이 있는 것일까. 그리고 마지막 테스트 결과도 뭔가 이상해 보이는데, 아마도 최적화 하는 과정에서 루프로 인식했는데 루프 내부에 별 다른 코드가 없어서 그냥 빼버린 것이 아닐까 생각된다. (아니면 말고 :-p)

하지만 처리 시간 자체는 신경 쓸 필요가 없다. 어차피 CPU나 돌아가는 환경에 따라 시간을 달라지기 마련. 여기서 봐야 할 것은 각 최적화 별 프로세스 타임 차이이다.

좀 이상한 결과가 많지만 추측하여 test1의 코드가 나머지에 비해 현저하게 느린 속도를 보여준다. 그런데 이건 당연하다. 1...100000 이라는 코드는 그냥 루프를 도는 코드가 아니라 Range<int> 라는 인스턴스를 만들고 이 인스턴스를 이터레이션(Iteration) 하는 방식으로 동작하다. 일반적으로 느릴 수 밖에 없는 상황이다.

test1을 제외한 나머지 결과들은 큰 차이가 보이지 않는다.

결과적으로 볼 때 루프를 짤 때 이런 Range를 생성하는 방식은 좀 더 느려진다는 것을 알 수 있다.

물론 이걸 맹신해서는 안된다. 분명 LLVM은 계속 발전할 것이고 Swift 코드의 빌드 타임 최적화 기술도 점점 발전할 것이다. 알아서 속도가 늘어날 것임은 분명하다. 그냥 참고만 하자.

[관련글] 스위프트(Swift) 가이드

댓글 없음 :