Epi*_*yte 5 arrays optimization performance struct swift
我正在开发一个使用Swift的游戏,我有一个静态的位置数据数组,我用它来在游戏循环中进行处理.我最初使用一个Structs数组来保存这些数据,但我决定切换到类,所以我可以使用引用.但是在进行更改和分析之后,我注意到CPU在处理此数据的方法上花费的时间比在使用Structs时花费的时间多得多.
所以我决定创建一个简单的测试来看看发生了什么.
final class SomeClass {}
struct SomeStruct {}
let classes = [
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
]
let structs = [
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
]
Run Code Online (Sandbox Code Playgroud)
func test1() {
for i in 0...10000000 {
for s in classes {}
}
}
func test2() {
for i in 0...10000000 {
for s in structs {}
}
}
Run Code Online (Sandbox Code Playgroud)
Test1需要15.4722579717636 s而Test2仅需0.276068031787872 s.通过结构阵列连续迭代的速度提高了56倍.所以我的问题是,这是为什么?我正在寻找一个详细的答案.如果我不得不猜测,我会说结构本身按顺序存储在内存中,而类只存储为地址.所以他们每次都需要被解除引用.但话说回来,每次都不需要复制结构吗?
旁注:两个数组都很小,但我不断迭代它们.如果我将代码更改为迭代一次但是使数组非常大,如下所示:
for i in 0...10000000 {
structs.append(SomeStruct())
classes.append(SomeClass())
}
func test1() {
for s in classes {}
}
func test2() {
for s in structs {}
}
Run Code Online (Sandbox Code Playgroud)
然后我得到以下结果:Test1需要0.841085016727448 s而Test2需要0.00960797071456909 s.结构需要快88倍.
我正在使用OS X版本构建,并设置了优化级别 Fastest,Smallest [-Os]
根据要求,我编辑了这个问题,以包含一个测试,其中结构和类不再是空的.它们使用我在游戏中使用的相同属性.仍然没有什么区别.结构仍然快得多,我不知道为什么.希望有人能提供答案.
import Foundation
final class StructTest {
let surfaceFrames = [
SurfaceFrame(a: SurfacePoint(x: 0, y: 410), b: SurfacePoint(x: 0, y: 400), c: SurfacePoint(x: 875, y: 410), surfaceID: 0, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 880, y: 304), b: SurfacePoint(x: 880, y: 294), c: SurfacePoint(x: 962, y: 304), surfaceID: 1, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 787, y: 138), b: SurfacePoint(x: 791, y: 129), c: SurfacePoint(x: 1031, y: 248), surfaceID: 2, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 523, y: 138), b: SurfacePoint(x: 523, y: 128), c: SurfacePoint(x: 806, y: 144), surfaceID: 3, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 1020, y: 243), b: SurfacePoint(x: 1020, y: 233), c: SurfacePoint(x: 1607, y: 241), surfaceID: 4, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 1649, y: 304), b: SurfacePoint(x: 1649, y: 294), c: SurfacePoint(x: 1731, y: 305), surfaceID: 5, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 1599, y: 240), b: SurfacePoint(x: 1595, y: 231), c: SurfacePoint(x: 1852, y: 128), surfaceID: 6, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 1807, y: 141), b: SurfacePoint(x: 1807, y: 131), c: SurfacePoint(x: 2082, y: 138), surfaceID: 7, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 976, y: 413), b: SurfacePoint(x: 976, y: 403), c: SurfacePoint(x: 1643, y: 411), surfaceID: 8, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 1732, y: 410), b: SurfacePoint(x: 1732, y: 400), c: SurfacePoint(x: 2557, y: 410), surfaceID: 9, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 2130, y: 490), b: SurfacePoint(x: 2138, y: 498), c: SurfacePoint(x: 2109, y: 512), surfaceID: 10, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 1598, y: 828), b: SurfacePoint(x: 1597, y: 818), c: SurfacePoint(x: 1826, y: 823), surfaceID: 11, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 715, y: 826), b: SurfacePoint(x: 715, y: 816), c: SurfacePoint(x: 953, y: 826), surfaceID: 12, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 840, y: 943), b: SurfacePoint(x: 840, y: 933), c: SurfacePoint(x: 920, y: 943), surfaceID: 13, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 1005, y: 1011), b: SurfacePoint(x: 1005, y: 1001), c: SurfacePoint(x: 1558, y: 1011), surfaceID: 14, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 1639, y: 943), b: SurfacePoint(x: 1639, y: 933), c: SurfacePoint(x: 1722, y: 942), surfaceID: 15, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 1589, y: 825), b: SurfacePoint(x: 1589, y: 815), c: SurfacePoint(x: 1829, y: 825), surfaceID: 16, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 0, y: 0), b: SurfacePoint(x: 1, y: 1), c: SurfacePoint(x: 2, y: 2), surfaceID: 17, dynamic:true)
]
func run() {
let startTime = CFAbsoluteTimeGetCurrent()
for i in 0 ... 10000000 {
for s in surfaceFrames {
}
}
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
println("Time elapsed \(timeElapsed) s")
}
}
struct SurfacePoint {
var x,y: Int
}
struct SurfaceFrame {
let a,b,c :SurfacePoint
let surfaceID: Int
let dynamic: Bool
}
Run Code Online (Sandbox Code Playgroud)
import Foundation
final class ClassTest {
let surfaceFrames = [
SurfaceFrame(a: SurfacePoint(x: 0, y: 410), b: SurfacePoint(x: 0, y: 400), c: SurfacePoint(x: 875, y: 410), surfaceID: 0, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 880, y: 304), b: SurfacePoint(x: 880, y: 294), c: SurfacePoint(x: 962, y: 304), surfaceID: 1, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 787, y: 138), b: SurfacePoint(x: 791, y: 129), c: SurfacePoint(x: 1031, y: 248), surfaceID: 2, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 523, y: 138), b: SurfacePoint(x: 523, y: 128), c: SurfacePoint(x: 806, y: 144), surfaceID: 3, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 1020, y: 243), b: SurfacePoint(x: 1020, y: 233), c: SurfacePoint(x: 1607, y: 241), surfaceID: 4, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 1649, y: 304), b: SurfacePoint(x: 1649, y: 294), c: SurfacePoint(x: 1731, y: 305), surfaceID: 5, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 1599, y: 240), b: SurfacePoint(x: 1595, y: 231), c: SurfacePoint(x: 1852, y: 128), surfaceID: 6, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 1807, y: 141), b: SurfacePoint(x: 1807, y: 131), c: SurfacePoint(x: 2082, y: 138), surfaceID: 7, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 976, y: 413), b: SurfacePoint(x: 976, y: 403), c: SurfacePoint(x: 1643, y: 411), surfaceID: 8, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 1732, y: 410), b: SurfacePoint(x: 1732, y: 400), c: SurfacePoint(x: 2557, y: 410), surfaceID: 9, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 2130, y: 490), b: SurfacePoint(x: 2138, y: 498), c: SurfacePoint(x: 2109, y: 512), surfaceID: 10, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 1598, y: 828), b: SurfacePoint(x: 1597, y: 818), c: SurfacePoint(x: 1826, y: 823), surfaceID: 11, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 715, y: 826), b: SurfacePoint(x: 715, y: 816), c: SurfacePoint(x: 953, y: 826), surfaceID: 12, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 840, y: 943), b: SurfacePoint(x: 840, y: 933), c: SurfacePoint(x: 920, y: 943), surfaceID: 13, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 1005, y: 1011), b: SurfacePoint(x: 1005, y: 1001), c: SurfacePoint(x: 1558, y: 1011), surfaceID: 14, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 1639, y: 943), b: SurfacePoint(x: 1639, y: 933), c: SurfacePoint(x: 1722, y: 942), surfaceID: 15, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 1589, y: 825), b: SurfacePoint(x: 1589, y: 815), c: SurfacePoint(x: 1829, y: 825), surfaceID: 16, dynamic:false),
SurfaceFrame(a: SurfacePoint(x: 0, y: 0), b: SurfacePoint(x: 1, y: 1), c: SurfacePoint(x: 2, y: 2), surfaceID: 17, dynamic:true)
]
func run() {
let startTime = CFAbsoluteTimeGetCurrent()
for i in 0 ... 10000000 {
for s in surfaceFrames {
}
}
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
println("Time elapsed \(timeElapsed) s")
}
}
struct SurfacePoint {
var x,y: Int
}
final class SurfaceFrame {
let a,b,c :SurfacePoint
let surfaceID: Int
let dynamic: Bool
init(a: SurfacePoint, b: SurfacePoint, c: SurfacePoint, surfaceID: Int, dynamic: Bool) {
self.a = a
self.b = b
self.c = c
self.surfaceID = surfaceID
self.dynamic = dynamic
}
}
Run Code Online (Sandbox Code Playgroud)
在此测试中,类使用了14.5261079668999 s,而使用结构的测试仅花费了0.310304999351501 s.结构快了47倍.
正如 Martin R 所建议的,我对这两个测试进行了分析,实际上,保留/释放调用使得遍历类数组比遍历结构数组慢得多。需要明确的是,这是我运行的测试。
import Foundation
final class SomeClass {}
struct SomeStruct {}
var classes = [
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
SomeClass(),
]
var structs = [
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
SomeStruct(),
]
let startTime = CFAbsoluteTimeGetCurrent()
/*
structTest()
classTest()
*/
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
println("Time elapsed \(timeElapsed) s")
Run Code Online (Sandbox Code Playgroud)
func structTest() {
for i in 0 ... 1000000 {
for e in structs {}
}
}
Run Code Online (Sandbox Code Playgroud)
func classTest() {
for i in 0 ... 1000000 {
for e in classes {}
}
}
Run Code Online (Sandbox Code Playgroud)
以下是使用仪器对两项测试进行分析的图片。只需将运行时间相加就可以看到,类测试在每次迭代期间几乎将所有时间都花在了保留/释放上。我很想知道 Swift 2.0 如何处理这个问题。
结构体
课程
因此,出于好奇,我想如果我可以通过直接在数组上执行指针算术来绕过保留/释放调用,会发生什么(旁注:我建议您永远不要在真正的应用程序中这样做)。所以我创建了最后一个测试。然而,在这个测试中,我不会多次迭代数组,而是创建一个大数组并迭代一次,因为无论如何这是大部分开销发生的地方。我还决定访问此测试中的属性,以减少优化中的歧义。
这是最终测试的结果:
下面是测试的代码。
final class SomeClass {
var a: Int
init(a: Int) {
self.a = a
}
}
struct SomeStruct {
var a: Int
init(a: Int) {
self.a = a
}
}
var classes: [SomeClass] = []
var structs: [SomeStruct] = []
var total: Int = 0
for i in 0 ... 100000000 {
classes.append(SomeClass(a:i))
structs.append(SomeStruct(a:i))
}
let startTime = CFAbsoluteTimeGetCurrent()
/*structTest()
classTest()
structTestPointer()
classTestPointer()*/
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
println("Time elapsed \(timeElapsed) s")
func structTest() {
for someStruct in structs {
let a = someStruct.a
total = total &+ a
}
}
func structTestPointer() {
var pointer = UnsafePointer<SomeStruct>(structs)
for j in 0 ..< structs.count {
let someStruct = pointer.memory
let a = someStruct.a
total = total &+ a
pointer++
}
}
func classTest() {
for someClass in classes {
let a = someClass.a
total = total &+ a
}
}
func classTestPointer() {
var pointer = UnsafePointer<SomeClass>(classes)
for j in 0 ..< classes.count {
let someClass = pointer.memory
let a = someClass.a
total = total &+ a
pointer++
}
}
Run Code Online (Sandbox Code Playgroud)