Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Huge CPU gap between ReSwift Store vs just SwiftUI ObservableObject #496

Open
pianostringquartet opened this issue Nov 23, 2022 · 1 comment

Comments

@pianostringquartet
Copy link

First of all, huge thanks for creating and maintaining ReSwift; it’s a pleasure to use!

I noticed a big CPU gap between ReSwift Store vs just a SwiftUI ObservableObject:

ReSwift Store: 23-28% CPU
SwiftUI ObservableObject: 4-5% CPU

Tested on iPad Pro, 11 inch, running iPadOS 16.1.1 (20B101).

My app uses DisplayLink’s .onFrame to perform certain actions on each frame: https://github.com/timdonnelly/DisplayLink

Interestingly, I had benchmarked this in ~Feb 2021 on ReSwift version 6.0.0 and had a much better CPU, e.g. ~6-7%.

Currently, I get better perf on the older versions with StateType conformance:

  • 6.0.0: 16-19%
  • 5.0.0: 17-18%

Is this expected, or am I using ReSwift incorrectly? What could account for such a big perf difference?

Relevant code below. Also, repo with sample project: https://github.com/pianostringquartet/ReSwiftPerfTest/blob/main/PerfTestApp/ReSwiftPerfPlay.swift

//
//  ReSwiftPerfTest.swift
//
//  Created by Christian J Clampitt on 11/23/22.
//

import SwiftUI
import ReSwift
import DisplayLink

//@main
//struct MyApp: App {
//    var body: some Scene {
//        WindowGroup {
//            ReSwiftPerfPlay()
//        }
//    }
//}

class ObservableData: ObservableObject {
    var counter = 0
}

struct ReSwiftState: StateType, Equatable {
    var counter = 0
}

struct ReSwiftPerfPlay: View {

    @StateObject var observableData = ObservableData()
    @StateObject var storeData = ReSwiftStore<ReSwiftState>(store: reswiftStore())

    var body: some View {
        Text("UI does not use state")
            // DisplayLink: on-frame callback:
            // https://github.com/timdonnelly/DisplayLink
            .onFrame { _ in

                // When updating `observableData` alone,
                // CPU is 4-5%
//                observableData.counter += 1

                // When updating `storeData` alone:
                // ReSwift 6.1.0: CPU is 23-28%
                // ReSwift 6.0.0: CPU is 16-19%
                // ReSwift 5.0.0: CPU is 17-18%
                storeData.dispatch(CounterIncremented())
            }
    }
}

// -- MARK: reducer, store, action

func reswiftReducer(action: Action,
                    state: ReSwiftState?) -> ReSwiftState {
    var totalState = state ?? ReSwiftState()

    if action is CounterIncremented {
        totalState.counter += 1
        return totalState
    } else {
        return totalState
    }
}

struct CounterIncremented: Action, Equatable { }

func reswiftStore() -> Store<ReSwiftState> {
    Store<ReSwiftState>(reducer: reswiftReducer,
                        state: ReSwiftState())
}

// -- MARK: adapting ReSwift to SwiftUI

typealias Dispatch = (Action) -> Void

class ReSwiftStore<T: StateType>: ObservableObject {
    private var store: Store<T>

    @Published var state: T

    let dispatch: Dispatch

    init(store: Store<T>) {
        self.store = store
        self.state = store.state

        let dispatch: Dispatch = store.dispatch
        self.dispatch = dispatch

        store.subscribe(self)
    }

    deinit {
        store.unsubscribe(self)
    }
}

extension ReSwiftStore: StoreSubscriber {
    public func newState(state: T) {
        DispatchQueue.main.async {
            self.state = state
        }
    }
}
@mjarvis
Copy link
Member

mjarvis commented Nov 23, 2022

ReSwift is definitely not optimized for real-time usage like within a DisplayLink.

We can definitely run some instrumenting on this to see if we can find any improvements, but it is likely to not be suitable for this use case without drastic changes. I'll try to find some time but it would be appreciated if you are able to contribute in this manner as well.

Some things such as the automatic skipping of repeats may be causing slowdowns here, you could try disabling that.

Regarding your usage of ReSwift within SwiftUI, I would be inclined to look at a more specific subscriber solution, rather than a catch-all state subscriber.

See comments near the end of #455 for other solutions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants