Swift 루프 퍼포먼스 테스트
Swift는 Objective-C를 대체하기 위해 나온 언어라서 그런지 둘 간의 퍼포먼스 벤치마크가 약간씩 나오고 있는 것 같다. 물론 Objective-C가 좀 더 low-level 이라서 퍼포먼스가 잘 나오는건 당연한거라 생각되지만, 어쩌면 Swift 코드를 잘못 짜서 결과가 더 벌어지는게 아닐까 하는 생각이 들 때도 있었다.
작성한 코드는 아래와 같다.
빌드 시 최적화 옵션은 아래와 같이 세팅했다.
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) 가이드
퍼포먼스 비교 시 루프를 이용해 특정 횟수로 테스트를 하는건 일반적이다. 하지만 루프를 어떻게 짜느냐에 따라 속도도 차이가 벌어질 수 있다. 그래서 확인 할 겸 Swift로 몇 가지 루프 코드를 짜서 실행 속도를 비교해 봤다.
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) 가이드
댓글