【问题标题】:Quickest way to concatenate strings in Swift 2在 Swift 2 中连接字符串的最快方法
【发布时间】:2016-01-30 21:37:49
【问题描述】:

在 Swift 2 中连接多个字符串最快、最有效的方法是什么?

// Solution 1...
let newString:String = string1 + " " + string2
// ... Or Solution 2?
let newString:String = "\(string1) \(string2)"

或者是它在程序员看来的唯一区别?

【问题讨论】:

  • 还有String函数appendContentsOf,它只追加1个字符串,但也可以作为基准测试。

标签: swift swift2


【解决方案1】:

我在模拟器和 iPhone6S Plus 上运行了以下代码。两种情况下的结果都显示我使用的字符串的string1 + " " + string2 加法速度更快。我没有尝试使用不同类型的字符串、优化等,但您可以运行代码并检查您的特定字符串等。尝试在IBM Swift Sandbox 中在线运行此代码。 计时器结构来自这里:Measure elapsed time in Swift

要运行代码,在 Xcode 中创建单个视图应用程序并将以下代码添加到 ViewController:

import UIKit
import CoreFoundation

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let a = "abscdefghi jkl¢€@sads dljlæejktæljæ leijroptjiæa Dog!? iojeg r æioej rgæoija"
        let b = a
        timeStringAdding(a, string2: b, times: 1_000_000, repetitions: 5)
    }
            
    struct RunTimer: CustomStringConvertible {
        var begin: CFAbsoluteTime
        var end:CFAbsoluteTime
        
        init() {
            begin = CFAbsoluteTimeGetCurrent()
            end = 0
        }

        mutating func start() {
            begin = CFAbsoluteTimeGetCurrent()
            end = 0
        }

        @discardableResult
        mutating func stop() -> Double {
            if (end == 0) { end = CFAbsoluteTimeGetCurrent() }
            return Double(end - begin)
        }

        var duration: CFAbsoluteTime {
            get {
                if (end == 0) { return CFAbsoluteTimeGetCurrent() - begin }
                else { return end - begin }
            }
        }

        var description: String {
            let time = duration
            if (time > 100) {return " \(time/60) min"}
            else if (time < 1e-6) {return " \(time*1e9) ns"}
            else if (time < 1e-3) {return " \(time*1e6) µs"}
            else if (time < 1) {return " \(time*1000) ms"}
            else {return " \(time) s"}
        }
    }
    
    func timeStringAdding(string1:String, string2:String, times:Int, repetitions:Int) {
        var newString = ""
        var i = 0
        var j = 0
        var timer = RunTimer()
        
        while j < repetitions {
            i = 0
            timer.start()
            while i < times {
                newString = string1 + " " + string2
                i += 1
            }
            print("+ add \(timer)")
            
            i = 0
            timer.start()
            while i < times {
                newString = "\(string1) \(string2)"
                i += 1
            }
            print("\\(  add \(timer)")
            j += 1
        }
    }
}

在 iPhone 6S Plus 上,它给出了:

+   add  727.977991104126 ms
\(  add  1.1197350025177 s

+   add  693.499982357025 ms
\(  add  1.11982899904251 s

+   add  690.113961696625 ms
\(  add  1.12086200714111 s

+   add  707.363963127136 ms
\(  add  1.13451600074768 s

+   add  734.095990657806 ms
\(  add  1.19673496484756 s

在模拟器上(iMac Retina):

+   add  406.143009662628 ms
\(  add  594.823002815247 ms

+   add  366.503953933716 ms
\(  add  595.698952674866 ms

+   add  370.530009269714 ms
\(  add  596.457958221436 ms

+   add  369.667053222656 ms
\(  add  594.724953174591 ms

+   add  369.095981121063 ms
\(  add  595.37798166275 ms

大部分时间是为字符串结构分配和释放内存,对于那些真正好奇的人,使用Time Profiler 运行Instruments panel 中的代码,看看时间是如何分配给alloc 和free 等的,与那里也显示的机器代码。

【讨论】:

  • 嗯,这是我见过的最差的基准。这取决于实际操作以外的许多其他因素。我建议使用/usr/bin/time 命令,并将编译的可执行文件的路径作为参数。
  • @b1nary 是的,如果你想要用户时间,但我宁愿在 Instruments 面板中使用 CPU 使用率运行代码,看看时间是如何分配给 alloc 和 free 等的。但在这种情况下,快速肮脏的解决方案给出了一个想法,如果差异是一个数量级。
  • 目前在沙盒中运行基准测试有点棘手,因为作业的运行时间不得超过 5 秒。这是快速共享代码的好方法,但如果您正在运行长时间的基准测试,最好将沙盒代码复制到 Xcode 中。
  • @TheSoundDefense 谢谢,但我在 iPhone 和 Mac 上运行了答案中列出的代码。正如您所提到的,沙盒链接提供了一个有趣的选项来使用代码,我对其进行了调整,使其不会超过 5 秒的限制。但是,用户可以测试其他方法等。我正在努力宣传 IBM 的这一巨大努力!
【解决方案2】:

这个问题激起了我的好奇心,所以我把它放到了一个新项目中。这些是快速而肮脏的基准,应该与通常的盐粒一起使用,但结果很有趣。

var string1 = "This"
var string2 = "that"
var newString: String

let startTime = NSDate()
for _ in 1...100000000 {
  newString = string1 + " " + string2
}
print("Diff: \(startTime.timeIntervalSinceNow * -1)")

在我的 MacBook Pro(2014 年中 i7,2.5GHz)上运行的模拟器上运行 6 次,调试控制台的输出平均为 1.36 秒。作为调试代码部署到我的 iPhone 6S,6 次运行中所有输出的平均值为 1.33 秒。

使用相同的代码,但将字符串连接到此的行更改...

newString = "\(string1) \(string2)"

...给了我相当不同的结果。在模拟器上的 6 次运行中,调试控制台上报告的平均时间为 50.86 秒。在 iPhone 6S 上运行 6 次,平均运行时间为 88.82 秒。这几乎是两个数量级的差异。

这些结果表明,如果必须连接大量字符串,则应使用+ 运算符而不是字符串插值。

【讨论】:

    【解决方案3】:

    最快的方法可能取决于实现。您可以对编译器、编译器优化设置、库、框架、操作系统和处理器/CPU 的特定组合进行基准测试。但在不同的组合下,性能可能会有很大差异。

    string1 和 string2 是否可变的答案也可能不同,但也取决于编译器优化级别。

    【讨论】:

    • 您也可以执行以下操作:xcrun -sdk macosx swiftc -emit-assembly mytestcode.swift 以查看哪个输出的 asm 代码较少。
    【解决方案4】:

    斯威夫特 3

    在 Swift 3 中连接字符串的另一种方法是使用joined

    let stringArray = ["string1", "string2"]
    let newString = stringArray.joined(separator: " ")
    

    当然,这要求字符串在数组中。我没有做过任何时间配置文件,因此无法将其与其他建议的解决方案进行比较。

    【讨论】:

      【解决方案5】:

      TL;DR:仅当您使用大字符串(数百万字节/字符)时,差异才会明显。

      所有测试均在iMac 21.5 Late 2012, 2.7GHz Intel Core i5 上编译和执行。

      我做了一个小基准。这是代码。

      interpolation.swiftswiftc ./interpolation.swift -o ./interpolation编译

      import Swift
      
      _ = "\(Process.arguments[1]) \(Process.arguments[2])"
      

      带有-emit-assembly 标志的swiftc 的输出:

          .section    __TEXT,__text,regular,pure_instructions
          .macosx_version_min 10, 9
          .globl  _main
          .align  4, 0x90
      _main:
          .cfi_startproc
          pushq   %rbp
      Ltmp0:
          .cfi_def_cfa_offset 16
      Ltmp1:
          .cfi_offset %rbp, -16
          movq    %rsp, %rbp
      Ltmp2:
          .cfi_def_cfa_register %rbp
          subq    $128, %rsp
          movq    _globalinit_33_1BDF70FFC18749BAB495A73B459ED2F0_token5@GOTPCREL(%rip), %rax
          movq    __TZvOs7Process5_argcVs5Int32@GOTPCREL(%rip), %rcx
          movl    %edi, (%rcx)
          cmpq    $-1, (%rax)
          movq    %rsi, -56(%rbp)
          je  LBB0_2
          movq    _globalinit_33_1BDF70FFC18749BAB495A73B459ED2F0_token5@GOTPCREL(%rip), %rdi
          movq    _globalinit_33_1BDF70FFC18749BAB495A73B459ED2F0_func5@GOTPCREL(%rip), %rax
          movq    %rax, %rsi
          callq   _swift_once
      LBB0_2:
          movl    $5, %eax
          movl    %eax, %edi
          movq    __TZvOs7Process11_unsafeArgvGSpGSpVs4Int8__@GOTPCREL(%rip), %rcx
          movq    -56(%rbp), %rdx
          movq    %rdx, (%rcx)
          callq   __TTSg5SS___TFs27_allocateUninitializedArrayurFBwTGSax_Bp_
          leaq    L___unnamed_1(%rip), %rdi
          xorl    %esi, %esi
          movl    $1, %r8d
          movq    %rdx, -64(%rbp)
          movl    %r8d, %edx
          movq    %rax, -72(%rbp)
          callq   __TFSSCfT21_builtinStringLiteralBp8byteSizeBw7isASCIIBi1__SS
          movq    %rax, %rdi
          movq    %rdx, %rsi
          movq    %rcx, %rdx
          callq   __TFSSCfT26stringInterpolationSegmentSS_SS
          movq    -64(%rbp), %rsi
          movq    %rax, (%rsi)
          movq    %rdx, 8(%rsi)
          movq    %rcx, 16(%rsi)
          callq   __TFOs7Processau9argumentsGSaSS_
          movq    (%rax), %rax
          movq    %rax, %rdi
          movq    %rax, -80(%rbp)
          callq   _swift_bridgeObjectRetain
          leaq    -24(%rbp), %rdi
          movl    $1, %r8d
          movl    %r8d, %esi
          movq    -80(%rbp), %rdx
          movq    %rax, -88(%rbp)
          callq   __TTSg5SS___TFSag9subscriptFSix
          movq    -80(%rbp), %rdi
          callq   _swift_bridgeObjectRelease
          movq    -24(%rbp), %rdi
          movq    -16(%rbp), %rsi
          movq    -8(%rbp), %rdx
          callq   __TFSSCfT26stringInterpolationSegmentSS_SS
          leaq    L___unnamed_2(%rip), %rdi
          movl    $1, %r8d
          movl    %r8d, %esi
          movl    $1, %r8d
          movq    -64(%rbp), %r9
          movq    %rax, 24(%r9)
          movq    %rdx, 32(%r9)
          movq    %rcx, 40(%r9)
          movl    %r8d, %edx
          callq   __TFSSCfT21_builtinStringLiteralBp8byteSizeBw7isASCIIBi1__SS
          movq    %rax, %rdi
          movq    %rdx, %rsi
          movq    %rcx, %rdx
          callq   __TFSSCfT26stringInterpolationSegmentSS_SS
          movq    -64(%rbp), %rsi
          movq    %rax, 48(%rsi)
          movq    %rdx, 56(%rsi)
          movq    %rcx, 64(%rsi)
          callq   __TFOs7Processau9argumentsGSaSS_
          movq    (%rax), %rax
          movq    %rax, %rdi
          movq    %rax, -96(%rbp)
          callq   _swift_bridgeObjectRetain
          leaq    -48(%rbp), %rdi
          movl    $2, %r8d
          movl    %r8d, %esi
          movq    -96(%rbp), %rdx
          movq    %rax, -104(%rbp)
          callq   __TTSg5SS___TFSag9subscriptFSix
          movq    -96(%rbp), %rdi
          callq   _swift_bridgeObjectRelease
          movq    -48(%rbp), %rdi
          movq    -40(%rbp), %rsi
          movq    -32(%rbp), %rdx
          callq   __TFSSCfT26stringInterpolationSegmentSS_SS
          leaq    L___unnamed_1(%rip), %rdi
          xorl    %r8d, %r8d
          movl    %r8d, %esi
          movl    $1, %r8d
          movq    -64(%rbp), %r9
          movq    %rax, 72(%r9)
          movq    %rdx, 80(%r9)
          movq    %rcx, 88(%r9)
          movl    %r8d, %edx
          callq   __TFSSCfT21_builtinStringLiteralBp8byteSizeBw7isASCIIBi1__SS
          movq    %rax, %rdi
          movq    %rdx, %rsi
          movq    %rcx, %rdx
          callq   __TFSSCfT26stringInterpolationSegmentSS_SS
          movq    -64(%rbp), %rsi
          movq    %rax, 96(%rsi)
          movq    %rdx, 104(%rsi)
          movq    %rcx, 112(%rsi)
          movq    -72(%rbp), %rdi
          callq   __TFSSCft19stringInterpolationGSaSS__SS
          movq    %rcx, %rdi
          movq    %rax, -112(%rbp)
          movq    %rdx, -120(%rbp)
          callq   _swift_unknownRelease
          xorl    %eax, %eax
          addq    $128, %rsp
          popq    %rbp
          retq
          .cfi_endproc
      
          .section    __TEXT,__cstring,cstring_literals
      L___unnamed_1:
          .space  1
      
      L___unnamed_2:
          .asciz  " "
      
          .linker_option "-lswiftCore"
          .linker_option "-lobjc"
          .section    __DATA,__objc_imageinfo,regular,no_dead_strip
      L_OBJC_IMAGE_INFO:
          .long   0
          .long   768
      
      
      .subsections_via_symbols
      

      addstr.swift+ 运算符)用 swiftc ./addstr.swift -o ./addstr 编译

      import Swift
      
      _ = Process.arguments[1] + " " + Process.arguments[2]
      

      swiftc-emit-assembly 标志的输出:

          .section    __TEXT,__text,regular,pure_instructions
          .macosx_version_min 10, 9
          .globl  _main
          .align  4, 0x90
      _main:
          .cfi_startproc
          pushq   %rbp
      Ltmp0:
          .cfi_def_cfa_offset 16
      Ltmp1:
          .cfi_offset %rbp, -16
          movq    %rsp, %rbp
      Ltmp2:
          .cfi_def_cfa_register %rbp
          subq    $176, %rsp
          movq    _globalinit_33_1BDF70FFC18749BAB495A73B459ED2F0_token5@GOTPCREL(%rip), %rax
          movq    __TZvOs7Process5_argcVs5Int32@GOTPCREL(%rip), %rcx
          movl    %edi, (%rcx)
          cmpq    $-1, (%rax)
          movq    %rsi, -56(%rbp)
          je  LBB0_2
          movq    _globalinit_33_1BDF70FFC18749BAB495A73B459ED2F0_token5@GOTPCREL(%rip), %rdi
          movq    _globalinit_33_1BDF70FFC18749BAB495A73B459ED2F0_func5@GOTPCREL(%rip), %rax
          movq    %rax, %rsi
          callq   _swift_once
      LBB0_2:
          movq    __TZvOs7Process11_unsafeArgvGSpGSpVs4Int8__@GOTPCREL(%rip), %rax
          movq    -56(%rbp), %rcx
          movq    %rcx, (%rax)
          callq   __TFOs7Processau9argumentsGSaSS_
          movq    (%rax), %rax
          movq    %rax, %rdi
          movq    %rax, -64(%rbp)
          callq   _swift_bridgeObjectRetain
          leaq    -24(%rbp), %rdi
          movl    $1, %edx
          movl    %edx, %esi
          movq    -64(%rbp), %rdx
          movq    %rax, -72(%rbp)
          callq   __TTSg5SS___TFSag9subscriptFSix
          movq    -64(%rbp), %rdi
          callq   _swift_bridgeObjectRelease
          leaq    L___unnamed_1(%rip), %rdi
          movl    $1, %r8d
          movl    %r8d, %esi
          movl    $1, %edx
          movq    -24(%rbp), %rax
          movq    -16(%rbp), %rcx
          movq    -8(%rbp), %r9
          movq    %r9, -80(%rbp)
          movq    %rcx, -88(%rbp)
          movq    %rax, -96(%rbp)
          callq   __TFSSCfT21_builtinStringLiteralBp8byteSizeBw7isASCIIBi1__SS
          movq    -96(%rbp), %rdi
          movq    -88(%rbp), %rsi
          movq    -80(%rbp), %r9
          movq    %rdx, -104(%rbp)
          movq    %r9, %rdx
          movq    %rcx, -112(%rbp)
          movq    %rax, %rcx
          movq    -104(%rbp), %r8
          movq    -112(%rbp), %r9
          callq   __TZFsoi1pFTSSSS_SS
          movq    %rax, -120(%rbp)
          movq    %rdx, -128(%rbp)
          movq    %rcx, -136(%rbp)
          callq   __TFOs7Processau9argumentsGSaSS_
          movq    (%rax), %rax
          movq    %rax, %rdi
          movq    %rax, -144(%rbp)
          callq   _swift_bridgeObjectRetain
          leaq    -48(%rbp), %rdi
          movl    $2, %r10d
          movl    %r10d, %esi
          movq    -144(%rbp), %rdx
          movq    %rax, -152(%rbp)
          callq   __TTSg5SS___TFSag9subscriptFSix
          movq    -144(%rbp), %rdi
          callq   _swift_bridgeObjectRelease
          movq    -48(%rbp), %rcx
          movq    -40(%rbp), %r8
          movq    -32(%rbp), %r9
          movq    -120(%rbp), %rdi
          movq    -128(%rbp), %rsi
          movq    -136(%rbp), %rdx
          callq   __TZFsoi1pFTSSSS_SS
          movq    %rcx, %rdi
          movq    %rax, -160(%rbp)
          movq    %rdx, -168(%rbp)
          callq   _swift_unknownRelease
          xorl    %eax, %eax
          addq    $176, %rsp
          popq    %rbp
          retq
          .cfi_endproc
      
          .section    __TEXT,__cstring,cstring_literals
      L___unnamed_1:
          .asciz  " "
      
          .linker_option "-lswiftCore"
          .linker_option "-lobjc"
          .section    __DATA,__objc_imageinfo,regular,no_dead_strip
      L_OBJC_IMAGE_INFO:
          .long   0
          .long   768
      
      
      .subsections_via_symbols
      

      如您所见,addstr.swift 的程序集包含的命令少于 interpolation.swift

      以下是使用 /usr/bin/time 进行计时的基准测试结果 (bash-3.2)。

      $ ARG1=$(printf '?%.0s' {1..30000}) # 30000 '?' characters
      $ ARG2=$(printf '?%.0s' {1..30000}) # 30000 '?' characters
      
      $ time ./interpolation $ARG1 $ARG2
      > 
      > real  0m0.026s
      > user  0m0.018s
      > sys   0m0.006s
      
      $ time ./addstr $ARG1 $ARG2
      > 
      > real  0m0.026s
      > user  0m0.018s
      > sys   0m0.006s
      

      我已多次运行此测试,但结果始终相同 (±0.001s)。

      【讨论】:

      • 这不是测量字符串加法。您可以更改其中一个代码以添加两次字符串,但您仍然会得到相同的结果。您实际测量启动程序所需的时间,因此结果是相同的,因为字符串添加时间不到一毫秒。
      • @HannesSverrisson 看看这个Gist(它使用 Swift 的 GYB 工具来生成代码)。它在输入字符串中有60_000_000个字符,默认运行1000次加法运算。
      • 但现在您通过分配 6000 万个字符的字符串来衡量分配时间。使用上面代码中的字符串并运行操作数百万次并给我们结果。
      猜你喜欢
      • 2012-02-04
      • 1970-01-01
      • 2021-07-09
      • 1970-01-01
      • 2015-12-18
      • 2011-07-01
      • 2014-03-19
      • 2011-01-28
      相关资源
      最近更新 更多