I am developing a game with Swift, and I have a static array of positional data that I use to process in the game loop. I originally used an array of Structs to store this data, but I decided to switch to classes to use references. However, after making changes and profiling, I noticed that the processor spends much more time on a method that processes this data than when using Structs.
So, I decided to create a simple test to find out what was going on.
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 takes 15.4722579717636 s, while Test2 takes only 0.276068031787872 s. Iterating continuously through an array of structs was 56 times faster. So my question is: why is this? I am looking for a detailed answer. If I were to assume, I would say that the structures themselves are stored sequentially in memory, and classes are stored only as addresses. Therefore, each time they need to be played. But then again, is it not necessary to copy structures every time?
Side note: Both arrays are small, but I repeat them continuously. If I changed the code to iterate once, making the arrays very large, for example:
for i in 0...10000000 { structs.append(SomeStruct()) classes.append(SomeClass()) } func test1() { for s in classes {} } func test2() { for s in structs {} }
Then I get the following: Test1 takes 0.841085016727448 s, and Test2 takes 0.00960797071456909 s. Structures 88 times faster.
I am using the OS X build build and the optimization level is set to Fastest,Smallest [-Os]
Edit
As requested, I edited this question to include a test in which structures and classes are no longer empty. They use the same properties that I use in my game. Still not changed. Structures are still much faster, and I don't know why. Hope someone can give an answer.
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 } }
In this test, classes took 14.5261079668999 s, while the test with structs took only 0.310304999351501 s. Structures were 47 times faster.