Swift Beta에서 알고리즘을 구현하고 있었고 성능이 매우 열악하다는 것을 알았습니다. 더 깊이 파고 들자 병목 현상 중 하나가 배열 정렬과 같은 간단한 것임을 깨달았습니다. 관련 부분은 다음과 같습니다.
let n = 1000000
var x = [Int](repeating: 0, count: n)
for i in 0..<n {
x[i] = random()
}
// start clock here
let y = sort(x)
// stop clock here
C ++에서 비슷한 작업은 내 컴퓨터에서 0.06 초가 걸립니다 .
파이썬에서는 0.6 초가 걸립니다 (정수 목록은 y = sorted (x)).
Swift에서는 다음 명령으로 컴파일하면 6 초가 걸립니다 .
xcrun swift -O3 -sdk `xcrun --show-sdk-path --sdk macosx`
그리고 다음 명령으로 컴파일하면 88 초가 걸립니다 .
xcrun swift -O0 -sdk `xcrun --show-sdk-path --sdk macosx`
"릴리스"와 "디버그"빌드를 사용한 Xcode의 타이밍은 비슷합니다.
여기서 무엇이 잘못 되었습니까? C ++과 비교할 때 약간의 성능 손실을 이해할 수는 있지만 순수한 Python과 비교할 때 10 배의 속도 저하는 아닙니다.
편집 : 날씨에 따라이 코드를 C ++ 버전만큼 빠르게 실행 하도록 변경 -O3
했습니다 -Ofast
! 그러나 -Ofast
언어의 의미를 많이 변경했습니다. 필자의 테스트에서는 정수 오버플로 및 배열 인덱싱 오버플로 검사를 비활성화했습니다 . 예를 들어, -Ofast
다음 Swift 코드를 사용 하면 충돌없이 자동으로 실행되고 일부 가비지가 인쇄됩니다.
let n = 10000000
print(n*n*n*n*n)
let x = [Int](repeating: 10, count: n)
print(x[n])
그래서 -Ofast
우리가 원하는 것이 아니다; 스위프트의 요점은 안전망이 있다는 것입니다. 물론 안전망은 성능에 약간의 영향을 주지만 프로그램 속도를 100 배 느리게 만들면 안됩니다. Java는 이미 배열 경계를 검사하고 일반적인 경우 속도가 2보다 훨씬 작은 것을 기억하십시오. Clang과 GCC -ftrapv
에서는 정수 오버플로를 검사 (서명)해야하며 느리지 않습니다.
따라서 질문 : 안전망을 잃지 않고 어떻게 Swift에서 합리적인 성능을 얻을 수 있습니까?
편집 2 : 라인을 따라 매우 간단한 루프로 벤치마킹을 더했습니다.
for i in 0..<n {
x[i] = x[i] ^ 12345678
}
(여기서 xor 연산은 어셈블리 코드에서 관련 루프를 더 쉽게 찾을 수 있도록하기 위해 존재합니다. 나는 확인하기가 쉽지만 관련 점검이 필요하지 않다는 의미에서 "무해한"연산을 선택하려고했습니다. 정수 오버플로로.)
다시 사이의 성능에 큰 차이가 -O3
하고 -Ofast
. 그래서 어셈블리 코드를 살펴 보았습니다.
와
-Ofast
나는 거의 내가 기대하는 것을 얻을. 관련 부품은 5 개의 기계 언어 지침이있는 루프입니다.함께
-O3
나는 나의 거칠은 상상을 넘어서는 무언가를 얻을. 내부 루프는 88 줄의 어셈블리 코드에 걸쳐 있습니다. 모든 것을 이해하려고하지는 않았지만 가장 의심스러운 부분은 "callq _swift_retain"의 13 번의 호출과 "callq _swift_release"의 또 다른 13 번의 호출입니다. 즉 , 내부 루프에서 26 개의 서브 루틴 호출 !
편집 3 : 의견에서, Ferruccio는 빌트인 기능 (예 : 정렬)에 의존하지 않는다는 의미에서 공정한 벤치 마크를 요구했습니다. 다음 프로그램은 상당히 좋은 예라고 생각합니다.
let n = 10000
var x = [Int](repeating: 1, count: n)
for i in 0..<n {
for j in 0..<n {
x[i] = x[j]
}
}
산술이 없으므로 정수 오버플로에 대해 걱정할 필요가 없습니다. 우리가하는 유일한 것은 단지 많은 배열 참조입니다. 결과는 다음과 같습니다. 스위프트 -O3은 -Ofast와 비교하여 거의 500 배나 손실됩니다.
- C ++ -O3 : 0.05 초
- C ++ -O0 : 0.4 초
- 자바 : 0.2 초
- PyPy가 포함 된 Python : 0.5 초
- 파이썬 : 12 초
- 신속-고속 : 0.05 초
- 스위프트 -O3 : 23 초
- 스위프트 -O0 : 443s
(컴파일러가 무의미한 루프를 완전히 최적화 할 것을 우려하는 경우이를 루프로 변경하고 x[i] ^= x[j]
출력을 출력하는 print 문을 추가 할 수 있습니다 x[0]
. 이것은 변경되지 않습니다. 타이밍은 매우 유사합니다.)
그리고 그렇습니다. 여기서 파이썬 구현은 정수 목록과 중첩 된 for 루프가있는 어리석은 순수한 파이썬 구현이었습니다. 최적화되지 않은 스위프트보다 훨씬 느려 야합니다 . Swift 및 배열 인덱싱으로 인해 심각한 문제가 발생한 것 같습니다.
편집 4 : 이 문제 (및 다른 성능 문제)는 Xcode 6 베타 5에서 수정 된 것으로 보입니다.
정렬을 위해 다음 타이밍이 있습니다.
- clang ++ -O3 : 0.06 초
- swiftc-고속 : 0.1 초
- swiftc -O : 0.1 초
- swiftc : 4 초
중첩 루프의 경우 :
- clang ++ -O3 : 0.06 초
- swiftc-고속 : 0.3 초
- swiftc -O : 0.4 초
- swiftc : 540 초
더 이상 안전하지 않은 것을 사용할 이유가없는 것 같습니다 -Ofast
(일명 -Ounchecked
). plain -O
은 똑같이 좋은 코드를 생성합니다.
xcrun --sdk macosx swift -O3
. 더 짧습니다.