arrays - 为什么连续迭代结构数组比类数组快得多?

标签 arrays performance swift optimization struct

我正在使用 Swift 开发一款游戏,我有一个静态位置数据数组,用于在游戏循环中进行处理。我最初是使用一个结构数组来保存这些数据,但我决定切换到类,这样我就可以使用引用了。然而,在进行更改和分析之后,我注意到 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 秒,而 Test2 仅耗时 0.276068031787872 秒。连续迭代结构数组的速度提高了 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 秒,而 Test2 需要 0.00960797071456909 秒。这些结构的速度提高了 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 秒,而结构测试只用了 0.310304999351501 秒。结构速度提高了 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")


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


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

以下是使用仪器对这两个测试进行分析的图片。只需将运行时间相加,您就可以看到类测试在每次迭代期间几乎将所有时间都花在了保留/释放上。我很想知道 Swift 2.0 如何处理这个问题。

结构 enter image description here

enter image description here

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

下面是最终测试的结果:

  • 对大型结构数组进行一次迭代:1.00037097930908 秒
  • 大型类数组的一次迭代:11.3165299892426 秒
  • 使用指针对大型结构数组进行一次迭代 算术:0.773443996906281 s
  • 使用指针对大型类数组进行一次迭代 算术: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++
    }
}

关于arrays - 为什么连续迭代结构数组比类数组快得多?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31032772/

相关文章:

vb.net - 在 vb.net 中将字符串转换为数组

ios - 使用两个标签自定义分段控件

mysql - 如何提高大型 InnoDB 表的 DELETE FROM 性能?

Android:为什么 native 代码比 Java 代码快这么多

c# - 当某些内容缓存在 c# asp.net 中时,它是缓存在服务器上还是客户端上?

arrays - 如何快速遍历数组

ios - 仅在 UITableView 中将分隔符添加到第一个单元格

java - 将两个 ArrayList 相互连接

arrays - 多次复印多张纸

c - 指向二维数组的指针数组,读取不正确的值