Skip to content

Commit 1449971

Browse files
authored
perf: optimize animations (~5-20% win) (#778)
## 📜 Description Slightly optimized performance of `SpringAnimation` and `TimingAnimation`. ## 💡 Motivation and Context I didn't change any implementation details. Just re-factored the code and found places that can be optimized, for example: - pre-compute re-usable variables; - pre-compute conditions; - avoid unnecessary types conversion As a result it gave ~5% boost for `TimingAnimation` and about 10-20% (depend on metric) for `SpringAnimation`. Pretty cool to have performance tests in the project to see which change gives you which boost. > On CI turns out after runner re-deploy (in the past) it has a different CPU than my laptop, so it seems like it always create its own baseline (so there is no ability to run perf benchmarks there). ## 📢 Changelog <!-- High level overview of important changes --> <!-- For example: fixed status bar manipulation; added new types declarations; --> <!-- If your changes don't affect one of platform/language below - then remove this platform/language --> ### iOS - convert `speed` to `Double` in `KeyboardAnimation` and avoid additional conversions in frequently called methods; - pre-compute `isUnderDamped` condition (very small boost, but still); - pre-compute `aUnder`, `bUnder` and `aCritical`, `bCritical`. ## 🤔 How Has This Been Tested? Tested via performance tests. ## 📸 Screenshots (if appropriate): Took results on target branch, then switched to `main` and run tests again: <img width="604" alt="image" src="https://github.com/user-attachments/assets/31ebac9f-a9d8-4c51-8c80-c8d51659b284" /> <img width="610" alt="image" src="https://github.com/user-attachments/assets/ec402a67-b024-4578-a408-09329c0533e3" /> And on this branch: <img width="597" alt="image" src="https://github.com/user-attachments/assets/5868db1b-f240-4c16-b618-f8441a619ec6" /> ## 📝 Checklist - [x] CI successfully passed - [x] I added new mocks and corresponding unit-tests if library API was changed
1 parent f4eb088 commit 1449971

File tree

5 files changed

+49
-26
lines changed

5 files changed

+49
-26
lines changed
Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,28 @@
1111
<key>com.apple.dt.XCTMetric_CPU.cycles</key>
1212
<dict>
1313
<key>baselineAverage</key>
14-
<real>692279.864500</real>
14+
<real>509000.000000</real>
1515
<key>baselineIntegrationDisplayName</key>
1616
<string>Local Baseline</string>
1717
</dict>
1818
<key>com.apple.dt.XCTMetric_CPU.instructions_retired</key>
1919
<dict>
2020
<key>baselineAverage</key>
21-
<real>2497477.878400</real>
21+
<real>1720000.000000</real>
2222
<key>baselineIntegrationDisplayName</key>
2323
<string>Local Baseline</string>
2424
</dict>
2525
<key>com.apple.dt.XCTMetric_CPU.time</key>
2626
<dict>
2727
<key>baselineAverage</key>
28-
<real>0.223160</real>
28+
<real>0.187000</real>
2929
<key>baselineIntegrationDisplayName</key>
3030
<string>Local Baseline</string>
3131
</dict>
3232
<key>com.apple.dt.XCTMetric_Clock.time.monotonic</key>
3333
<dict>
3434
<key>baselineAverage</key>
35-
<real>0.251555</real>
35+
<real>0.187000</real>
3636
<key>baselineIntegrationDisplayName</key>
3737
<string>Local Baseline</string>
3838
</dict>
@@ -42,28 +42,28 @@
4242
<key>com.apple.dt.XCTMetric_CPU.cycles</key>
4343
<dict>
4444
<key>baselineAverage</key>
45-
<real>11784.023400</real>
45+
<real>9480.000000</real>
4646
<key>baselineIntegrationDisplayName</key>
4747
<string>Local Baseline</string>
4848
</dict>
4949
<key>com.apple.dt.XCTMetric_CPU.instructions_retired</key>
5050
<dict>
5151
<key>baselineAverage</key>
52-
<real>52409.916600</real>
52+
<real>46000.000000</real>
5353
<key>baselineIntegrationDisplayName</key>
5454
<string>Local Baseline</string>
5555
</dict>
5656
<key>com.apple.dt.XCTMetric_CPU.time</key>
5757
<dict>
5858
<key>baselineAverage</key>
59-
<real>0.003792</real>
59+
<real>0.003490</real>
6060
<key>baselineIntegrationDisplayName</key>
6161
<string>Local Baseline</string>
6262
</dict>
6363
<key>com.apple.dt.XCTMetric_Clock.time.monotonic</key>
6464
<dict>
6565
<key>baselineAverage</key>
66-
<real>0.003172</real>
66+
<real>0.003050</real>
6767
<key>baselineIntegrationDisplayName</key>
6868
<string>Local Baseline</string>
6969
</dict>
@@ -76,14 +76,14 @@
7676
<key>com.apple.dt.XCTMetric_CPU.cycles</key>
7777
<dict>
7878
<key>baselineAverage</key>
79-
<real>8198357.543500</real>
79+
<real>7640000.000000</real>
8080
<key>baselineIntegrationDisplayName</key>
8181
<string>Local Baseline</string>
8282
</dict>
8383
<key>com.apple.dt.XCTMetric_CPU.instructions_retired</key>
8484
<dict>
8585
<key>baselineAverage</key>
86-
<real>39224511.178500</real>
86+
<real>37300000.000000</real>
8787
<key>baselineIntegrationDisplayName</key>
8888
<string>Local Baseline</string>
8989
</dict>
@@ -107,28 +107,28 @@
107107
<key>com.apple.dt.XCTMetric_CPU.cycles</key>
108108
<dict>
109109
<key>baselineAverage</key>
110-
<real>316000.000000</real>
110+
<real>299000.000000</real>
111111
<key>baselineIntegrationDisplayName</key>
112112
<string>Local Baseline</string>
113113
</dict>
114114
<key>com.apple.dt.XCTMetric_CPU.instructions_retired</key>
115115
<dict>
116116
<key>baselineAverage</key>
117-
<real>1460000.000000</real>
117+
<real>1390000.000000</real>
118118
<key>baselineIntegrationDisplayName</key>
119119
<string>Local Baseline</string>
120120
</dict>
121121
<key>com.apple.dt.XCTMetric_CPU.time</key>
122122
<dict>
123123
<key>baselineAverage</key>
124-
<real>0.097500</real>
124+
<real>0.110000</real>
125125
<key>baselineIntegrationDisplayName</key>
126126
<string>Local Baseline</string>
127127
</dict>
128128
<key>com.apple.dt.XCTMetric_Clock.time.monotonic</key>
129129
<dict>
130130
<key>baselineAverage</key>
131-
<real>0.108737</real>
131+
<real>0.109000</real>
132132
<key>baselineIntegrationDisplayName</key>
133133
<string>Local Baseline</string>
134134
</dict>

ios/KeyboardControllerNative/KeyboardControllerNative.xcodeproj/xcshareddata/xcbaselines/0873ED612BB6B7390004F3A4.xcbaseline/Info.plist

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<dict>
55
<key>runDestinationsByUUID</key>
66
<dict>
7-
<key>1A32030D-32A5-4C54-9D19-E5D6E86658C9</key>
7+
<key>AE1417BE-2A84-4C63-BD95-0B7DE93E2975</key>
88
<dict>
99
<key>localComputer</key>
1010
<dict>
@@ -30,7 +30,7 @@
3030
<key>targetDevice</key>
3131
<dict>
3232
<key>modelCode</key>
33-
<string>iPhone16,2</string>
33+
<string>iPhone16,1</string>
3434
<key>platformIdentifier</key>
3535
<string>com.apple.platform.iphonesimulator</string>
3636
</dict>

ios/animations/KeyboardAnimation.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@ public class KeyboardAnimation: KeyboardAnimationProtocol {
2323
// constructor variables
2424
let fromValue: Double
2525
let toValue: Double
26-
let speed: Float
26+
let speed: Double
2727
let timestamp: CFTimeInterval
2828

2929
init(fromValue: Double, toValue: Double, animation: CAMediaTiming) {
3030
self.fromValue = fromValue
3131
self.toValue = toValue
3232
self.animation = animation
33-
speed = animation.speed
33+
speed = Double(animation.speed)
3434
timestamp = CACurrentMediaTime()
3535
}
3636

@@ -63,7 +63,7 @@ public class KeyboardAnimation: KeyboardAnimationProtocol {
6363

6464
while (upperBound - lowerBound) > tolerance {
6565
tGuess = (lowerBound + upperBound) / 2
66-
let currentValue = valueAt(time: tGuess / Double(speed))
66+
let currentValue = valueAt(time: tGuess / speed)
6767

6868
// Adjust the condition to account for the direction of animation
6969
if (currentValue < value && isIncreasing) || (currentValue > value && !isIncreasing) {
@@ -73,6 +73,6 @@ public class KeyboardAnimation: KeyboardAnimationProtocol {
7373
}
7474
}
7575

76-
return tGuess / Double(speed)
76+
return tGuess / speed
7777
}
7878
}

ios/animations/SpringAnimation.swift

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ public final class SpringAnimation: KeyboardAnimation {
1616
private let omega0: Double // Undamped angular frequency of the oscillator
1717
private let omega1: Double // Exponential decay
1818
private let v0: Double // Initial velocity
19+
// pre-computed values
20+
private let x0: Double
21+
private let isUnderDamped: Bool
22+
private let aUnder: Double
23+
private let bUnder: Double
24+
private let aCritical: Double
25+
private let bCritical: Double
1926

2027
// constructor variables
2128
private let stiffness: Double
@@ -33,24 +40,40 @@ public final class SpringAnimation: KeyboardAnimation {
3340
omega0 = sqrt(stiffness / mass) // Undamped angular frequency of the oscillator
3441
omega1 = omega0 * sqrt(1.0 - zeta * zeta) // Exponential decay
3542
v0 = -initialVelocity
43+
x0 = toValue - fromValue
44+
isUnderDamped = zeta < 1
45+
46+
if isUnderDamped {
47+
// Under damped
48+
aCritical = 0
49+
bCritical = 0
50+
aUnder = (v0 + zeta * omega0 * x0) / omega1
51+
bUnder = x0
52+
} else {
53+
// Critically damped
54+
aCritical = x0
55+
bCritical = (v0 + omega0 * x0)
56+
aUnder = 0
57+
bUnder = 0
58+
}
3659

3760
super.init(fromValue: fromValue, toValue: toValue, animation: animation)
3861
}
3962

4063
// public functions
4164
override func valueAt(time: Double) -> Double {
42-
let t = time * Double(speed)
43-
let x0 = toValue - fromValue
65+
let t = time * speed
4466

4567
var y: Double
46-
if zeta < 1 {
68+
if isUnderDamped {
4769
// Under damped
4870
let envelope = exp(-zeta * omega0 * t)
49-
y = toValue - envelope * (((v0 + zeta * omega0 * x0) / omega1) * sin(omega1 * t) + x0 * cos(omega1 * t))
71+
let angle = omega1 * t
72+
y = toValue - envelope * (aUnder * sin(angle) + bUnder * cos(angle))
5073
} else {
5174
// Critically damped
5275
let envelope = exp(-omega0 * t)
53-
y = toValue - envelope * (x0 + (v0 + omega0 * x0) * t)
76+
y = toValue - envelope * (aCritical + bCritical * t)
5477
}
5578

5679
return y

ios/animations/TimingAnimation.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public final class TimingAnimation: KeyboardAnimation {
3535

3636
// public functions
3737
override func valueAt(time: Double) -> Double {
38-
let x = time * Double(speed)
38+
let x = time * speed
3939
let frames = (animation?.duration ?? 0.0) * Double(speed)
4040
let fraction = min(x / frames, 1)
4141
let t = findTForX(xTarget: fraction)

0 commit comments

Comments
 (0)