为什么连续遍历一个Structs数组比一个类数组更快?

时间:2015-06-24 17:05:58

标签: arrays performance swift optimization struct

我正在开发一个使用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(),
]


func test1() {
    for i in 0...10000000 {
        for s in classes {}
    }
}

func test2() {
    for i in 0...10000000 {
        for s in structs {}
    }
}

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 {}
}

然后我得到以下结果: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
}


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
    }
}

在此测试中,类使用了14.5261079668999 s,而使用结构的测试仅花费了0.310304999351501 s。结构快了47倍。

3 个答案:

答案 0 :(得分:2)

正如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")


func structTest() {
    for i in 0 ... 1000000 {
        for e in structs {}
    }
}


func classTest() {
    for i in 0 ... 1000000 {
        for e in classes {}
    }
}

以下是使用仪器进行两次测试分析的图片。您可以通过将每次迭代期间Classes测试几乎所有时间花在保留/释放上的运行时间加起来来看。我很想知道Swift 2.0如何处理这个问题。

的Structs enter image description here

enter image description here

所以出于好奇,我想如果我可以通过直接在阵列上执行指针算法来绕过保留/释放调用会发生什么(旁注:我建议你从不这样做在一个真正的应用程序)。所以我创建了最后一个测试。但是在这个测试中,我不是多次迭代数组,而是创建一个大型数组并迭代一次,因为这是大多数开销发生的地方。我还决定在此测试中访问属性以减少优化中的模糊性。

以下是最终测试的结果:

  • 对大型Struct数组进行一次迭代:1.00037097930908 s
  • 对大型类阵列进行一次迭代:11.3165299892426 s
  • 使用指针对大型Struct数组进行一次迭代 算术:0.773443996906281 s
  • 使用指针对大型Class数组进行一次迭代 算术:2.81995397806168 s

以下是测试的代码。

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++
    }
}

答案 1 :(得分:0)

这是高度依赖编译器的。

你的结构是空的,这使得这是一个没有意义的比较。您应该创建具有唯一属性的实际结构和具有唯一属性的实际类对象,以进行有效测试。 (比如说每个人都有一个随机的Int?)

我的猜测是,由于结构是一个值类型,Swift实际上创建了一个连续的内存块来保存值并执行指针数学来获取单个结构,但是对于类对象,必须进行一些间接和消息传递。差异仍然很大。

实际上,由于您的结构是相同且不可变的,因此Swift编译器可能会将所有结构折叠为单个对象并忽略您的数组索引。

答案 2 :(得分:0)

我在某处读到结构和枚举的创建和复制在最新时刻发生,以便优化性能。如果这是真的,那么最迟将在访问时创建一个结构。

您是否尝试访问

中的结构
for  i in 0 ... 10000000 {
        for s in surfaceFrames {

e.g。通过将内容打印到控制台?知道这是否会对测量的性能产生影响会很有趣。