신속한 베타 성능 : 배열 정렬


929

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은 똑같이 좋은 코드를 생성합니다.


20
또 다른 "C보다 100 배 느린 스위프트"질문이 있습니다 : stackoverflow.com/questions/24102609/…
Jukka Suomela

16
그리고 정렬에있어서 스위프트의 우수한 성능과 관련된 애플의 마케팅 자료에 대한 토론이 있습니다 : programmers.stackexchange.com/q/242816/913
Jukka Suomela

2
다음과 같이 컴파일 할 수 있습니다 xcrun --sdk macosx swift -O3. 더 짧습니다.
Southern Hospitality

3
링크는 Objective-C와 비교하여 다른 기본 조작을 보여줍니다.
고원

4
베타 5에서는 Swift의 속도가 크게 향상되었습니다 . 자세한 내용 은 Jesse Squires의이 게시물을 참조하십시오 .
Nate Cook

답변:


460

tl; dr Swift 1.0은 기본 릴리스 최적화 수준 [-O]을 사용하여이 벤치 마크에서 C만큼 빠릅니다.


Swift Beta의 적절한 퀵 정렬은 다음과 같습니다.

func quicksort_swift(inout a:CInt[], start:Int, end:Int) {
    if (end - start < 2){
        return
    }
    var p = a[start + (end - start)/2]
    var l = start
    var r = end - 1
    while (l <= r){
        if (a[l] < p){
            l += 1
            continue
        }
        if (a[r] > p){
            r -= 1
            continue
        }
        var t = a[l]
        a[l] = a[r]
        a[r] = t
        l += 1
        r -= 1
    }
    quicksort_swift(&a, start, r + 1)
    quicksort_swift(&a, r + 1, end)
}

C에서도 마찬가지입니다.

void quicksort_c(int *a, int n) {
    if (n < 2)
        return;
    int p = a[n / 2];
    int *l = a;
    int *r = a + n - 1;
    while (l <= r) {
        if (*l < p) {
            l++;
            continue;
        }
        if (*r > p) {
            r--;
            continue;
        }
        int t = *l;
        *l++ = *r;
        *r-- = t;
    }
    quicksort_c(a, r - a + 1);
    quicksort_c(l, a + n - l);
}

두 작품 :

var a_swift:CInt[] = [0,5,2,8,1234,-1,2]
var a_c:CInt[] = [0,5,2,8,1234,-1,2]

quicksort_swift(&a_swift, 0, a_swift.count)
quicksort_c(&a_c, CInt(a_c.count))

// [-1, 0, 2, 2, 5, 8, 1234]
// [-1, 0, 2, 2, 5, 8, 1234]

둘 다 작성된 것과 동일한 프로그램에서 호출됩니다.

var x_swift = CInt[](count: n, repeatedValue: 0)
var x_c = CInt[](count: n, repeatedValue: 0)
for var i = 0; i < n; ++i {
    x_swift[i] = CInt(random())
    x_c[i] = CInt(random())
}

let swift_start:UInt64 = mach_absolute_time();
quicksort_swift(&x_swift, 0, x_swift.count)
let swift_stop:UInt64 = mach_absolute_time();

let c_start:UInt64 = mach_absolute_time();
quicksort_c(&x_c, CInt(x_c.count))
let c_stop:UInt64 = mach_absolute_time();

절대 시간을 초로 변환합니다.

static const uint64_t NANOS_PER_USEC = 1000ULL;
static const uint64_t NANOS_PER_MSEC = 1000ULL * NANOS_PER_USEC;
static const uint64_t NANOS_PER_SEC = 1000ULL * NANOS_PER_MSEC;

mach_timebase_info_data_t timebase_info;

uint64_t abs_to_nanos(uint64_t abs) {
    if ( timebase_info.denom == 0 ) {
        (void)mach_timebase_info(&timebase_info);
    }
    return abs * timebase_info.numer  / timebase_info.denom;
}

double abs_to_seconds(uint64_t abs) {
    return abs_to_nanos(abs) / (double)NANOS_PER_SEC;
}

다음은 컴파일러의 최적화 수준에 대한 요약입니다.

[-Onone] no optimizations, the default for debug.
[-O]     perform optimizations, the default for release.
[-Ofast] perform optimizations and disable runtime overflow checks and runtime type checks.

n = 10_000에 대해 [-Onone] 인 시간 (초) :

Swift:            0.895296452
C:                0.001223848

다음은 n = 10_000에 대한 Swift의 내장 sort ()입니다 .

Swift_builtin:    0.77865783

n = 10_000에 대한 [-O] 는 다음과 같습니다 .

Swift:            0.045478346
C:                0.000784666
Swift_builtin:    0.032513488

보다시피 Swift의 성능은 20 배 향상되었습니다.

mweathers의 답변에 따라 [-Ofast] 를 설정 하면 실제 차이가 발생하여 n = 10_000의 시간이 발생합니다 .

Swift:            0.000706745
C:                0.000742374
Swift_builtin:    0.000603576

그리고 n = 1_000_000의 경우 :

Swift:            0.107111846
C:                0.114957179
Swift_sort:       0.092688548

비교를 위해 n = 1_000_000의 경우 [-Onone]같습니다 .

Swift:            142.659763258
C:                0.162065333
Swift_sort:       114.095478272

따라서 최적화가없는 Swift는이 개발 단계 에서이 벤치 마크에서 C보다 거의 1000 배 느 렸습니다. 반면에 두 컴파일러 모두 [-Ofast]로 설정 한 경우 Swift는 실제로 C보다 약간 나쁘지 않은 경우에도 실제로 수행되었습니다.

[-Ofast]는 언어의 의미를 변경하여 잠재적으로 안전하지 않은 것으로 지적되었습니다. 이것이 Xcode 5.0 릴리스 노트에서 Apple이 말한 내용입니다.

LLVM에서 사용할 수있는 새로운 최적화 수준 -Ofast는 적극적인 최적화를 가능하게합니다. -Ofast는 대부분의 코드에 안전한 대부분의 부동 소수점 연산에 대한 일부 보수적 인 제한을 완화합니다. 컴파일러의 성능이 크게 향상 될 수 있습니다.

그들은 모두 그것을 옹호합니다. 그것이 현명한 지 아닌지는 말할 수 없지만, 내가 말할 수있는 것에서 고정밀 부동 소수점 산술을하지 않고 정수가 없거나 프로그램에서 배열 오버플로가 가능합니다. 고성능 오버플로 검사 / 정확한 산술 이 필요한 경우 지금 다른 언어를 선택하십시오.

베타 3 업데이트 :

[-O]를 사용한 n = 10_000 :

Swift:            0.019697268
C:                0.000718064
Swift_sort:       0.002094721

Swift는 일반적으로 약간 빠르며 Swift의 내장 정렬이 상당히 크게 변경된 것처럼 보입니다.

최종 업데이트 :

[-온원] :

Swift:   0.678056695
C:       0.000973914

[-O] :

Swift:   0.001158492
C:       0.001192406

[-확인되지 않음] :

Swift:   0.000827764
C:       0.001078914

25
-emit-sil을 사용하여 중간 SIL 코드를 출력하면 유지중인 내용이 표시됩니다 (아아, 스택 오버플로로 인해 형식을 지정할 수 없음). Array의 내부 버퍼 객체입니다. 이것은 분명히 옵티 마이저 버그처럼 들리는데, ARC 옵티마이 저는 -Ofast없이 리테이너를 제거 할 수 있어야합니다.
Catfish_Man

Ofast 최적화를 사용하려면 다른 언어를 사용해야한다는 데 동의하지 않을 것입니다. C와 같은 다른 언어를 선택하면 경계 검사 및 기타 사소한 문제에 대해서도 마찬가지로 처리해야합니다. 신속은 기본적으로 안전하고 필요에 따라 선택적으로 빠르고 안전하지 않기 때문에 정확합니다. 이를 통해 프로그래머는 코드를 디버그하고 모든 것이 정상인지 확인하고 Ofast를 사용하여 컴파일 할 수 있습니다. 현대 표준을 사용하면서 C와 같은 "안전하지 않은"언어의 힘을 가질 가능성은 매우 시원합니다.
Wallacy 2016 년

2
그것이 어떻게 유효하지 않을 수 있는지 말해 줄 수 있다면. 나는 항상 더 많은 것을 배우고 싶습니다
Joseph Mark

3
최종 업데이트를 완료 한 Swift는 표준 최적화를 사용하여이 벤치 마크에서 C만큼 빠릅니다.
Joseph Mark

4
팁 : 가장 작은 파티션에서 먼저 재귀를 수행하는 경우 빠른 정렬의 Swift 및 C 구현을 모두 향상시킬 수 있습니다 ! 최악의 경우 간단한 피벗 선택으로 구현 된 Quicksort는 O (n ^ 2) 시간이 걸리지 만이 최악의 경우에도 되풀이하여 O (log n) 스택 공간 만 있으면됩니다. 작은 파티션에서 먼저.
Macneil Shonle

108

TL; DR은 : 네, 유일한 스위프트 언어 구현은 느린 지금 . 빠른 숫자 (및 다른 유형의 코드, 아마도) 코드가 필요한 경우 다른 코드를 사용하십시오. 앞으로는 선택을 다시 평가해야합니다. 그러나 더 높은 수준으로 작성된 대부분의 응용 프로그램 코드에는 충분할 수 있습니다.

내가 SIL과 LLVM IR에서보고있는 것에서, 보유 및 릴리스를 제거하기위한 많은 최적화가 필요한 것처럼 보입니다. 이것은 Clang (Objective-C의 경우) 으로 구현 될 수 있지만 아직 포팅되지 않았습니다. 이 질문의 마지막 테스트 사례에서 프로파일 러를 실행하면 다음과 같은“예쁜”결과를 얻을 수 있기 때문에 이것이 제가 지금 가지고있는 이론입니다 (현재로서는 Clang이 이에 ​​대해 무언가를 수행하고 있음을 확인해야합니다).

-O3에 대한 시간 프로파일 링 -Ofast에 대한 시간 프로파일 링

많은 사람들이 말했듯이, -Ofast완전히 안전하지 않으며 언어 의미를 변경합니다. 저에게는“이 언어를 사용하려면 다른 언어 만 사용하십시오”단계에 있습니다. 나중에 선택 사항이 변경되면 다시 평가하겠습니다.

-O3우리에게 한 무리의 도착 swift_retainswift_release그들이이 예를 들어이 있어야처럼, 정직하게, 보이지 않는 전화를. 옵티마이 저는 어레이에 대한 대부분의 정보를 알고 있으며 (적어도) 그것에 대한 강력한 참조를 가지고 있기 때문에 AFAICT를 제거해야합니다.

객체를 해제 할 수있는 함수를 호출하지 않아도 더 많은 보유를 방출해서는 안됩니다. 배열 생성자가 요청 된 것보다 작은 배열을 반환 할 수 있다고 생각하지 않습니다. 즉, 방출 된 많은 검사가 쓸모가 없다는 것을 의미합니다. 또한 정수가 10k를 초과 하지 않기 때문에 오버플로 검사 최적화 할 수 있습니다 (이상하지 않기 때문에가 -Ofast아니라 언어의 의미론으로 인해 var에 변경하거나 액세스 할 수 없으며 최대 10k를 추가하는 것은 없습니다) 유형에 안전합니다 Int).

그러나 컴파일러는 배열 또는 배열 요소의 압축을 풀지 못할 수도 있습니다 sort(). 외부 함수 인으로 전달되어 예상 한 인수를 가져와야하기 때문입니다. 이렇게하면 Int값을 간접적으로 사용해야하므로 조금 느려집니다. sort()컴파일러가 범용 함수가 아닌 일반 함수를 사용할 수 있고 인라인 된 경우 변경 될 수 있습니다 .

이것은 아주 새로운 (공개적으로) 언어이며, 피드백을 요청 스위프트 언어와 관련된 (크게) 사람들이 있기 때문에 그것은, 내가 생각 무엇을 통해 많은 변화가되는 것입니다 그들은 모든 언어가 완료되지 않습니다 말하고 것이다 변화.

사용 된 코드 :

import Cocoa

let swift_start = NSDate.timeIntervalSinceReferenceDate();
let n: Int = 10000
let x = Int[](count: n, repeatedValue: 1)
for i in 0..n {
    for j in 0..n {
        let tmp: Int = x[j]
        x[i] = tmp
    }
}
let y: Int[] = sort(x)
let swift_stop = NSDate.timeIntervalSinceReferenceDate();

println("\(swift_stop - swift_start)s")

추신 : 저는 Objective-C 또는 Cocoa , Objective-C 또는 Swift 런타임의 모든 시설에 대한 전문가가 아닙니다 . 내가 쓰지 않은 것들을 가정하고있을 수도 있습니다.


그러나 컴파일러는 배열 또는 배열 요소의 압축을 풀지 못할 수도 있습니다. 외부 함수 인 sort ()로 전달되기 때문에 예상되는 인수를 가져와야하기 때문입니다. 비교적 좋은 컴파일러에는 중요하지 않습니다. 실제 데이터에 대한 메타 데이터 (포인터에서-64 비트는 많은 제방을 제공)를 전달하고 호출 된 함수에서 분기합니다.
bestsss

3
-Ofast"완전히 안전하지 않은" 이유는 무엇입니까 ? 코드를 테스트하고 오버플로를 배제하는 방법을 알고 있다고 가정합니다.
Joseph Mark

@ sjeohp : 실제로 많은 것을 가정하고 있습니다 :-) 코드를 확인하고 오버플로를 배제하는 것은 어렵습니다. 내 경험상 (컴파일러 작업을하고 큰 코드베이스를 확인했습니다), 컴파일러를 사용하는 사람들이 거대 회사에서 일하면서 오버플로 및 기타 정의되지 않은 동작을 올바르게 수행하는 것은 어렵습니다 . UB 수정에 대한 Apple의 조언 (예를 들어)조차 잘못되기도합니다 ( randomascii.wordpress.com/2014/04/17/… ). -Ofast또한 언어 의미를 변경하지만 그에 대한 문서는 지원할 수 없습니다. 그것이 무엇을하고 있는지 어떻게 확신 할 수 있습니까?
filcab 2016 년

@ bestsss : 가능하지만 유용하지 않을 수 있습니다. Int []에 대한 모든 액세스에 대한 점검을 추가합니다. Int 배열과 몇 가지 다른 기본 유형 (최대 3 비트가 있음)이 많이 사용되는지 (특히 필요한 경우 C로 낮출 수있는 경우)에 따라 다릅니다. 또한 비 ARC GC를 추가하려는 경우 사용할 비트를 사용합니다. 둘 이상의 인수로 제네릭으로 확장되지 않습니다. 그것들은 모든 타입을 가지고 있기 때문에 Int []를 만진 모든 코드 (Int? []는 아님)를 인라인 된 Int를 사용하도록 전문화하는 것이 훨씬 쉬울 것입니다. 그러나 걱정해야 할 Obj-C interop이 있습니다.
filcab 2016 년

@filcab, 비 ARC (즉, 실제) GC는 실제로 유용하지만 실제로 동시 STW가 아닌 GC를 원한다면 C와 호환되지 않는 것이 필요합니다. Int[]컴파일러가 인라인 할 수있는 레벨에 따라 다르며 지침에 따라 / 후에 타이트한 루프를 인라인 할 수 있어야하기 때문에 '모든 액세스'에 대해 걱정하지 않아도 됩니다.
bestsss 2016 년

53

나는 이것을 재미있게 살펴보기로 결정했고, 다음과 같은 타이밍을 얻었습니다.

Swift 4.0.2           :   0.83s (0.74s with `-Ounchecked`)
C++ (Apple LLVM 8.0.0):   0.74s

빠른

// Swift 4.0 code
import Foundation

func doTest() -> Void {
    let arraySize = 10000000
    var randomNumbers = [UInt32]()

    for _ in 0..<arraySize {
        randomNumbers.append(arc4random_uniform(UInt32(arraySize)))
    }

    let start = Date()
    randomNumbers.sort()
    let end = Date()

    print(randomNumbers[0])
    print("Elapsed time: \(end.timeIntervalSince(start))")
}

doTest()

결과 :

스위프트 1.1

xcrun swiftc --version
Swift version 1.1 (swift-600.0.54.20)
Target: x86_64-apple-darwin14.0.0

xcrun swiftc -O SwiftSort.swift
./SwiftSort     
Elapsed time: 1.02204304933548

스위프트 1.2

xcrun swiftc --version
Apple Swift version 1.2 (swiftlang-602.0.49.6 clang-602.0.49)
Target: x86_64-apple-darwin14.3.0

xcrun -sdk macosx swiftc -O SwiftSort.swift
./SwiftSort     
Elapsed time: 0.738763988018036

스위프트 2.0

xcrun swiftc --version
Apple Swift version 2.0 (swiftlang-700.0.59 clang-700.0.72)
Target: x86_64-apple-darwin15.0.0

xcrun -sdk macosx swiftc -O SwiftSort.swift
./SwiftSort     
Elapsed time: 0.767306983470917

로 컴파일하면 동일한 성능 인 것 같습니다 -Ounchecked.

스위프트 3.0

xcrun swiftc --version
Apple Swift version 3.0 (swiftlang-800.0.46.2 clang-800.0.38)
Target: x86_64-apple-macosx10.9

xcrun -sdk macosx swiftc -O SwiftSort.swift
./SwiftSort     
Elapsed time: 0.939633965492249

xcrun -sdk macosx swiftc -Ounchecked SwiftSort.swift
./SwiftSort     
Elapsed time: 0.866258025169373

Swift 2.0에서 Swift 3.0으로의 성능 회귀가 있었던 것 같습니다. 또한 처음 -O과 사이에 차이가 -Ounchecked있습니다.

스위프트 4.0

xcrun swiftc --version
Apple Swift version 4.0.2 (swiftlang-900.0.69.2 clang-900.0.38)
Target: x86_64-apple-macosx10.9

xcrun -sdk macosx swiftc -O SwiftSort.swift
./SwiftSort     
Elapsed time: 0.834299981594086

xcrun -sdk macosx swiftc -Ounchecked SwiftSort.swift
./SwiftSort     
Elapsed time: 0.742045998573303

간극 유지하면서 신속한 4 다시 성능을 향상 -O-Ounchecked . -O -whole-module-optimization차이를 보이지 않았다.

C ++

#include <chrono>
#include <iostream>
#include <vector>
#include <cstdint>
#include <stdlib.h>

using namespace std;
using namespace std::chrono;

int main(int argc, const char * argv[]) {
    const auto arraySize = 10000000;
    vector<uint32_t> randomNumbers;

    for (int i = 0; i < arraySize; ++i) {
        randomNumbers.emplace_back(arc4random_uniform(arraySize));
    }

    const auto start = high_resolution_clock::now();
    sort(begin(randomNumbers), end(randomNumbers));
    const auto end = high_resolution_clock::now();

    cout << randomNumbers[0] << "\n";
    cout << "Elapsed time: " << duration_cast<duration<double>>(end - start).count() << "\n";

    return 0;
}

결과 :

애플 클랜 6.0

clang++ --version
Apple LLVM version 6.0 (clang-600.0.54) (based on LLVM 3.5svn)
Target: x86_64-apple-darwin14.0.0
Thread model: posix

clang++ -O3 -std=c++11 CppSort.cpp -o CppSort
./CppSort     
Elapsed time: 0.688969

애플 클랜 6.1.0

clang++ --version
Apple LLVM version 6.1.0 (clang-602.0.49) (based on LLVM 3.6.0svn)
Target: x86_64-apple-darwin14.3.0
Thread model: posix

clang++ -O3 -std=c++11 CppSort.cpp -o CppSort
./CppSort     
Elapsed time: 0.670652

애플 클랜 7.0.0

clang++ --version
Apple LLVM version 7.0.0 (clang-700.0.72)
Target: x86_64-apple-darwin15.0.0
Thread model: posix

clang++ -O3 -std=c++11 CppSort.cpp -o CppSort
./CppSort     
Elapsed time: 0.690152

애플 클랑 8.0.0

clang++ --version
Apple LLVM version 8.0.0 (clang-800.0.38)
Target: x86_64-apple-darwin15.6.0
Thread model: posix

clang++ -O3 -std=c++11 CppSort.cpp -o CppSort
./CppSort     
Elapsed time: 0.68253

애플 클랑 9.0.0

clang++ --version
Apple LLVM version 9.0.0 (clang-900.0.38)
Target: x86_64-apple-darwin16.7.0
Thread model: posix

clang++ -O3 -std=c++11 CppSort.cpp -o CppSort
./CppSort     
Elapsed time: 0.736784

평결

이 글을 쓰는 시점에서 Swift의 정렬은 빠르지 만 -O위 컴파일러 및 라이브러리를 사용하여 컴파일 할 때 C ++ 정렬만큼 빠르지는 않습니다 . 을 사용하면 -OuncheckedSwift 4.0.2 및 Apple LLVM 9.0.0에서 C ++만큼 빠릅니다.


2
실제로 천만 개의 요소를 삽입하기 전에 vector :: reserve ()호출 하면 안됩니다 .
BJovke

혹시! 현재 정렬 만 시간 지정되고 있습니다.
OpenGL ES

34

보낸 사람 The Swift Programming Language:

Sort Function Swift의 표준 라이브러리는 sort라는 함수를 제공합니다. sort라는 함수는 사용자가 제공 한 정렬 클로저의 출력을 기반으로 알려진 유형의 값 배열을 정렬합니다. 정렬 프로세스가 완료되면 sort 함수는 요소가 올바른 정렬 순서로 이전 유형과 크기 및 크기가 같은 새 배열을 반환합니다.

sort함수에는 두 가지 선언이 있습니다.

비교 클로저를 지정할 수있는 기본 선언 :

func sort<T>(array: T[], pred: (T, T) -> Bool) -> T[]

그리고 하나의 매개 변수 (배열) 만 사용하고 "보다 작은 비교기를 사용하도록 하드 코딩 된"두 번째 선언입니다.

func sort<T : Comparable>(array: T[]) -> T[]

Example:
sort( _arrayToSort_ ) { $0 > $1 }

클로저가 추가 된 놀이터에서 수정 된 코드 버전을 테스트하여 기능을 좀 더 자세히 모니터링 할 수 있었고 n을 1000으로 설정하면 클로저가 약 11,000 번 호출되었습니다.

let n = 1000
let x = Int[](count: n, repeatedValue: 0)
for i in 0..n {
    x[i] = random()
}
let y = sort(x) { $0 > $1 }

효율적인 기능이 아니므로 더 나은 정렬 기능 구현을 사용하는 것이 좋습니다.

편집하다:

Quicksort wikipedia 페이지를보고 Swift 구현을 작성했습니다. 내가 사용한 전체 프로그램은 다음과 같습니다 (놀이터에서).

import Foundation

func quickSort(inout array: Int[], begin: Int, end: Int) {
    if (begin < end) {
        let p = partition(&array, begin, end)
        quickSort(&array, begin, p - 1)
        quickSort(&array, p + 1, end)
    }
}

func partition(inout array: Int[], left: Int, right: Int) -> Int {
    let numElements = right - left + 1
    let pivotIndex = left + numElements / 2
    let pivotValue = array[pivotIndex]
    swap(&array[pivotIndex], &array[right])
    var storeIndex = left
    for i in left..right {
        let a = 1 // <- Used to see how many comparisons are made
        if array[i] <= pivotValue {
            swap(&array[i], &array[storeIndex])
            storeIndex++
        }
    }
    swap(&array[storeIndex], &array[right]) // Move pivot to its final place
    return storeIndex
}

let n = 1000
var x = Int[](count: n, repeatedValue: 0)
for i in 0..n {
    x[i] = Int(arc4random())
}

quickSort(&x, 0, x.count - 1) // <- Does the sorting

for i in 0..n {
    x[i] // <- Used by the playground to display the results
}

이것을 n = 1000으로 사용하면

  1. quickSort ()가 약 650 번 호출되었습니다.
  2. 약 6000 개의 교환이 이루어졌으며
  3. 약 10,000 개의 비교가 있습니다

내장 정렬 방법이 빠른 정렬 (또는 가까운) 인 것처럼 보이며 실제로 느립니다 ...


17
아마도 나는 완전히 틀렸지 만 en.wikipedia.org/wiki/Quicksort 에 따르면 , Quicksort의 평균 비교 횟수는 2*n*log(n)입니다. 즉, n = 1000 개의 요소를 정렬하기위한 13815 개의 비교이므로 비교 함수가 약 11000 회 호출되면 그리 나쁘지 않은 것으로 보입니다.
Martin R

6
또한 Apple은 "복잡한 객체 정렬"(그것이 무엇이든)이 Python보다 Swift에서 3.9 배 더 빠르다고 주장했습니다. 따라서 "더 나은 정렬 기능"을 찾을 필요는 없습니다. -그러나 Swift는 아직 개발 중입니다.
Martin R

6
그것은 않는 자연 로그를 참조하십시오.
Martin R

24
log(n)알고리즘 복잡도는 일반적으로 log base-2를 나타냅니다. 밑을 나타내지 않는 이유는 로그에 대한 밑이 바뀐 법칙은 상수 승수만을 도입하기 때문에 O- 표기의 목적으로 버려집니다.
minuteman3

3
자연 대수 대 기본 2 대수에 대한 논의와 관련하여 : Wikipedia 페이지의 정확한 설명은 n 개의 요소에 필요한 평균 비교 횟수는 C(n) = 2n ln n ≈ 1.39n log₂ n입니다. n = 1000 인 경우 C (n) = 13815가 되며 "big-O 표기법" 이 아닙니다 .
Martin R

18

Xcode 7부터는을 켤 수 있습니다 Fast, Whole Module Optimization. 성능이 즉시 향상됩니다.

여기에 이미지 설명을 입력하십시오


12

스위프트 어레이 성능 재검토 :

Swift와 C / Objective-C를 비교 한 자체 벤치 마크를 작성했습니다. 내 벤치 마크는 소수를 계산합니다. 이전 소수의 배열을 사용하여 각 새 후보에서 소수를 찾습니다. 따라서 매우 빠릅니다. 그러나 배열 읽기의 TONS를 수행하고 배열에 쓰기를 줄입니다.

원래 Swift 1.2에 대해이 벤치 마크를 수행했습니다. 프로젝트를 업데이트하고 Swift 2.0에서 실행하기로 결정했습니다.

이 프로젝트를 통해 일반적인 빠른 배열 사용과 배열 의미론을 사용하는 Swift 안전하지 않은 메모리 버퍼 사용 중에서 선택할 수 있습니다.

C / Objective-C의 경우 NSArray 또는 C malloc'ed 배열을 사용하도록 선택할 수 있습니다.

테스트 결과는 가장 빠르고 작은 코드 최적화 ([-0s]) 또는 가장 빠르고 공격적인 ([-0fast]) 최적화와 매우 유사 해 보입니다.

C / Objective-C 성능은 약간 느리지 만 Swift 2.0 성능은 여전히 ​​코드 최적화가 꺼져있어 끔찍합니다.

결론적으로 C malloc의 배열 기반 계산은 완만 한 마진으로 가장 빠릅니다.

안전하지 않은 버퍼가있는 스위프트는 가장 빠르고 작은 코드 최적화를 사용할 때 C malloc의 어레이보다 1.19X-1.20X 더 오래 걸립니다. 빠르고 공격적인 최적화에서는 차이가 약간 줄어 듭니다 (Swift는 C보다 1.18x에서 1.16x 더 오래 걸립니다.

일반 Swift 배열을 사용하는 경우 C와의 차이가 약간 큽니다. (Swift는 ~ 1.22 ~ 1.23 시간이 더 걸립니다.)

레귤러 스위프트 어레이는 DRAMATICALLY Swift 1.2 / Xcode 6보다 속도 빠릅니다. 성능이 Swift 안전하지 않은 버퍼 기반 어레이에 너무 가깝기 때문에 안전하지 않은 메모리 버퍼를 사용하는 것이 더 이상 문제의 가치가없는 것처럼 보입니다.

BTW, Objective-C NSArray 성능에 악영향을 미칩니다. 당신이 두 언어의 기본 컨테이너 개체를 사용하려고하는 경우, 스위프트는 DRAMATICALLY 빨리.

SwiftPerformanceBenchmark 에서 github의 프로젝트를 확인할 수 있습니다

통계를 매우 쉽게 수집 할 수있는 간단한 UI가 있습니다.

Swift에서는 C보다 정렬이 약간 빠르지 만 Swift에서는이 소수 알고리즘이 여전히 빠르다는 점이 흥미 롭습니다.


8

다른 사람들이 언급했지만 충분히 언급되지 않은 주요 문제는 -O3Swift에서 전혀 아무것도하지 않으며 결코 가지고 있지 않으므로 실제로 컴파일되지 않을 때 효과적으로 최적화되지 않는 것입니다 ( -Onone).

옵션 이름은 시간이 지남에 따라 변경되었으므로 다른 답변에는 빌드 옵션에 대한 플래그가 더 이상 사용되지 않습니다. 올바른 현재 옵션 (Swift 2.2)은 다음과 같습니다.

-Onone // Debug - slow
-O     // Optimised
-O -whole-module-optimization //Optimised across files

전체 모듈 최적화는 컴파일 속도가 느리지 만 모듈 내의 파일, 즉 각 프레임 워크 내 및 실제 응용 프로그램 코드 내에서 파일간에 최적화 할 수는 없습니다. 중요한 성능을 위해서는 이것을 사용해야합니다)

더 빠른 속도를 위해 안전 검사를 비활성화 할 수 있지만 모든 어설 션 및 사전 조건은 비활성화 될뿐만 아니라 정확한 기준에 따라 최적화됩니다. 만약 당신이 주장에 부딪쳤다면 이것은 당신이 정의되지 않은 행동을하고 있다는 것을 의미합니다. 테스트를 통해 속도 향상이 가치가 있다고 판단되는 경우에만 각별히주의하십시오. 일부 코드에서 유용하다고 생각되면 해당 코드를 별도의 프레임 워크로 분리하고 해당 모듈에 대한 안전 검사 만 비활성화하는 것이 좋습니다.


이 답변은 이제 구식입니다. Swift 4.1부터 전체 모듈 최적화 옵션은 다른 설정과 결합 할 수있는 별도의 부울이며 크기에 맞게 최적화 할 -O가 있습니다. 정확한 옵션 플래그를 확인할 시간이 있으면 업데이트 할 수 있습니다.
Joseph Lord

7
func partition(inout list : [Int], low: Int, high : Int) -> Int {
    let pivot = list[high]
    var j = low
    var i = j - 1
    while j < high {
        if list[j] <= pivot{
            i += 1
            (list[i], list[j]) = (list[j], list[i])
        }
        j += 1
    }
    (list[i+1], list[high]) = (list[high], list[i+1])
    return i+1
}

func quikcSort(inout list : [Int] , low : Int , high : Int) {

    if low < high {
        let pIndex = partition(&list, low: low, high: high)
        quikcSort(&list, low: low, high: pIndex-1)
        quikcSort(&list, low: pIndex + 1, high: high)
    }
}

var list = [7,3,15,10,0,8,2,4]
quikcSort(&list, low: 0, high: list.count-1)

var list2 = [ 10, 0, 3, 9, 2, 14, 26, 27, 1, 5, 8, -1, 8 ]
quikcSort(&list2, low: 0, high: list2.count-1)

var list3 = [1,3,9,8,2,7,5]
quikcSort(&list3, low: 0, high: list3.count-1) 

빠른 정렬에 대한 내 블로그입니다. 빠른 정렬 Github 샘플 빠른 정렬에

목록 분할에서 Lomuto의 분할 알고리즘을 살펴볼 수 있습니다. 스위프트로 작성되었습니다.


4

Swift 4.1 에는 새로운 -Osize최적화 모드가 도입되었습니다 .

Swift 4.1에서 컴파일러는 새로운 최적화 모드를 지원하여 전용 최적화를 통해 코드 크기를 줄일 수 있습니다.

Swift 컴파일러는 강력한 최적화 기능을 제공합니다. -O로 컴파일 할 때 컴파일러는 코드를 변환하여 최대 성능으로 실행되도록합니다. 그러나 런타임 성능이 향상되면 코드 크기가 커질 수 있습니다. 새로운 -Osize 최적화 모드에서는 사용자가 최대 속도가 아닌 최소 코드 크기로 컴파일 할 수 있습니다.

명령 행에서 크기 최적화 모드를 사용하려면 -O 대신 -Osize를 사용하십시오.

추가 읽기 : https://swift.org/blog/osize/

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.