str = str + "abc" 比 str = "abc" + str 慢?

str = str + "abc" slower than str = "abc" + str?

你信吗? 我有一个这样的循环(请原谅任何错误,我不得不大量编辑大量信息和变量名称,相信我它有效)。

...旧示例已删除,请参阅下面的代码...

如果我将那些中间的 str = "Blah \(odat.count)" + str 类型行更改为 str = str + "Blah \(odat.count)",UI 就会停止,我会得到色轮。 NSTextField 确实到达第一个 self.display.string... 但随后冻结。

我是多线程新手,请随时指正我的方法。希望我想要的很清楚。

我不得不承认工作版本也有点卡顿,但从未真正死机。 典型值为 n = 70,var3 = 7。

编辑:

这是一个完整的示例。只需 link 文本视图、进度条和按钮。尝试在主要功能之间切换。

//
//  Controllers.swift
//
//

import Cocoa

class MainController: NSObject {

    @IBOutlet var display: NSTextView!
    @IBOutlet weak var prog: NSProgressIndicator!

    @IBAction func go1(sender: AnyObject) {
        theRoutine(70)
    }

    @IBAction func go2(sender: AnyObject) {
        theRoutine(50)
    }

    class SomeClass {
        var x: Int
        var y: Int
        var p: Double

        init?(size: Int, pro: Double) {
            x = size
            y = size
            p = pro
        }
    }

    func theRoutine(n: Int) {
        prog.hidden = false
        prog.doubleValue = 0
        prog.maxValue = 7 * 40
        let priority = DISPATCH_QUEUE_PRIORITY_HIGH
        dispatch_async(dispatch_get_global_queue(priority, 0)) {
            self.theFunc(n, var1: 0.06, var2: 0.06, var3: 7)
            self.theFunc(n, var1: 0.1*log(Double(n))/Double(n), var2: 0.3*log(Double(n))/Double(n), var3: 7)
            dispatch_async(dispatch_get_main_queue()) {
                self.prog.hidden = true
                self.appOut("done!")
            }
        }
    }

    //This doesn't
//  func theFunc(n: Int, var1: Double, var2: Double, var3: Int) {
//      var m: AnEnum
//      var gra: SomeClass
//      var p = var1
//      for _ in 0...(var3 - 1) {
//          var str  = "blah \(p)\n"
//          for _ in 1...20 {
//              gra = SomeClass(size: n, pro: p)!
//              m = self.doSomething(gra)
//              switch m {
//              case .First(let dat):
//                  str = str + "Blah:\n\(self.arrayF(dat, transform: {"blah\([=12=])blah\()=blah"}))" + "\n\n" + str
//              case .Second(let odat):
//                  str = str + "Blah\(odat.count) blah\(self.arrayF(odat, transform: {"bl\()"}))" + "\n\n" + str
//              }
//              dispatch_async(dispatch_get_main_queue()) {
//                  self.prog.incrementBy(1)
//              }
//          }
//          dispatch_async(dispatch_get_main_queue()) {
//              // update some UI
//              self.display.string = str + "\n" + (self.display.string ?? "")
//          }
//          p += var2
//      }
//  }

    //This works
    func theFunc(n: Int, var1: Double, var2: Double, var3: Int) {
        var m: AnEnum
        var gra: SomeClass
        var p = var1
        for _ in 0...(var3 - 1) {
            var str  = "blah \(p)\n"
            for _ in 1...20 {
                gra = SomeClass(size: n, pro: p)!
                m = self.doSomething(gra)
                switch m {
                case .First(let dat):
                    str = "Blah:\n\(self.arrayF(dat, transform: {"blah\([=12=])blah\()=blah"}))" + "\n\n" + str
                case .Second(let odat):
                    str = "Blah\(odat.count) blah\(self.arrayF(odat, transform: {"bl\()"}))" + "\n\n" + str
                }
                dispatch_async(dispatch_get_main_queue()) {
                    self.prog.incrementBy(1)
                }
            }
            dispatch_async(dispatch_get_main_queue()) {
                // update some UI
                self.display.string = str + "\n" + (self.display.string ?? "")
            }
            p += var2
        }
    }

    func doSomething(G: SomeClass) -> AnEnum {
        usleep(30000)
        if drand48() <= G.p {
            return AnEnum.First([0, 0])
        } else {
            return AnEnum.Second([1, 1, 1])
        }
    }

    enum AnEnum {
        case First([Int])
        case Second([Int])
    }

    func appOut(out: String?) {
        if out != nil {
            display.string = out! + "\n\n" + (display.string ?? "")
        }
    }

    func arrayF(array: [Int], transform: (index: Int, value: Int) -> String) -> String {
        let arr = Array(0...(array.count - 1))
        return "[\(arr.map{transform(index: [=12=], value: array[[=12=]])}.joinWithSeparator(", "))]"
    }
}

因为除了

之外,你并没有真正问其他问题

Can you believe it?

我会告诉你我当然可以,但说真的,在很多情况下,前置某些东西可能 slower/faster 而不是追加。以链表为例。 Prepend 是 O(1) 并且 Append 是 O(N) 如果您没有持有对列表最后一个元素的引用。

我将要点放在一起,只是对那个特定问题进行计时,并且在 5-6 次运行中它似乎没有显着差异,但是在我的机器上前置仍然慢 10%。

有趣的是,关于您的情况,您基本上有 4 种在 Swift 中连接字符串的方法:

  • 添加到累加器 str = newstr + str
  • 追加到累加器 str = str + newstr
  • 附加变异str.append(newstr)
  • 使用好旧的数组作为字符串缓冲区并一次加入它a = []; a.append(x); str = a.joined(separator: " ")

在我的机器上,它们似乎几乎同时偏离,典型的时间如下:

prepend
real    0m0.082s
user    0m0.060s
sys 0m0.018s

append
real    0m0.070s
user    0m0.049s
sys 0m0.018s

append mutate
real    0m0.075s
user    0m0.054s
sys 0m0.019s

join
real    0m0.086s
user    0m0.064s
sys 0m0.020s

追加最快。

你可以在我的要点中看到所有四种情况的代码 https://gist.github.com/ojosdegris/df72a94327d12a67fe65e5989f9dcc53

如果您查看 Github 上的 Swift 来源,您会看到:

@effects(readonly)
@_semantics("string.concat")
public static func + (lhs: String, rhs: String) -> String {
 if lhs.isEmpty {
  return rhs
 }
 var lhs = lhs
 lhs._core.append(rhs._core)
 return lhs
}

所以当累加器字符串增长时可能会发生什么,复制它会更昂贵。

Vittore 的回答是正确的。查看Swift的String(stdlib/public/core/String.swift)的源代码,我们可以看到:

Although strings in Swift have value semantics, strings use a copy-on-write strategy to store their data in a buffer. This buffer can then be shared by different copies of a string. A string's data is only copied lazily, upon mutation, when more than one string instance is using the same buffer. Therefore, the first in any sequence of mutating operations may cost O(n) time and space.

When a string's contiguous storage fills up, a new buffer must be allocated and data must be moved to the new storage. String buffers use an exponential growth strategy that makes appending to a string a constant time operation when averaged over many append operations.

根据维基百科的 Copy-on-write

If a resource is duplicated but not modified, it is not necessary to create a new resource; the resource can be shared between the copy and the original.

考虑到这一点,在执行 str = str + "abc" 时,编译器正在执行 str 的浅拷贝并将 "abc" 附加到其连续内存中。另一方面,str = "abc" + str 创建了一个独立的实例,它有自己唯一的数据副本,因为它不再使用连续内存。