如何在Swift 4中快速创建一个长字符串?

时间:2017-12-21 12:18:25

标签: swift string

another question的游乐场准备一些长Swift String时,我注意到String创建时间已经变得非常明显,对于1000行,比枚举这些行至少慢100倍,所以我开始调查。最终我找到了两种方法来足够快地创建一个长String(实际上比枚举它的行更快):

let line = "I am a line terminated by CRLF\r\n"

let longString = String(repeating: line, count: 1000)
// finishes in about 3 ms on my MacBook Pro

var longString2 = line
for _ in 1...10 {
    longString2 = longString2 + longString2
}
// this will give me 1024 lines in about 7 ms

然而,只有所有线条完全相同时,这两种解决方案才有效。创建一长串不同行的“天真”方式,如

var longString3 = ""

for i in 1...1000 {
    longString3.append("I am a line \(i) terminated by CRLF\r\n")
}
// suddenly takes 1.7 *seconds*

远离计时方式。

鉴于Apples文档说明:

  

当字符串的连续存储填满时,必须有一个新的缓冲区   已分配和数据必须移动到新存储。   字符串缓冲区使用指数增长策略   在平均值时将字符串附加到常量时间操作   许多追加行动。

只会假设施工时间略有下降。但它变得更糟,添加了接下来的1000行(这次使用+=只是为了尝试别的东西),如

for i in 1001...2000 {
    longString3+="I am a line \(i) terminated by CRLF\r\n"
}
// takes 5.6 seconds!

接下来的1000行需要8.5秒,这表示运行时间呈二次方式增加。这表示缓冲区被重新分配并分配给新的存储区线性次数

好吧,我们可以帮助你说的编译器,但即使这样似乎也行不通。投掷

longString3.reserveCapacity(50000)
初始化后

在运行时更改 nothing 。现在我问自己上面的初始化者如何设法以如此惊人的速度创建他们的Strings

有一种方法可以改善这一点。您可以在Array然后join中初始化您的行,然后转到

var text = ""
var array = [String]()
array.reserveCapacity(10000)
for i in 1...1000 {
    array.append("I am line \(i) terminated by CRLF\r\n")
}
text = array.joined(separator: "")
text.count
// with a very disappointing running time of 20.7 seconds for 32893 characters

这对我来说相当不错,甚至比其他策略慢,但后来我尝试了

var text = ""
var array = Array(repeatElement(line, count: 1000))
for i in 1...1000 {
    array[i-1] = "I am line \(i) terminated by CRLF\r\n"
}
text = array.joined(separator: "")
text.count
// with a much more reasonable 124 ms running time

仍然不是非常快,但可以接受,至少对于游乐场而言。

但是现在我在问自己:游乐场会发生这种情况吗?对于看似相似的代码,这种不可预测的行为来自何处?最重要的是:在Swift中快速创建一个长String的最佳策略是什么?(以可读的方式)?

更新

鉴于评论,我决定在控制台应用程序中运行更多实验,如下所示:

import Foundation
import QuartzCore

func executionTimeInterval(block: () -> ()) -> CFTimeInterval {
    let start = CACurrentMediaTime()
    block();
    let end = CACurrentMediaTime()
    return end - start
}

let line = "I am a line terminated by CRLF\r\n"
var text = ""

let createTime = executionTimeInterval {
    text = String(repeating: line, count: 10000)
}
print("creating 10000 lines through initializer: \(createTime)")

var longString = ""
let unreservedThousandTime = executionTimeInterval {
    for i in 1...1000 {
        longString.append("I am line \(i) terminated by CRLF\r\n")
    }
}
print("append one thousand lines to empty (unreserved): \(unreservedThousandTime)")

let reservedThousandTime = executionTimeInterval {
    longString = ""
    longString.reserveCapacity(50000)
    for i in 1...1000 {
        longString.append("I am line \(i) terminated by CRLF\r\n")
    }
}
print("append one thousand lines to empty (reserved): \(reservedThousandTime)")

let secondThousendTime = executionTimeInterval {
    for i in 1001...2000 {
        longString+="I am line \(i) terminated by CRLF\r\n"
    }
}
print("second thousand lines appended: \(secondThousendTime)")

let thirdThousandTime = executionTimeInterval {
    for i in 2001...3000 {
        longString=longString+"I am line \(i) terminated by CRLF\r\n"
    }
}
print("third thousand lines appended: \(thirdThousandTime)")

let fourthThousandTime = executionTimeInterval {
    for i in 3001...4000 {
        longString+="I am line \(i) terminated by CRLF\r\n"
    }
}
print("fourth thousand lines appended: \(fourthThousandTime)")

var strArray = [String]()
let oneThousandJoined = executionTimeInterval {
    for i in 1...1000 {
        strArray.append("I am line \(i) terminated by CRLF\r\n")
    }
    let longString2 = strArray.joined()
}
print("one thousand lines joined: \(oneThousandJoined)")

这主要是预期的结果:

creating 10000 lines through initializer: 0.000855136197060347
append one thousand lines to empty (unreserved): 0.00150042399764061
append one thousand lines to empty (reserved): 0.0011014249175787
second thousand lines appended: 0.00116470409557223
third thousand lines appended: 0.00800192193128169
fourth thousand lines appended: 0.00095564010553062
one thousand lines joined: 0.0015953469555825

由于时间以低单位数毫秒结束,它们并不完全一致,但知道创建String的“天真”方式在“常规”Swift中表现良好肯定令人放心。有一个值得注意的例外,第三种创建长String+的方式始终比追加变种慢5倍。

因此,我在长时间运行时遇到的问题只与在Playground中这样做有关。我认为这个问题已经结束了,但我会打开另一个专门针对Playgrounds的问题,也许有人可以对此进行一些说明。

0 个答案:

没有答案