Swift struct работает в 10 раз быстрее как параметр, чем локальная переменная
Я внес некоторые изменения в это доказательство концептуального кода, который сравнивает производительность структур и классов, и обнаружил, что код в 10 раз медленнее для структур, когда я делаю простое изменение, передавая ContainerStruct в качестве параметра, а не создаю его как локальную переменную; передача ContainerClass в качестве параметра также выполняется немного медленнее, но не настолько. Для меня не имеет смысла, что локальная переменная вообще работает медленнее, поскольку она пропускает передачу переменной другой функции.
Я включил две реализации ниже в качестве класса UIViewController, который вы можете скопировать в новый проект Xcode, хотя я изменил проект, чтобы иметь генерацию кода Optimize for Speed [-O] в режиме отладки, чтобы структуры работали так же быстро, как и на release, поэтому, если вы запустите код, я предлагаю сделать то же самое. На моей машине записанное время в миллисекундах выглядит следующим образом:
Реализация 1 (локальная переменная): Struct: 12888, Class: 5381
Реализация 2 (параметр): Struct: 1273, Class: 4484
Одно наблюдение, которое я сделал, - если я удалю линию let copy = container
и передать контейнер в testStruct напрямую, производительность обеих реализаций практически идентична. Однако я не нашел этому объяснения.
Код для реализации №1 (локальная переменная)
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
let testIterationCount: Int64 = 100_000_000
calculateStructPerformance(iterations: testIterationCount)
// Test performance for class
calculateClassPerformance(iterations: testIterationCount)
}
func testClass(_ container: ContainerClass) -> Int
{
// Just call other function to create a new reference to the class
let value = simpleCalculationForClass(container)
return value
}
func testStruct(_ container: ContainerStruct) -> Int
{
// Just call other function to create a new copy of the struct
let value = simpleCalculationForStruct(container)
return value
}
func simpleCalculationForClass(_ testClass: ContainerClass) -> Int
{
// Make a simple operation
return (testClass.dummy3.count ^ 0x9e3779b9) >> testClass.dummy9.count
}
func simpleCalculationForStruct(_ testStruct: ContainerStruct) -> Int
{
// Make a simple operation
return (testStruct.dummy3.count ^ 0x9e3779b9) >> testStruct.dummy9.count
}
/// Function to test class performance
func calculateClassPerformance(iterations: Int64)
{
var dummies = ContiguousArray<DummyClass>()
for j in 0..<10
{
let dummy = DummyClass(flag: j % 2 == 0, count: j)
dummies.append(dummy)
}
// Create a container class instance
let container = ContainerClass(
dummy0: dummies[0],
dummy1: dummies[1],
dummy2: dummies[2],
dummy3: dummies[3],
dummy4: dummies[4],
dummy5: dummies[5],
dummy6: dummies[6],
dummy7: dummies[7],
dummy8: dummies[8],
dummy9: dummies[9]
)
let startTime = MachTimeUtils.now()
var result: Int = 0
for _ in 0..<iterations
{
// Create a copy of instance then pass it to the function
// to create new pointers to the original instance
let copy = container
result += testClass(copy)
}
let endTime = MachTimeUtils.now()
let period = MachTimeUtils.milliSecondsBetween(start: startTime, end: endTime)
print("Class: \(period)")
print("Result: \(result)")
}
func calculateStructPerformance(iterations: Int64)
{
var dummies = Array<DummyClass>()
for j in 0..<10
{
let dummy = DummyClass(flag: j % 2 == 0, count: j)
dummies.append(dummy)
}
// Create an instance of struct that contains multiple classes
let container = ContainerStruct(
dummy0: dummies[0],
dummy1: dummies[1],
dummy2: dummies[2],
dummy3: dummies[3],
dummy4: dummies[4],
dummy5: dummies[5],
dummy6: dummies[6],
dummy7: dummies[7],
dummy8: dummies[8],
dummy9: dummies[9]
)
let startTime = MachTimeUtils.now()
var result: Int = 0
for _ in 0..<iterations
{
// Create a copy of instance then pass it to the function
// to create new copies of the original instance
let copy = container
result += testStruct(copy)
}
let endTime = MachTimeUtils.now()
let period = MachTimeUtils.milliSecondsBetween(start: startTime, end: endTime)
print("Struct: \(period)")
print("Result: \(result)")
}
}
import Foundation
import Darwin
typealias MachTime = UInt64
enum MachTimeUtils {
/// - Returns: Current mach time of execution
static func now() -> MachTime {
return mach_absolute_time()
}
/// Finds time difference between two mach time
///
/// - Parameters:
/// - start: Starting mach time
/// - end: Ending mach time
/// - Returns: Time difference in milliseconds
static func milliSecondsBetween(start: MachTime, end: MachTime) -> MachTime {
return MachTime(convertToNanoSeconds(end - start) / 1e6)
}
/// Converts given mach time to nanoseconds
///
/// - Parameter time: Mach time to convert
/// - Returns: Time in naneseconds
static func convertToNanoSeconds(_ time: MachTime) -> Double {
var timeBase = mach_timebase_info(numer: 0, denom: 0)
mach_timebase_info(&timeBase)
return Double(time) * Double(timeBase.numer) / Double(timeBase.denom)
}
}
class DummyClass
{
var flag: Bool
var count: Int
init(flag: Bool, count: Int)
{
self.flag = flag
self.count = count
}
}
class ContainerClass
{
var dummy0: DummyClass
var dummy1: DummyClass
var dummy2: DummyClass
var dummy3: DummyClass
var dummy4: DummyClass
var dummy5: DummyClass
var dummy6: DummyClass
var dummy7: DummyClass
var dummy8: DummyClass
var dummy9: DummyClass
init(dummy0: DummyClass,
dummy1: DummyClass,
dummy2: DummyClass,
dummy3: DummyClass,
dummy4: DummyClass,
dummy5: DummyClass,
dummy6: DummyClass,
dummy7: DummyClass,
dummy8: DummyClass,
dummy9: DummyClass)
{
self.dummy0 = dummy0
self.dummy1 = dummy1
self.dummy2 = dummy2
self.dummy3 = dummy3
self.dummy4 = dummy4
self.dummy5 = dummy5
self.dummy6 = dummy6
self.dummy7 = dummy7
self.dummy8 = dummy8
self.dummy9 = dummy9
}
}
struct ContainerStruct
{
var dummy0: DummyClass
var dummy1: DummyClass
var dummy2: DummyClass
var dummy3: DummyClass
var dummy4: DummyClass
var dummy5: DummyClass
var dummy6: DummyClass
var dummy7: DummyClass
var dummy8: DummyClass
var dummy9: DummyClass
}
Реализация №2 (Параметр) - [Замените функции выше этими]
override func viewDidLoad() {
let testIterationCount: Int64 = 100_000_000
var dummiesForStruct = ContiguousArray<DummyClass>()
for j in 0..<10
{
let dummy = DummyClass(flag: j % 2 == 0, count: j)
dummiesForStruct.append(dummy)
}
let containerStruct = ContainerStruct(
dummy0: dummiesForStruct[0],
dummy1: dummiesForStruct[1],
dummy2: dummiesForStruct[2],
dummy3: dummiesForStruct[3],
dummy4: dummiesForStruct[4],
dummy5: dummiesForStruct[5],
dummy6: dummiesForStruct[6],
dummy7: dummiesForStruct[7],
dummy8: dummiesForStruct[8],
dummy9: dummiesForStruct[9]
)
calculateStructPerformance(with: containerStruct, iterations: testIterationCount)
// Test performance for class
var dummiesForClass = ContiguousArray<DummyClass>()
for j in 0..<10
{
let dummy = DummyClass(flag: j % 2 == 0, count: j)
dummiesForClass.append(dummy)
}
// Create a container class instance
let containerClass = ContainerClass(
dummy0: dummiesForClass[0],
dummy1: dummiesForClass[1],
dummy2: dummiesForClass[2],
dummy3: dummiesForClass[3],
dummy4: dummiesForClass[4],
dummy5: dummiesForClass[5],
dummy6: dummiesForClass[6],
dummy7: dummiesForClass[7],
dummy8: dummiesForClass[8],
dummy9: dummiesForClass[9]
)
calculateClassPerformance(with: containerClass, iterations: testIterationCount)
}
func testClass(_ container: ContainerClass) -> Int
{
// Just call other function to create a new reference to the class
let value = simpleCalculationForClass(container)
return value
}
func calculateClassPerformance(with container: ContainerClass, iterations: Int64)
{
let startTime = MachTimeUtils.now()
var result: Int = 0
for _ in 0..<iterations
{
// Create a copy of instance then pass it to the function
// to create new pointers to the original instance
let copy = container
result += testClass(copy)
}
let endTime = MachTimeUtils.now()
let period = MachTimeUtils.milliSecondsBetween(start: startTime, end: endTime)
print("Class: \(period)")
print("Result: \(result)")
}
func calculateStructPerformance(with container: ContainerStruct, iterations: Int64)
{
let startTime = MachTimeUtils.now()
var result: Int = 0
for _ in 0..<iterations
{
// Create a copy of instance then pass it to the function
// to create new copies of the original instance
let copy = container
result += testStruct(copy)
}
let endTime = MachTimeUtils.now()
let period = MachTimeUtils.milliSecondsBetween(start: startTime, end: endTime)
print("Struct: \(period)")
print("Result: \(result)")
}