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

0 ответов

Другие вопросы по тегам