Commit 3c2a066
authored
[generator] emit faster overloads for non-virtual Formatted props (#1101)
Context: dotnet/maui#12130
Context: #994
Context: 79a8e1e
Context: xamarin/monodroid@23f4212
When binding members which have parameter types or return types which
are `java.lang.CharSequence`, the member is "overloaded" to replace
`CharSequence` with `System.String`, and the "original" member has a
`Formatted` *suffix*.
For example, consider [`android.widget.TextView`][1], which has
[`getText()`][1] and [`setText()`][2] methods which have parameter
types and return types which are `java.lang.CharSequence`:
// Java
/* partial */ class TextView extends View {
public CharSequence getText();
public final void setText(CharSequence text);
}
When bound, this results in *two* properties:
// C#
partial class TextView : View {
public Java.Lang.ICharSequence? TextFormatted {get => …; set => …; }
public string? Text {get => …; set => …; }
}
This is also done for methods; see also 79a8e1e.
The "non-`Formatted` overload" works by creating `String` temporaries
to invoke the `Formatted` overload:
partial class TextView {
public string? Text {
get => TextFormatted?.ToString ();
set {
var jls = value == null ? null : new Java.Lang.String (value);
TextFormatted = jls;
jls?.Dispose ();
}
}
}
*Why* was this done? Because [C# 4.0][3] didn't allow interfaces to
provide conversion operators. ([C# 8.0][4] would add support for
interfaces to contain operators.) "Overloading" in this fashion made
it easier to use `System.String` literals with `ICharSequence` members;
compare:
view.Text = "string literal";
// vs.
view.TextFormatted = new Java.Lang.String("string literal");
// …and who would know how to do this?
A problem with the this approach is performance: creating a new
`Java.Lang.String` instance requires:
1. Creating the managed peer (the `Java.Lang.String` instance),
2. Creating the native peer (the `java.lang.String` instance),
3. And *registering the mapping* between (1) and (2)
which feels a bit "silly" when we immediately dispose of the value.
This is particularly noticeable with .NET MAUI apps. Consider the
[angelru/CvSlowJittering][5] app, which uses XAML to set `Text`
properties, which eventually hit `TextView.Text`. Profiling shows:
653.69ms (6.3%) mono.android!Android.Widget.TextView.set_Text(string)
198.05ms (1.9%) mono.android!Java.Lang.String..ctor(string)
121.57ms (1.2%) mono.android!Java.Lang.Object.Dispose()
*6.3%* of scrolling time is spent in the `TextView.Text` property
setter!
*Partially optimize* this case: if the `*Formatted` member is
(1) a property, and (2) *not* `virtual`, then we can directly call
the Java setter method. This avoids the need to create a managed
peer and to register a mapping between the peers:
// New hotness
partial class TextView {
public string? Text {
get => TextFormatted?.ToString (); // unchanged
set {
const string __id = "setText.(Ljava/lang/CharSequence;)V";
JniObjectReference native_value = JniEnvironment.Strings.NewString (value);
try {
JniArgumentValue* __args = stackalloc JniArgumentValue [1];
__args [0] = new JniArgumentValue (native_value);
_members.InstanceMethods.InvokeNonvirtualVoidMethod (__id, this, __args);
} finally {
JniObjectReference.Dispose (ref native_value);
}
}
}
}
[The result][6]?
| Method | Mean | Error | StdDev | Allocated |
|-------------------- |---------:|----------:|----------:|----------:|
| Before SetFinalText | 6.632 us | 0.0101 us | 0.0079 us | 112 B |
| After SetFinalText | 1.361 us | 0.0022 us | 0.0019 us | - |
The `TextView.Text` property setter invocation time is reduced to 20%
of the previous average invocation time.
Note: We *cannot* do this "inlining" if the "`Formatted` overload" is
[`virtual`][7], as that will result in broken semantics that make
sense to *nobody*.
TODO: Consider Optimizing the "`virtual` overload" scenario? This
could be done by updating `Java.Lang.String`:
partial interface ICharSequence {
public static String FromJniObjectReference (ref JniObjectReference reference, JniObjectReferenceOptions options);
}
Then updating the "non-`Formatted` overload" to use
`String.FromJniHandle()`:
partial class TextView {
public string? Text {
get => TextFormatted?.ToString (); // unchanged
set {
JniObjectReference native_value = JniEnvironment.Strings.NewString (value);
var java_value = Java.Lang.ICharSequence.FromJniObjectReference (ref native_value, JniObjectReferenceOptions.CopyAndDoNotRegister);
TextFormatted = java_value;
java_value?.Dispose ();
}
}
}
[0]: https://developer.android.com/reference/android/widget/TextView
[1]: https://developer.android.com/reference/android/widget/TextView#getText()
[2]: https://developer.android.com/reference/android/widget/TextView#setText(java.lang.CharSequence)
[3]: https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history#c-version-40
[4]: https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history#c-version-80
[5]: https://github.com/angelru/CvSlowJittering
[6]: https://github.com/jonathanpeppers/BenchmarkDotNet-Android/tree/Android.Widget.TextView
[7]: #994 (comment)1 parent e7d2edb commit 3c2a066
File tree
6 files changed
+163
-26
lines changed- tests/generator-Tests/Unit-Tests
- tools/generator/SourceWriters
- Extensions
6 files changed
+163
-26
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1152 | 1152 | | |
1153 | 1153 | | |
1154 | 1154 | | |
| 1155 | + | |
| 1156 | + | |
| 1157 | + | |
| 1158 | + | |
| 1159 | + | |
| 1160 | + | |
| 1161 | + | |
| 1162 | + | |
| 1163 | + | |
| 1164 | + | |
| 1165 | + | |
| 1166 | + | |
| 1167 | + | |
| 1168 | + | |
| 1169 | + | |
| 1170 | + | |
| 1171 | + | |
| 1172 | + | |
| 1173 | + | |
| 1174 | + | |
| 1175 | + | |
| 1176 | + | |
| 1177 | + | |
| 1178 | + | |
| 1179 | + | |
| 1180 | + | |
| 1181 | + | |
| 1182 | + | |
| 1183 | + | |
| 1184 | + | |
| 1185 | + | |
| 1186 | + | |
| 1187 | + | |
| 1188 | + | |
| 1189 | + | |
| 1190 | + | |
| 1191 | + | |
| 1192 | + | |
| 1193 | + | |
| 1194 | + | |
| 1195 | + | |
| 1196 | + | |
| 1197 | + | |
| 1198 | + | |
| 1199 | + | |
| 1200 | + | |
| 1201 | + | |
| 1202 | + | |
| 1203 | + | |
| 1204 | + | |
| 1205 | + | |
| 1206 | + | |
| 1207 | + | |
1155 | 1208 | | |
1156 | 1209 | | |
1157 | 1210 | | |
| |||
1236 | 1289 | | |
1237 | 1290 | | |
1238 | 1291 | | |
| 1292 | + | |
| 1293 | + | |
| 1294 | + | |
| 1295 | + | |
| 1296 | + | |
| 1297 | + | |
| 1298 | + | |
| 1299 | + | |
| 1300 | + | |
| 1301 | + | |
| 1302 | + | |
| 1303 | + | |
| 1304 | + | |
| 1305 | + | |
| 1306 | + | |
| 1307 | + | |
| 1308 | + | |
| 1309 | + | |
| 1310 | + | |
| 1311 | + | |
| 1312 | + | |
| 1313 | + | |
| 1314 | + | |
| 1315 | + | |
| 1316 | + | |
| 1317 | + | |
| 1318 | + | |
| 1319 | + | |
| 1320 | + | |
| 1321 | + | |
| 1322 | + | |
| 1323 | + | |
| 1324 | + | |
| 1325 | + | |
| 1326 | + | |
| 1327 | + | |
| 1328 | + | |
| 1329 | + | |
| 1330 | + | |
| 1331 | + | |
| 1332 | + | |
| 1333 | + | |
| 1334 | + | |
| 1335 | + | |
| 1336 | + | |
| 1337 | + | |
| 1338 | + | |
| 1339 | + | |
| 1340 | + | |
| 1341 | + | |
| 1342 | + | |
| 1343 | + | |
1239 | 1344 | | |
1240 | 1345 | | |
1241 | 1346 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
346 | 346 | | |
347 | 347 | | |
348 | 348 | | |
349 | | - | |
| 349 | + | |
350 | 350 | | |
351 | 351 | | |
352 | 352 | | |
| |||
361 | 361 | | |
362 | 362 | | |
363 | 363 | | |
364 | | - | |
| 364 | + | |
| 365 | + | |
365 | 366 | | |
366 | 367 | | |
367 | | - | |
| 368 | + | |
368 | 369 | | |
369 | 370 | | |
370 | 371 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
215 | 215 | | |
216 | 216 | | |
217 | 217 | | |
218 | | - | |
| 218 | + | |
219 | 219 | | |
220 | 220 | | |
221 | 221 | | |
222 | 222 | | |
223 | 223 | | |
224 | | - | |
| 224 | + | |
225 | 225 | | |
226 | 226 | | |
227 | 227 | | |
| |||
Lines changed: 30 additions & 4 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
16 | | - | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
17 | 25 | | |
18 | 26 | | |
19 | 27 | | |
| |||
46 | 54 | | |
47 | 55 | | |
48 | 56 | | |
49 | | - | |
50 | | - | |
51 | | - | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
52 | 78 | | |
53 | 79 | | |
54 | 80 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
96 | 96 | | |
97 | 97 | | |
98 | 98 | | |
99 | | - | |
| 99 | + | |
100 | 100 | | |
101 | 101 | | |
102 | 102 | | |
| |||
Lines changed: 21 additions & 16 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
238 | 238 | | |
239 | 239 | | |
240 | 240 | | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
241 | 262 | | |
242 | 263 | | |
243 | 264 | | |
| |||
265 | 286 | | |
266 | 287 | | |
267 | 288 | | |
268 | | - | |
269 | | - | |
270 | | - | |
271 | | - | |
272 | | - | |
273 | | - | |
274 | | - | |
275 | | - | |
276 | | - | |
277 | | - | |
278 | | - | |
279 | | - | |
280 | | - | |
281 | | - | |
282 | | - | |
283 | | - | |
284 | 289 | | |
285 | 290 | | |
286 | 291 | | |
| |||
0 commit comments