我在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.06s.

在Python中,它需要0.6s(没有技巧,只有y=sorted(x)表示整数列表).

在Swift中,如果我使用以下命令编译它,则需要6s:

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

如果我用下面的命令编译它,需要花费88s美元:

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

Xcode中"Release"版本与"Debug"版本的时间安排类似.

这里怎么了?与C++相比,我可以理解一些性能损失,但与纯Python相比,它的速度并不是10倍.


Edit:的天气注意到,-O3-Ofast的变化使得这个代码运行的速度和C++版本差不多快!然而,-Ofast极大地改变了语言的语义——在我的测试中,它是disabled the checks for integer overflows and array indexing overflows.例如,对于-Ofast,下面的Swift代码在没有崩溃的情况下silent运行(并打印出一些垃圾):

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

所以-Ofast不是我们想要的;Swift的全部意义在于我们已经建立了安全网.当然,安全网对性能有一定影响,但它们不应使程序的速度降低-Ofast倍.请记住,Java已经判断了数组边界,在典型情况下,速度的降低要比2小得多.在Clang和GCC中,我们有-ftrapv个用于判断(有符号)整数溢出,而且速度也不慢.

因此产生了一个问题:我们如何在不失go 安全网的情况下在Swift中获得合理的性能?


Edit 2:我做了更多的基准测试,沿着

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

(这里有xor操作,这样我就可以更容易地在汇编代码中找到相关的循环.我试图 Select 一个容易发现但"无害"的操作,因为它不需要任何与整数溢出相关的判断.)

同样,-O3-Ofast之间的表现存在巨大差异.所以我看了一下汇编代码:

  • 有了-Ofast美元,我得到的几乎是我所期望的.相关部分是一个包含5条机器语言指令的循环.

  • 有了-O3英镑,我得到了超出我想象的东西.内部循环跨越88行汇编代码.我并没有试图理解所有的内容,但最可疑的部分是13次调用"callq_swift_retain"和另外13次调用"callq_swift_release".就是26 subroutine calls in the inner loop


Edit 3:在 comments 中,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]
    }
}

没有算术运算,所以我们不需要担心整数溢出.很多数组引用只是我们做的事情.结果显示,与Ofast相比,Swift-O3损失了近500倍:

  • C++—O3:0.05 s
  • C++ -O0:0.4 s
  • Java :0.2 s
  • 带PyPy的Python:0.5s
  • Python :12 s
  • Swift-Ofast:0.05秒
  • Swift -O3:23 s
  • Swift-O0:443秒

(如果您担心编译器可能会完全优化无意义的循环,您可以将其更改为例如x[i] ^= x[j],并添加输出x[0]的打印语句.这不会改变任何内容;计时将非常相似.)

是的,这里的Python实现是一个愚蠢的纯Python实现,有一个int列表和嵌套for循环.它应该比未优化的Swift慢much.Swift和数组索引似乎严重 destruct 了某些功能.


Edit 4:这些问题(以及其他一些性能问题)似乎已在Xcode 6 beta 5中修复.

对于排序,我现在有以下时间安排:

  • 叮当声++-O3:0.06秒
  • swiftc-Ofast:0.1秒
  • swiftc-O:0.1秒
  • swiftc:4秒

对于嵌套循环:

  • 叮当声++-O3:0.06秒
  • swiftc-Ofast:0.3秒
  • swiftc-O:0.4秒
  • swiftc:540秒

似乎没有理由再使用不安全的-Ofast(又称-Ounchecked);普通-O产生同样好的代码.

推荐答案

tl;现在,使用Swift作为基准的优化版本为[0.0].


以下是Swift测试版中的就地快速排序:

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.

时间以秒为单位,[-Onone]代表n=10_000:

Swift:            0.895296452
C:                0.001223848

以下是Swift 的内置排序()n=10_000:

Swift_builtin:    0.77865783

以下是n=10_000[-O]:

Swift:            0.045478346
C:                0.000784666
Swift_builtin:    0.032513488

如你所见,Swift 的表现提高了20倍.

根据mweathers' answer,设置[-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

相比之下,[-Onone]代表n=1_000_000:

Swift:            142.659763258
C:                0.162065333
Swift_sort:       114.095478272

在这个基准测试中,没有优化的Swift比C在其开发阶段慢了近1000倍.另一方面,当两个编译器都设置为[-Ofast]时,Swift的实际性能至少与C一样好,如果不是略好的话.

有人指出,[-Ofast]改变了语言的语义,使其具有潜在的不安全性.这是苹果在Xcode 5.0发行说明中所说的:

LLVM中提供了一个新的优化级别——Ofast,支持积极的优化-Ofast放松了一些保守的限制,主要是针对浮点运算的限制,这些限制对大多数代码来说都是安全的.它可以从编译器中获得显著的高性能胜利.

他们几乎都主张这样做.无论这是否明智,我都不能说,但从我所能看出,如果你不做高精度浮点运算,并且你确信你的程序中不可能出现整数或数组溢出,那么在发布版中使用[-Ofast]似乎是合理的.如果您确实需要高性能and溢出判断/精确算法,那么现在就 Select 另一种语言.

更新3测试版:

n=10_000[-O]:

Swift:            0.019697268
C:                0.000718064
Swift_sort:       0.002094721

一般来说,Swift 的速度要快一点,看起来Swift 的内置排序已经发生了很大的变化.

FINAL UPDATE:

[-Onone]:

Swift:   0.678056695
C:       0.000973914

[-O]:

Swift:   0.001158492
C:       0.001192406

[-Ounchecked]:

Swift:   0.000827764
C:       0.001078914

Swift相关问答推荐

Swift UI在视图中排序不同的 struct

在运行时检测蒸汽工人的类型

动画过程中的SwiftUI重绘视图

出错-无法将季节类型的值转换为预期的参数类型';[水果]';

如何在SWIFT Memory中处理对VAR数组的更新(对COW感到困惑)

有条件地在同一视图上设置两个不同的过渡

在 SwiftUI 视图中观察 UIViewRepresentable 的 @State var 变化

如何从UIKit中的AttributeContainer获取属性并将其分配给某个变量

是否可以利用 Codable 从 Dictionary 初始化符合类型

String(validatingUTF8:) 和 String(utf8String:) 之间有区别吗?

如何裁剪图像 3:4 ?迅速

Swift 全局函数列表

SwiftUI:决定键盘重叠的内容

如何在 Swift 中的两个 UIView 中心之间添加线?

Swift:withCheckedContinuation 和 Dispatch QoSClass

如何删除标签和步进按钮之间的间距是 SwiftUI Stepper?

更改该函数的参数之一时,如何将这些 SwiftUI 异步任务合并到一个任务中以从函数中获得正确的结果

在 Swift 中获取双精度的小数部分

在 Xcode 中自动实现 Swift 协议方法

Swift 的 JSONDecoder 在 JSON 字符串中有多种日期格式?