swift - 循环和范围运算符的基准

标签 swift loops for-loop benchmarking

我读到基于范围的循环在某些编程语言上具有更好的性能。在 Swift 中也是这样吗?例如在 Playground 中:

func timeDebug(desc: String, function: ()->() )
{
    let start : UInt64 = mach_absolute_time()
    function()
    let duration : UInt64 = mach_absolute_time() - start

    var info : mach_timebase_info = mach_timebase_info(numer: 0, denom: 0)
    mach_timebase_info(&info)

    let total = (duration * UInt64(info.numer) / UInt64(info.denom)) / 1_000
    println("\(desc): \(total) µs.")
}

func loopOne(){
    for i in 0..<4000 {
        println(i);
    }
}

func loopTwo(){
    for var i = 0; i < 4000; i++ {
        println(i);
    }
}

基于范围的循环

timeDebug("Loop One time"){
    loopOne(); // Loop One time: 2075159 µs.
}

正常的for循环

timeDebug("Loop Two time"){
    loopTwo(); // Loop Two time: 1905956 µs.
}

如何在 swift 中正确进行基准测试?

//在设备上更新

首次运行

循环两次:54 µs

循环一次:482 µs

第二

循环两次:44 µs

循环一次:382 µs

第三

循环两次:43 µs

循环一次:419 µs

第四

循环两次:44 µs

循环一次:399 µs

//更新2

    func printTimeElapsedWhenRunningCode(title:String, operation:()->()) {
        let startTime = CFAbsoluteTimeGetCurrent()
        operation()
        let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
        println("Time elapsed for \(title): \(timeElapsed) s")
    }


    printTimeElapsedWhenRunningCode("Loop Two time") {
        loopTwo(); // Time elapsed for Loop Two time: 4.10079956054688e-05 s
    }

    printTimeElapsedWhenRunningCode("Loop One time") {
        loopOne(); // Time elapsed for Loop One time: 0.000500023365020752 s.
    }

最佳答案

您不应该真正在 Playground 中进行基准测试,因为它们尚未优化。除非您对调试时需要多长时间感兴趣,否则您应该只对优化的构建进行基准测试 ( swiftc -O )。

要了解为什么基于范围的循环可以更快,您可以查看为两个选项生成的程序集:

基于范围

% echo "for i in 0..<4_000 { println(i) }" | swiftc -O -emit-assembly -
; snip opening boiler plate...
LBB0_1:
    movq    %rbx, -32(%rbp)
; increment i
    incq    %rbx
    movq    %r14, %rdi
    movq    %r15, %rsi
; print (pre-incremented) i
    callq   __TFSs7printlnU__FQ_T_
; compare i to 4_000
    cmpq    $4000, %rbx
; loop if not equal
    jne LBB0_1
    xorl    %eax, %eax
    addq    $8, %rsp
    popq    %rbx
    popq    %r14
    popq    %r15
    popq    %rbp
    retq
    .cfi_endproc

C 风格 for循环

% echo "for var i = 0;i < 4_000;++i { println(i) }" | swiftc -O -emit-assembly -
; snip opening boiler plate...
LBB0_1:
    movq    %rbx, -32(%rbp)
    movq    %r14, %rdi
    movq    %r15, %rsi
; print i
    callq   __TFSs7printlnU__FQ_T_
; increment i
    incq    %rbx
; jump if overflow
    jo  LBB0_4
; compare i to 4_000
    cmpq    $4000, %rbx
; loop if less than
    jl  LBB0_1
    xorl    %eax, %eax
    addq    $8, %rsp
    popq    %rbx
    popq    %r14
    popq    %r15
    popq    %rbp
    retq
LBB0_4:
; raise illegal instruction due to overflow
    ud2
    .cfi_endproc

所以 C 风格循环速度较慢的原因是它执行了额外的操作 - 检查溢出。要么 Range编写是为了避免溢出检查(或预先进行),或者优化器更能够使用 Range 来消除它。版本。

如果您改用免检查加法运算符,则可以消除此检查。这会生成与基于范围的版本几乎相同的代码(唯一的区别是代码的一些无关紧要的顺序):

% echo "for var i = 0;i < 4_000;i = i &+ 1 { println(i) }" | swiftc -O -emit-assembly -
; snip
LBB0_1:
    movq    %rbx, -32(%rbp)
    movq    %r14, %rdi
    movq    %r15, %rsi
    callq   __TFSs7printlnU__FQ_T_
    incq    %rbx
    cmpq    $4000, %rbx
    jne LBB0_1
    xorl    %eax, %eax
    addq    $8, %rsp
    popq    %rbx
    popq    %r14
    popq    %r15
    popq    %rbp
    retq
    .cfi_endproc

永远不要对未优化的构建进行基准测试

如果您想了解原因,请尝试查看 Range 的输出基于上述版本,但没有优化:echo "for var i = 0;i < 4_000;++i { println(i) }" | swiftc -Onone -emit-assembly - 。您将看到它输出了更多更多代码。那是因为Range通过 for…in 使用是一个抽象,一个与自定义运算符和返回生成器的函数一起使用的结构,并且执行大量安全检查和其他有用的操作。这使得编写/阅读代码变得更加容易。但是当您打开优化器时,所有这些都会消失,您将得到非常高效的代码。

基准测试

至于基准测试的方法,这是我倾向于使用的代码,只需替换数组:

import CoreFoundation.CFDate

func timeRun<T>(name: String, f: ()->T) -> String {
    let start = CFAbsoluteTimeGetCurrent()
    let result = f()
    let end = CFAbsoluteTimeGetCurrent()
    let timeStr = toString(Int((end - start) * 1_000_000))
    return "\(name)\t\(timeStr)µs, produced \(result)"
}

let n = 4_000

let runs: [(String,()->Void)] = [
    ("for in range", {
        for i in 0..<n { println(i) }    
    }),
    ("plain ol for", {
        for var i = 0;i < n;++i { println(i) }    
    }),
    ("w/o overflow", {
        for var i = 0;i < n;i = i &+ 1 { println(i) }    
    }),
]

println("\n".join(map(runs, timeRun)))

但结果可能毫无意义,因为 println 期间存在抖动。可能会掩盖实际测量。要真正进行基准测试(假设您不仅仅信任组装分析:),您需要用非常轻量级的东西替换它。

关于swift - 循环和范围运算符的基准,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30274187/

相关文章:

java - 初级java for循环

swift - 使用 sprite 工具包垂直滚动背景的问题

ios - 如何在 iOS 中绘制一个点?

ios 约束不起作用

ios - 从 CloudKit 获取并保存到 CoreData

java - 为什么这个 for 循环不打印任何内容?

javascript - 如何通过类获取跨度内的文本。 (for循环内部)

jQuery如何循环函数

node.js - Node : Getting paginated get requests

java - 如果大小写匹配,则从 ArrayList 中删除名称不起作用