Hiệu suất Swift Beta: sắp xếp mảng


929

Tôi đã thực hiện một thuật toán trong Swift Beta và nhận thấy rằng hiệu suất rất kém. Sau khi đào sâu hơn, tôi nhận ra rằng một trong những điểm nghẽn là một thứ đơn giản như sắp xếp các mảng. Phần có liên quan ở đây:

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

Trong C ++, một thao tác tương tự mất 0,06 giây trên máy tính của tôi.

Trong Python, phải mất 0,6 giây (không có thủ thuật, chỉ cần y = sort (x) cho danh sách các số nguyên).

Trong Swift, tôi mất 6 giây nếu tôi biên dịch nó bằng lệnh sau:

xcrun swift -O3 -sdk `xcrun --show-sdk-path --sdk macosx`

Và phải mất tới 88 giây nếu tôi biên dịch nó bằng lệnh sau:

xcrun swift -O0 -sdk `xcrun --show-sdk-path --sdk macosx`

Thời gian trong Xcode với các bản dựng "Phát hành" so với "Gỡ lỗi" là tương tự nhau.

Có chuyện gì ở đây vậy? Tôi có thể hiểu một số mất hiệu năng so với C ++, nhưng không chậm hơn 10 lần so với Python thuần túy.


Chỉnh sửa: thời tiết nhận thấy rằng việc thay đổi -O3để -Ofastlàm cho mã này chạy gần như nhanh như phiên bản C ++! Tuy nhiên, -Ofastthay đổi ngữ nghĩa của ngôn ngữ rất nhiều - trong thử nghiệm của tôi, nó đã vô hiệu hóa các kiểm tra cho tràn số nguyên và tràn chỉ mục mảng . Ví dụ: với -Ofastmã Swift sau đây chạy âm thầm mà không gặp sự cố (và in ra một số rác):

let n = 10000000
print(n*n*n*n*n)
let x =  [Int](repeating: 10, count: n)
print(x[n])

Vì vậy, -Ofastkhông phải là những gì chúng ta muốn; toàn bộ quan điểm của Swift là chúng tôi có lưới an toàn. Tất nhiên, lưới an toàn có một số tác động đến hiệu suất, nhưng chúng không nên làm cho chương trình chậm hơn 100 lần. Hãy nhớ rằng Java đã kiểm tra giới hạn mảng và trong các trường hợp điển hình, sự chậm lại là do hệ số nhỏ hơn 2. Và trong Clang và GCC, chúng ta đã có -ftrapvđể kiểm tra số nguyên (đã ký) tràn ra và nó cũng không chậm.

Do đó, câu hỏi: làm thế nào chúng ta có thể có được hiệu suất hợp lý trong Swift mà không mất mạng lưới an toàn?


Chỉnh sửa 2: Tôi đã thực hiện thêm một số điểm chuẩn, với các vòng lặp rất đơn giản dọc theo dòng

for i in 0..<n {
    x[i] = x[i] ^ 12345678
}

(Ở đây có thao tác xor chỉ để tôi có thể dễ dàng tìm thấy vòng lặp có liên quan hơn trong mã lắp ráp. Tôi đã cố gắng chọn một thao tác dễ phát hiện nhưng cũng "vô hại" theo nghĩa là nó không yêu cầu bất kỳ kiểm tra nào liên quan để tràn số nguyên.)

Một lần nữa, có một sự khác biệt rất lớn trong hiệu suất giữa -O3-Ofast. Vì vậy, tôi đã xem xét mã lắp ráp:

  • Với -Ofasttôi nhận được khá nhiều những gì tôi mong đợi. Phần liên quan là một vòng lặp với 5 hướng dẫn ngôn ngữ máy.

  • Với -O3tôi nhận được một cái gì đó ngoài sức tưởng tượng điên cuồng nhất của tôi. Vòng lặp bên trong kéo dài 88 dòng mã lắp ráp. Tôi đã không cố gắng để hiểu tất cả về nó, nhưng phần đáng ngờ nhất là 13 cách gọi "callq _swift_retain" và 13 cách gọi khác của "callq _swift_release". Đó là, 26 cuộc gọi chương trình con trong vòng lặp bên trong !


Chỉnh sửa 3: Trong các bình luận, Ferruccio đã yêu cầu các điểm chuẩn công bằng theo nghĩa là chúng không dựa vào các chức năng tích hợp (ví dụ: sắp xếp). Tôi nghĩ rằng chương trình sau đây là một ví dụ khá hay:

let n = 10000
var x = [Int](repeating: 1, count: n)
for i in 0..<n {
    for j in 0..<n {
        x[i] = x[j]
    }
}

Không có số học, vì vậy chúng tôi không cần phải lo lắng về việc tràn số nguyên. Điều duy nhất mà chúng tôi làm chỉ là rất nhiều tài liệu tham khảo mảng. Và kết quả là ở đây, Swift Swift -O3 thua với hệ số gần 500 so với -Ofast:

  • C ++ -O3: 0,05 giây
  • C ++ -O0: 0,4 giây
  • Java: 0,2 giây
  • Python với PyPy: 0,5 s
  • Con trăn: 12 giây
  • Swift -Fast: 0,05 giây
  • Swift -O3: 23 giây
  • Swift -O0: 443 giây

(Nếu bạn lo ngại rằng trình biên dịch có thể tối ưu hóa hoàn toàn các vòng lặp vô nghĩa, bạn có thể thay đổi nó thành ví dụ x[i] ^= x[j]và thêm một câu lệnh in xuất ra x[0]. Điều này không thay đổi bất cứ điều gì; thời gian sẽ rất giống nhau.)

Và vâng, ở đây, việc triển khai Python là một triển khai Python thuần túy ngu ngốc với một danh sách các số nguyên và được lồng cho các vòng lặp. Nó sẽ được nhiều chậm hơn so với unoptimized Swift. Một cái gì đó dường như bị phá vỡ nghiêm trọng với Swift và lập chỉ mục mảng.


Chỉnh sửa 4: Những sự cố này (cũng như một số vấn đề về hiệu suất khác) dường như đã được khắc phục trong Xcode 6 beta 5.

Để sắp xếp, bây giờ tôi có các thời gian sau:

  • kêu vang ++ -O3: 0,06 giây
  • swiftc -Fast: 0,1 s
  • swiftc -O: 0,1 s
  • swiftc: 4 giây

Đối với các vòng lặp lồng nhau:

  • kêu vang ++ -O3: 0,06 giây
  • swiftc -Fast: 0,3 giây
  • swiftc -O: 0,4 giây
  • swiftc: 540 giây

Dường như không còn lý do nào nữa để sử dụng không an toàn -Ofast(aka -Ounchecked); đồng bằng -Osản xuất mã tốt như nhau.


20
Đây là một câu hỏi "Swift chậm hơn 100 lần so với C": stackoverflow.com/questions/24102609/
Khăn

16
Và đây là cuộc thảo luận về tài liệu tiếp thị của Apple liên quan đến hiệu suất tốt của Swift trong việc sắp xếp: lập trình
viên.stackexchange.com/q/242816/913

2
Bạn có thể biên dịch với : xcrun --sdk macosx swift -O3. Nó ngắn hơn.
Khách sạn miền Nam

3
Liên kết này cho thấy một số hoạt động cơ bản khác so với Objective-C.
Wold

4
Với Beta 5, đã có sự cải thiện đáng kể về tốc độ của Swift - xem bài đăng này của Jesse Squires để biết thêm chi tiết.
Nate Cook

Câu trả lời:


460

tl; dr Swift 1.0 hiện nhanh như C bằng điểm chuẩn này bằng mức tối ưu hóa phát hành mặc định [-O].


Đây là một quicksort tại chỗ trong 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)
}

Và tương tự trong 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);
}

Cả hai công việc:

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]

Cả hai được gọi trong cùng một chương trình như bằng văn bản.

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();

Điều này chuyển đổi thời gian tuyệt đối thành giây:

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;
}

Dưới đây là tóm tắt về các mức độ tối ưu hóa của trình biên dịch:

[-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.

Thời gian tính bằng giây với [-Onone] trong n = 10_000 :

Swift:            0.895296452
C:                0.001223848

Đây là phần dựng sẵn của Swift () cho n = 10_000 :

Swift_builtin:    0.77865783

Đây là [-O] cho n = 10_000 :

Swift:            0.045478346
C:                0.000784666
Swift_builtin:    0.032513488

Như bạn có thể thấy, hiệu suất của Swift được cải thiện với hệ số 20.

Theo câu trả lời của mweathers , việc đặt [-Fast] tạo ra sự khác biệt thực sự, dẫn đến những lần này cho n = 10_000 :

Swift:            0.000706745
C:                0.000742374
Swift_builtin:    0.000603576

Và với n = 1_000_000 :

Swift:            0.107111846
C:                0.114957179
Swift_sort:       0.092688548

Để so sánh, đây là với [-Onone] với n = 1_000_000 :

Swift:            142.659763258
C:                0.162065333
Swift_sort:       114.095478272

Vì vậy, Swift không có tối ưu hóa đã chậm hơn gần 1000 lần so với C trong điểm chuẩn này, ở giai đoạn phát triển này. Mặt khác, với cả hai trình biên dịch được đặt thành [-Fast] Swift thực sự cũng hoạt động ít nhất nếu không tốt hơn một chút so với C.

Nó đã được chỉ ra rằng [-Ofast] thay đổi ngữ nghĩa của ngôn ngữ, làm cho nó có khả năng không an toàn. Đây là những gì Apple nêu trong ghi chú phát hành Xcode 5.0:

Một mức tối ưu hóa mới -Fast, có sẵn trong LLVM, cho phép tối ưu hóa mạnh mẽ. -Ofast nới lỏng một số hạn chế bảo thủ, chủ yếu cho các hoạt động dấu phẩy động, an toàn cho hầu hết các mã. Nó có thể mang lại chiến thắng hiệu suất cao đáng kể từ trình biên dịch.

Họ đều ủng hộ nó. Dù điều đó có khôn ngoan hay không tôi không thể nói, nhưng từ những gì tôi có thể nói có vẻ hợp lý khi sử dụng [-Ofast] trong một bản phát hành nếu bạn không thực hiện số học dấu phẩy động có độ chính xác cao và bạn tự tin không có số nguyên hoặc tràn mảng có thể có trong chương trình của bạn. Nếu bạn cần hiệu suất cao kiểm tra tràn / số học chính xác thì hãy chọn ngôn ngữ khác ngay bây giờ.

CẬP NHẬT BETA 3:

n = 10_000 với [-O] :

Swift:            0.019697268
C:                0.000718064
Swift_sort:       0.002094721

Swift nói chung là nhanh hơn một chút và có vẻ như loại tích hợp sẵn của Swift đã thay đổi khá đáng kể.

CẬP NHẬT CUỐI CÙNG:

[-Một người] :

Swift:   0.678056695
C:       0.000973914

[-O] :

Swift:   0.001158492
C:       0.001192406

[-Được xem xét] :

Swift:   0.000827764
C:       0.001078914

25
Sử dụng -emit-sil để xuất mã SIL trung gian cho thấy những gì đang được giữ lại (argh, tràn ngăn xếp khiến điều này không thể định dạng). Đó là một đối tượng bộ đệm nội bộ trong Mảng. Điều này chắc chắn nghe giống như một lỗi tối ưu hóa, trình tối ưu hóa ARC sẽ có thể loại bỏ các phần giữ lại mà không cần -Ofast.
Catfish_Man

Chúng tôi sẽ không đồng ý rằng chúng tôi phải sử dụng ngôn ngữ khác nếu muốn sử dụng tối ưu hóa Ofast. Nó sẽ phải giải quyết tương tự với câu hỏi về kiểm tra giới hạn và các vấn đề nhỏ khác nếu chọn một ngôn ngữ khác như C. Swift nhanh chóng chính xác vì nó được bảo mật theo mặc định và tùy chọn nhanh và không an toàn nếu cần. Điều này cho phép lập trình viên cũng gỡ lỗi mã của bạn, để đảm bảo mọi thứ đều ổn và biên dịch bằng Ofast. Khả năng sử dụng các tiêu chuẩn hiện đại và có sức mạnh của ngôn ngữ "không an toàn" như C là rất tuyệt vời.
Wallacy

2
Nếu bạn có thể cho tôi biết làm thế nào nó có thể không hợp lệ xin vui lòng làm. tôi luôn muốn tìm hiểu thêm
Joseph Mark

3
đã thực hiện một bản cập nhật cuối cùng, Swift hiện nhanh như C bằng điểm chuẩn này bằng cách sử dụng tối ưu hóa tiêu chuẩn.
Joseph Mark

4
Mẹo: Cả hai triển khai quicksort Swift và C của bạn đều có thể được cải thiện nếu bạn lặp lại trên phân vùng nhỏ nhất trước! (Thay vì luôn luôn đệ quy trên phân vùng bên trái.) Quicksort được triển khai với lựa chọn trục đơn giản trong trường hợp xấu nhất mất thời gian O (n ^ 2), nhưng ngay cả trong trường hợp xấu nhất này, bạn chỉ cần không gian ngăn xếp O (log n) bằng cách đệ quy trên phân vùng nhỏ hơn trước.
Macneil Shonle

108

TL; DR : Có, việc triển khai ngôn ngữ Swift duy nhất là chậm, ngay bây giờ . Nếu bạn cần mã nhanh, số (và các loại mã khác, có lẽ là mã), chỉ cần đi với mã khác. Trong tương lai, bạn nên đánh giá lại sự lựa chọn của bạn. Nó có thể đủ tốt cho hầu hết các mã ứng dụng được viết ở mức cao hơn.

Từ những gì tôi thấy trong SIL và LLVM IR, có vẻ như họ cần một loạt các tối ưu hóa để loại bỏ giữ lại và phát hành, có thể được triển khai trong Clang (cho Objective-C), nhưng họ chưa chuyển chúng. Đó là lý thuyết mà tôi đang thực hiện (hiện tại, tôi vẫn cần phải xác nhận rằng Clang có làm gì đó với nó không), vì một trình hồ sơ chạy trên trường hợp thử nghiệm cuối cùng của câu hỏi này mang lại kết quả khá hay này:

Hồ sơ thời gian trên -O3 Hồ sơ thời gian trên -Ofast

Như đã nói bởi nhiều người khác, -Ofasthoàn toàn không an toàn và thay đổi ngữ nghĩa ngôn ngữ. Đối với tôi, đó là tại khu vực Nếu bạn sẽ sử dụng điều đó, chỉ cần sử dụng một giai đoạn ngôn ngữ khác. Tôi sẽ đánh giá lại lựa chọn đó sau, nếu nó thay đổi.

-O3Thành thật mà nói, hãy gọi cho chúng tôi swift_retain, swift_releasegọi một cách trung thực, đừng có vẻ như họ nên ở đó để lấy ví dụ này. Trình tối ưu hóa nên đã loại bỏ (hầu hết) chúng AFAICT, vì nó biết hầu hết các thông tin về mảng và biết rằng nó (ít nhất) có một tham chiếu mạnh đến nó.

Nó không nên phát ra nhiều giữ lại hơn khi nó thậm chí không gọi các chức năng có thể giải phóng các đối tượng. Tôi không nghĩ rằng một hàm tạo mảng có thể trả về một mảng nhỏ hơn những gì được yêu cầu, điều đó có nghĩa là rất nhiều kiểm tra được phát ra là vô ích. Nó cũng biết rằng số nguyên sẽ không bao giờ vượt quá 10k, do đó, kiểm tra tràn có thể được tối ưu hóa (không phải vì -Ofastkỳ lạ, mà vì ngữ nghĩa của ngôn ngữ (không có gì khác thay đổi mà var cũng không thể truy cập và thêm tới 10k là an toàn cho các loại Int).

Tuy nhiên, trình biên dịch có thể không thể mở hộp các mảng hoặc các thành phần của mảng, vì chúng được truyền vào sort(), đây là một hàm bên ngoài và phải lấy các đối số mà nó mong đợi. Điều này sẽ khiến chúng ta phải sử dụng các Intgiá trị một cách gián tiếp, điều này sẽ khiến nó đi chậm hơn một chút. Điều này có thể thay đổi nếu sort()hàm chung (không phải theo cách đa phương thức) có sẵn cho trình biên dịch và được nội tuyến.

Đây là một ngôn ngữ rất mới (công khai) và nó đang trải qua những gì tôi cho là có nhiều thay đổi, vì có những người (rất nhiều) liên quan đến ngôn ngữ Swift yêu cầu phản hồi và tất cả đều nói rằng ngôn ngữ chưa kết thúc và sẽ thay đổi.

Mã đã được sử dụng:

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")

Tái bút: Tôi không phải là chuyên gia về Objective-C cũng như tất cả các cơ sở từ ca cao , Objective-C hoặc thời gian chạy của Swift. Tôi cũng có thể giả định một số điều mà tôi đã không viết.


Tuy nhiên, trình biên dịch có thể không thể bỏ hộp các mảng hoặc các thành phần của mảng, vì chúng được truyền vào sort (), đây là một hàm bên ngoài và phải lấy các đối số mà nó mong đợi. Điều đó không quan trọng đối với một trình biên dịch tương đối tốt. Truyền siêu dữ liệu (trong con trỏ - 64 bit cung cấp rất nhiều đê) về dữ liệu thực tế và phân nhánh nó trong hàm được gọi.
bestsss

3
Chính xác thì điều gì làm cho -Ofast"hoàn toàn không an toàn"? Giả sử bạn biết cách kiểm tra mã của mình và loại trừ tràn.
Joseph Mark

@sjeohp: Điều đó thực sự giả định rất nhiều :-) Kiểm tra mã và loại trừ tràn là khó thực hiện. Từ kinh nghiệm của tôi (tôi làm công việc biên dịch và đã kiểm tra một số cơ sở mã lớn), và những gì tôi đã nghe từ những người làm công việc biên dịch trên các công ty lớn, việc vượt qua và các hành vi không xác định khác là khó khăn . Ngay cả lời khuyên của Apple (chỉ là một ví dụ) về sửa lỗi UB, đôi khi ( Randomascii.wordpress.com/2014/04/17/ ích ). -Ofastcũng thay đổi ngữ nghĩa ngôn ngữ, nhưng tôi không thể tài trợ cho bất kỳ tài liệu nào cho nó. Làm thế nào bạn có thể tự tin rằng bạn biết những gì nó đang làm?
filcab

@bestsss: Có thể, nhưng nó có thể không hữu ích. Nó thêm kiểm tra trên mọi quyền truy cập vào Int []. Nó phụ thuộc nếu các mảng Int và một vài loại nguyên thủy khác (tối đa bạn có 3 bit) được sử dụng rất nhiều (đặc biệt là khi bạn có thể hạ xuống C nếu bạn cần). Nó cũng sử dụng hết một số bit mà họ có thể muốn sử dụng nếu cuối cùng, họ muốn thêm không phải ARC ARC. Nó cũng không mở rộng thành khái quát với nhiều hơn một đối số. Vì chúng có tất cả các loại, nên việc chuyên môn hóa tất cả các mã đã chạm vào Int [] (nhưng không phải Int? []) Sẽ dễ dàng hơn nhiều. Nhưng sau đó bạn có Obj-C interop để lo lắng.
filcab

@filcab, GC không phải ARC (tức là thực) sẽ thực sự hữu ích nhưng họ cần một cái gì đó không tương thích với C nếu họ muốn một GC thực sự đồng thời, không STW. Tôi không lo lắng về 'mọi quyền truy cập Int[]' vì điều đó phụ thuộc vào mức độ trình biên dịch có thể nội tuyến và nó có thể nội tuyến các vòng lặp chặt chẽ với / sau một số hướng dẫn.
bestsss

53

Tôi quyết định xem cái này cho vui, và đây là thời gian mà tôi nhận được:

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

Nhanh

// 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()

Các kết quả:

Swift 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

Swift 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

Swift 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

Nó có vẻ là hiệu suất tương tự nếu tôi biên dịch với -Ounchecked.

Swift 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

Dường như đã có một hồi quy hiệu suất từ ​​Swift 2.0 sang Swift 3.0 và lần đầu tiên tôi cũng thấy sự khác biệt giữa -O-Ouncheckedlần đầu tiên.

Swift 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

Swift 4 cải thiện hiệu suất một lần nữa, trong khi vẫn duy trì khoảng cách giữa -O-Ounchecked. -O -whole-module-optimizationđã không xuất hiện để làm cho một sự khác biệt.

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;
}

Các kết quả:

Quả táo 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

Quả táo 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

Táo Clang 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

Quả táo 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

Apple Clang 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

Bản án

Vào thời điểm viết bài này, sắp xếp của Swift rất nhanh, nhưng chưa nhanh bằng cách sắp xếp của C ++ khi được biên dịch cùng -Ovới các trình biên dịch & thư viện ở trên. Với -Ounchecked, nó dường như nhanh như C ++ trong Swift 4.0.2 và Apple LLVM 9.0.0.


2
Trong thực tế, bạn không bao giờ không nên gọi vector :: reserved () trước khi chèn mười triệu phần tử.
BJovke

Có lẽ! Chỉ có các loại đang được tính thời gian tại thời điểm này.
Tìm hiểu OpenGL ES

34

Từ The Swift Programming Language:

Thư viện tiêu chuẩn của Hàm sắp xếp Swift cung cấp một hàm gọi là sort, sắp xếp một mảng các giá trị của một loại đã biết, dựa trên đầu ra của một bao đóng sắp xếp mà bạn cung cấp. Khi nó hoàn thành quá trình sắp xếp, hàm sort sẽ trả về một mảng mới có cùng kiểu và kích thước như kiểu cũ, với các phần tử của nó theo đúng thứ tự được sắp xếp.

Các sortchức năng có hai tờ khai.

Khai báo mặc định cho phép bạn chỉ định đóng cửa so sánh:

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

Và một khai báo thứ hai chỉ lấy một tham số duy nhất (mảng) và được "mã hóa cứng để sử dụng bộ so sánh ít hơn".

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

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

Tôi đã thử nghiệm phiên bản sửa đổi mã của bạn trong một sân chơi với phần đóng được thêm vào để tôi có thể theo dõi chức năng chặt chẽ hơn một chút và tôi thấy rằng với n được đặt thành 1000, việc đóng được gọi khoảng 11.000 lần.

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

Nó không phải là một chức năng hiệu quả, tôi khuyên bạn nên sử dụng triển khai chức năng sắp xếp tốt hơn.

BIÊN TẬP:

Tôi đã xem trang wikipedia Quicksort và viết một triển khai Swift cho nó. Đây là toàn bộ chương trình tôi đã sử dụng (trong một sân chơi)

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
}

Sử dụng cái này với n = 1000, tôi thấy rằng

  1. quickSort () đã được gọi khoảng 650 lần,
  2. khoảng 6000 giao dịch hoán đổi đã được thực hiện,
  3. và có khoảng 10.000 so sánh

Có vẻ như phương pháp sắp xếp tích hợp là (hoặc gần với) sắp xếp nhanh và rất chậm ...


17
Có lẽ tôi hoàn toàn sai, nhưng theo en.wikipedia.org/wiki/Quicksort , số lượng so sánh trung bình trong Quicksort là 2*n*log(n). Đó là 13815 so sánh để sắp xếp n = 1000 phần tử, vì vậy nếu hàm so sánh được gọi khoảng 11000 lần thì có vẻ không tệ lắm.
Martin R

6
Ngoài ra, Apple tuyên bố rằng "sắp xếp đối tượng phức tạp" (bất kể đó là gì) nhanh hơn 3,9 lần trong Swift so với Python. Do đó, không cần thiết phải tìm "chức năng sắp xếp tốt hơn". - Nhưng Swift vẫn đang trong quá trình phát triển ...
Martin R

6
không đề cập đến logarit tự nhiên.
Martin R

24
log(n)đối với độ phức tạp thuật toán thường quy về log cơ sở-2. Lý do không nêu cơ sở là luật thay đổi cơ sở cho logarit chỉ đưa ra một số nhân không đổi, được loại bỏ cho các mục đích của ký hiệu O.
minuteman3

3
Về cuộc thảo luận về logarit tự nhiên so với logarit cơ sở 2: Tuyên bố chính xác từ trang Wikipedia là số lượng so sánh trung bình cần thiết cho n phần tử là C(n) = 2n ln n ≈ 1.39n log₂ n. Với n = 1000, điều này cho C (n) = 13815 và đó không phải là "ký hiệu big-O".
Martin R

18

Kể từ Xcode 7, bạn có thể bật Fast, Whole Module Optimization. Điều này sẽ tăng hiệu suất của bạn ngay lập tức.

nhập mô tả hình ảnh ở đây


12

Hiệu suất Swift Array được xem lại:

Tôi đã viết điểm chuẩn của riêng mình so sánh Swift với C / Objective-C. Điểm chuẩn của tôi tính các số nguyên tố. Nó sử dụng mảng các số nguyên tố trước để tìm các thừa số nguyên tố trong mỗi ứng cử viên mới, vì vậy nó khá nhanh. Tuy nhiên, nó thực hiện TẤN về đọc mảng và ít ghi vào mảng.

Ban đầu tôi đã làm điểm chuẩn này so với Swift 1.2. Tôi quyết định cập nhật dự án và chạy nó với Swift 2.0.

Dự án cho phép bạn chọn giữa việc sử dụng mảng nhanh chóng thông thường và sử dụng bộ đệm bộ nhớ không an toàn Swift bằng cách sử dụng ngữ nghĩa mảng.

Đối với C / Objective-C, bạn có thể chọn sử dụng mảng NSArrays hoặc C malloc'ed.

Các kết quả thử nghiệm dường như khá giống nhau với tối ưu hóa mã nhanh nhất, nhỏ nhất ([-0s]) hoặc nhanh nhất, tích cực ([-0fast]).

Hiệu suất Swift 2.0 vẫn còn khủng khiếp khi tắt tối ưu hóa mã, trong khi hiệu suất C / Objective-C chỉ chậm hơn ở mức độ vừa phải.

Điểm mấu chốt là các tính toán dựa trên mảng của C malloc là nhanh nhất, với biên độ khiêm tốn

Swift với bộ đệm không an toàn mất khoảng 1,19X - 1,20X so với mảng C malloc'd khi sử dụng tối ưu hóa mã nhanh nhất, nhỏ nhất. sự khác biệt có vẻ hơi ít với tối ưu hóa nhanh, mạnh mẽ (Swift mất nhiều thời gian hơn từ 1,18x đến 1,16x so với C.

Nếu bạn sử dụng mảng Swift thông thường, sự khác biệt với C sẽ lớn hơn một chút . (Swift mất ~ 1,22 đến 1,23 lâu hơn.)

Mảng Swift thông thường DRAMATICALLYnhanh hơn so với Swift 1.2 / Xcode 6. Hiệu suất của chúng rất gần với mảng dựa trên bộ đệm không an toàn của Swift mà việc sử dụng bộ đệm không an toàn dường như không thực sự đáng ngại nữa, điều này rất lớn.

Hiệu suất BTW, Objective-C NSArray bốc mùi. Nếu bạn sẽ sử dụng các đối tượng bộ chứa riêng trong cả hai ngôn ngữ, Swift sẽ LỪA ĐẢO nhanh hơn.

Bạn có thể kiểm tra dự án của tôi trên github tại SwiftPerformanceBenchmark

Nó có một giao diện người dùng đơn giản giúp thu thập số liệu thống kê khá dễ dàng.

Thật thú vị khi sắp xếp dường như nhanh hơn một chút trong Swift so với C bây giờ, nhưng thuật toán số nguyên tố này vẫn nhanh hơn trong Swift.


8

Vấn đề chính được đề cập bởi những người khác nhưng không được gọi là đủ là -O3không có gì trong Swift (và không bao giờ có) vì vậy khi được biên dịch với nó, nó không được tối ưu hóa một cách hiệu quả ( -Onone).

Tên tùy chọn đã thay đổi theo thời gian để một số câu trả lời khác có cờ lỗi thời cho các tùy chọn xây dựng. Các tùy chọn hiện tại chính xác (Swift 2.2) là:

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

Tối ưu hóa toàn bộ mô-đun có một biên dịch chậm hơn nhưng có thể tối ưu hóa trên các tệp trong mô-đun tức là trong từng khung và trong mã ứng dụng thực tế nhưng không phải giữa chúng. Bạn nên sử dụng điều này cho bất cứ điều gì hiệu suất quan trọng)

Bạn cũng có thể vô hiệu hóa kiểm tra an toàn để có tốc độ cao hơn nhưng với tất cả các xác nhận và điều kiện tiên quyết không chỉ bị vô hiệu hóa mà còn được tối ưu hóa trên cơ sở rằng chúng là chính xác. Nếu bạn từng nhấn một xác nhận, điều này có nghĩa là bạn đang ở trong hành vi không xác định. Sử dụng hết sức thận trọng và chỉ khi bạn xác định rằng việc tăng tốc độ là đáng giá cho bạn (bằng cách thử nghiệm). Nếu bạn thấy nó có giá trị đối với một số mã, tôi khuyên bạn nên tách mã đó thành một khung riêng và chỉ vô hiệu hóa kiểm tra an toàn cho mô-đun đó.


Câu trả lời này đã hết hạn. Kể từ Swift 4.1, tùy chọn tối ưu hóa toàn bộ mô-đun là một boolean riêng biệt có thể được kết hợp với các cài đặt khác và hiện có một -Os để tối ưu hóa kích thước. Tôi có thể cập nhật khi có thời gian để kiểm tra các cờ tùy chọn chính xác.
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) 

Đây là Blog của tôi về Sắp xếp nhanh- Mẫu Github Sắp xếp nhanh

Bạn có thể xem thuật toán phân vùng của Lomuto trong Phân vùng danh sách. Viết bằng Swift.


4

Swift 4.1 giới thiệu -Osizechế độ tối ưu hóa mới .

Trong Swift 4.1, trình biên dịch hiện hỗ trợ chế độ tối ưu hóa mới cho phép tối ưu hóa chuyên dụng để giảm kích thước mã.

Trình biên dịch Swift đi kèm với tối ưu hóa mạnh mẽ. Khi biên dịch với -O, trình biên dịch sẽ cố gắng chuyển đổi mã để nó thực thi với hiệu suất tối đa. Tuy nhiên, sự cải thiện hiệu năng thời gian chạy này đôi khi có thể đi kèm với sự đánh đổi kích thước mã tăng lên. Với chế độ tối ưu hóa kích thước mới, người dùng có thể chọn biên dịch cho kích thước mã tối thiểu thay vì tốc độ tối đa.

Để bật chế độ tối ưu hóa kích thước trên dòng lệnh, hãy sử dụng -Osize thay vì -O.

Đọc thêm: https://swift.org/blog/osize/

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.