为什么 Swift 在 for-in 循环中使用下标语法比直接访问元素更快?
Why Swift using subscript syntax in a for-in loops is faster than using direct access to the element?
我读了著名的 Why is it faster to process a sorted array than an unsorted array?,我决定尝试其他语言,例如 Swift。我对 2 个非常相似的代码片段之间的 运行 时间差异感到惊讶。
在 Swift 中,可以直接访问数组中的元素,也可以在 for-in 循环中使用下标访问数组中的元素。例如这段代码:
for i in 0..<size {
sum += data[i]
}
可以写成:
for element in data {
sum += element
}
size
data
长度和 data
可求和元素数组。
所以,我刚刚在 Swift(下面的代码)中实现了与我在第一段中提到的问题相同的算法,令我惊讶的是第一种方法比第二种方法快大约 5 倍方法。
我不太了解后台下标实现,但我认为直接访问 Swift for-in 循环中的元素只是语法糖。
问题
我的问题是这两种 for-in
语法有什么区别,为什么使用下标更快?
这里是定时器的详细信息。我在 2015 年初使用 Xcode 9.4.1 和 Swift 4.1 MacBook Air with a Commande Line Project。
// Using Direct Element Access
Elapsed Time: 8.506288427
Sum: 1051901000
对
// Using Subscript
Elapsed Time: 1.483967902
Sum: 1070388000
奖励问题:为什么 Swift 中的执行速度比 C++ 慢 100 倍(两者都在 n [=49] 中的相同 Mac 上执行=] 项目)?例如,C++ 中的 100,000 次重复所花费的时间几乎与 Swift 中的 1,000 次重复所花费的时间相同。我的第一个猜测是 Swift 是一种比 C++ 更高级的语言,并且 Swift 运行更多的安全检查。
这是我使用的Swift代码,我只修改了第二个嵌套循环:
import Foundation
import GameplayKit
let size = 32_768
var data = [Int]()
var sum = 0
var rand = GKRandomDistribution(lowestValue: 0, highestValue: 255)
for _ in 0..<size {
data.append(rand.nextInt())
}
// data.sort()
let start = DispatchTime.now()
for _ in 0..<1_000 {
// Only the following for-in loop changes
for i in 0..<size {
if data[i] <= 128 {
sum += data[i]
}
}
}
let stop = DispatchTime.now()
let nanoTime = stop.uptimeNanoseconds - start.uptimeNanoseconds
let elapsed = Double(nanoTime) / 1_000_000_000
print("Elapsed Time: \(elapsed)")
print("Sum: \(sum)")
整体性能输出在很大程度上取决于编译器所做的优化。如果您在启用优化的情况下编译代码,您会发现两种解决方案之间的差异很小。
为了证明这一点,我更新了您的代码,添加了两种方法,一种使用 subscripting
,另一种使用 for-in
。
import Foundation
import GameplayKit
let size = 32_768
var data = [Int]()
var sum = 0
var rand = GKRandomDistribution(lowestValue: 0, highestValue: 255)
for _ in 0..<size {
data.append(rand.nextInt())
}
// data.sort()
func withSubscript() {
let start = DispatchTime.now()
for _ in 0..<1_000 {
for i in 0..<size {
if data[i] <= 128 {
sum += data[i]
}
}
}
let stop = DispatchTime.now()
let elapsed = Double(stop.uptimeNanoseconds - start.uptimeNanoseconds) / 1_000_000_000
print("With subscript:")
print("- Elapsed Time: \(elapsed)")
print("- Sum: \(sum)")
}
func withForIn() {
let start = DispatchTime.now()
for _ in 0..<1_000 {
for element in data {
if element <= 128 {
sum += element
}
}
}
let stop = DispatchTime.now()
let elapsed = Double(stop.uptimeNanoseconds - start.uptimeNanoseconds) / 1_000_000_000
print("With for-in:")
print("- Elapsed Time: \(elapsed)")
print("- Sum: \(sum)")
}
withSubscript()
withForIn()
我将该代码保存到名为 array-subscripting.swift
的文件中。
然后,从命令行,我们可以 运行 它没有任何优化,像这样:
$ swift array-subscripting.swift
With subscript:
- Elapsed Time: 0.924554249
- Sum: 1057062000
With for-in:
- Elapsed Time: 5.796038213
- Sum: 2114124000
正如您在post中提到的,性能上有很大差异。
当使用优化编译代码时,这种差异可以忽略不计:
$ swiftc array-subscripting.swift -O
$ ./array-subscripting
With subscript:
- Elapsed Time: 0.110622556
- Sum: 1054578000
With for-in:
- Elapsed Time: 0.11670454
- Sum: 2109156000
如您所见,这两种解决方案都比以前快得多,而且执行时间非常相似。
回到您最初的问题,subscripting
提供对内存的直接访问,这在连续数组的情况下非常有效,其中元素在内存中彼此相邻存储。
另一方面,for-in
循环创建数组中每个元素的不可变副本,这会导致性能下降。
我读了著名的 Why is it faster to process a sorted array than an unsorted array?,我决定尝试其他语言,例如 Swift。我对 2 个非常相似的代码片段之间的 运行 时间差异感到惊讶。
在 Swift 中,可以直接访问数组中的元素,也可以在 for-in 循环中使用下标访问数组中的元素。例如这段代码:
for i in 0..<size {
sum += data[i]
}
可以写成:
for element in data {
sum += element
}
size
data
长度和 data
可求和元素数组。
所以,我刚刚在 Swift(下面的代码)中实现了与我在第一段中提到的问题相同的算法,令我惊讶的是第一种方法比第二种方法快大约 5 倍方法。
我不太了解后台下标实现,但我认为直接访问 Swift for-in 循环中的元素只是语法糖。
问题
我的问题是这两种 for-in
语法有什么区别,为什么使用下标更快?
这里是定时器的详细信息。我在 2015 年初使用 Xcode 9.4.1 和 Swift 4.1 MacBook Air with a Commande Line Project。
// Using Direct Element Access
Elapsed Time: 8.506288427
Sum: 1051901000
对
// Using Subscript
Elapsed Time: 1.483967902
Sum: 1070388000
奖励问题:为什么 Swift 中的执行速度比 C++ 慢 100 倍(两者都在 n [=49] 中的相同 Mac 上执行=] 项目)?例如,C++ 中的 100,000 次重复所花费的时间几乎与 Swift 中的 1,000 次重复所花费的时间相同。我的第一个猜测是 Swift 是一种比 C++ 更高级的语言,并且 Swift 运行更多的安全检查。
这是我使用的Swift代码,我只修改了第二个嵌套循环:
import Foundation
import GameplayKit
let size = 32_768
var data = [Int]()
var sum = 0
var rand = GKRandomDistribution(lowestValue: 0, highestValue: 255)
for _ in 0..<size {
data.append(rand.nextInt())
}
// data.sort()
let start = DispatchTime.now()
for _ in 0..<1_000 {
// Only the following for-in loop changes
for i in 0..<size {
if data[i] <= 128 {
sum += data[i]
}
}
}
let stop = DispatchTime.now()
let nanoTime = stop.uptimeNanoseconds - start.uptimeNanoseconds
let elapsed = Double(nanoTime) / 1_000_000_000
print("Elapsed Time: \(elapsed)")
print("Sum: \(sum)")
整体性能输出在很大程度上取决于编译器所做的优化。如果您在启用优化的情况下编译代码,您会发现两种解决方案之间的差异很小。
为了证明这一点,我更新了您的代码,添加了两种方法,一种使用 subscripting
,另一种使用 for-in
。
import Foundation
import GameplayKit
let size = 32_768
var data = [Int]()
var sum = 0
var rand = GKRandomDistribution(lowestValue: 0, highestValue: 255)
for _ in 0..<size {
data.append(rand.nextInt())
}
// data.sort()
func withSubscript() {
let start = DispatchTime.now()
for _ in 0..<1_000 {
for i in 0..<size {
if data[i] <= 128 {
sum += data[i]
}
}
}
let stop = DispatchTime.now()
let elapsed = Double(stop.uptimeNanoseconds - start.uptimeNanoseconds) / 1_000_000_000
print("With subscript:")
print("- Elapsed Time: \(elapsed)")
print("- Sum: \(sum)")
}
func withForIn() {
let start = DispatchTime.now()
for _ in 0..<1_000 {
for element in data {
if element <= 128 {
sum += element
}
}
}
let stop = DispatchTime.now()
let elapsed = Double(stop.uptimeNanoseconds - start.uptimeNanoseconds) / 1_000_000_000
print("With for-in:")
print("- Elapsed Time: \(elapsed)")
print("- Sum: \(sum)")
}
withSubscript()
withForIn()
我将该代码保存到名为 array-subscripting.swift
的文件中。
然后,从命令行,我们可以 运行 它没有任何优化,像这样:
$ swift array-subscripting.swift
With subscript:
- Elapsed Time: 0.924554249
- Sum: 1057062000
With for-in:
- Elapsed Time: 5.796038213
- Sum: 2114124000
正如您在post中提到的,性能上有很大差异。
当使用优化编译代码时,这种差异可以忽略不计:
$ swiftc array-subscripting.swift -O
$ ./array-subscripting
With subscript:
- Elapsed Time: 0.110622556
- Sum: 1054578000
With for-in:
- Elapsed Time: 0.11670454
- Sum: 2109156000
如您所见,这两种解决方案都比以前快得多,而且执行时间非常相似。
回到您最初的问题,subscripting
提供对内存的直接访问,这在连续数组的情况下非常有效,其中元素在内存中彼此相邻存储。
for-in
循环创建数组中每个元素的不可变副本,这会导致性能下降。