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

Fast FieldInfo reflection #98199

Merged
merged 12 commits into from
Feb 14, 2024
Merged

Conversation

steveharter
Copy link
Member

@steveharter steveharter commented Feb 9, 2024

This makes FieldInfo.SetValue and GetValue faster; the most improvements are:

  • ~9x faster for getting a reference type on a static field
  • ~6x faster for getting a reference type on an instance field
  • ~6x faster for setting a primitive type on a static field
  • ~4x faster for setting a primitive type on an instance field
  • ~3x faster for setting a reference type on a static field
  • ~2x faster for setting a reference type on an instance field

The cases for setting a value type in a static field are not improved since they follow the existing "slow path" to the runtime due to special logic including "box into" where we box the value type into an existing boxed object (non-primitive static value types are stored that way) and other special logic such as nullability. There are also other "slow paths" for cases where static variables are not fixed in memory, but these are somewhat rare cases.

This was done without using IL-emit; raw memory operations are used. This means there is no warm-up time and works on platforms that do not support emit.

This helps align field performance with fast property performance work (which is IL-emit based) that was done in v7 and v8. For example:

  • A property getter currently returns a reference type in ~8ns.
  • The same operation for a field get was ~24ns which is 3x slower than the getter and thus a bit out of line; now with the PR, it is ~4ns which is 2x faster than the getter.

In the future, the "FieldAccessor" class added here can be exposed publicly and methods added that do not require boxing\unboxing.

New benchmarks were added in dotnet/performance#3863.

Click for benchmarks
| Method                 | Job        | Toolchain                 | Mean      | Error     | StdDev    | Median    | Min       | Max       | Ratio | RatioSD | Gen0   | Allocated | Alloc Ratio |
|----------------------- |----------- |-------------------------- |----------:|----------:|----------:|----------:|----------:|----------:|------:|--------:|-------:|----------:|------------:|
| Field_Get_int          | Job-KTJOJZ | \FIELD_AFTER\corerun.exe  | 33.587 ns | 0.2600 ns | 0.2305 ns | 33.632 ns | 33.172 ns | 33.865 ns |  1.00 |    0.00 | 0.0022 |      24 B |        1.00 |
| Field_Get_int          | Job-HGOSQE | \FIELD_BEFORE\corerun.exe | 36.796 ns | 0.3367 ns | 0.2985 ns | 36.769 ns | 36.469 ns | 37.439 ns |  1.10 |    0.01 | 0.0022 |      24 B |        1.00 |
|                        |            |                           |           |           |           |           |           |           |       |         |        |           |             |
| Field_GetStatic_int    | Job-KTJOJZ | \FIELD_AFTER\corerun.exe  | 31.082 ns | 0.2469 ns | 0.2189 ns | 31.092 ns | 30.681 ns | 31.403 ns |  1.00 |    0.00 | 0.0022 |      24 B |        1.00 |
| Field_GetStatic_int    | Job-HGOSQE | \FIELD_BEFORE\corerun.exe | 40.721 ns | 0.4257 ns | 0.3555 ns | 40.684 ns | 40.081 ns | 41.492 ns |  1.31 |    0.02 | 0.0023 |      24 B |        1.00 |
|                        |            |                           |           |           |           |           |           |           |       |         |        |           |             |
| Field_Get_class        | Job-KTJOJZ | \FIELD_AFTER\corerun.exe  |  4.067 ns | 0.0652 ns | 0.0578 ns |  4.049 ns |  4.007 ns |  4.196 ns |  1.00 |    0.00 |      - |         - |          NA |
| Field_Get_class        | Job-HGOSQE | \FIELD_BEFORE\corerun.exe | 24.524 ns | 0.1935 ns | 0.1616 ns | 24.550 ns | 24.277 ns | 24.890 ns |  6.02 |    0.08 |      - |         - |          NA |
|                        |            |                           |           |           |           |           |           |           |       |         |        |           |             |
| Field_GetStatic_class  | Job-KTJOJZ | \FIELD_AFTER\corerun.exe  |  2.582 ns | 0.0155 ns | 0.0137 ns |  2.584 ns |  2.560 ns |  2.610 ns |  1.00 |    0.00 |      - |         - |          NA |
| Field_GetStatic_class  | Job-HGOSQE | \FIELD_BEFORE\corerun.exe | 23.314 ns | 0.1366 ns | 0.1211 ns | 23.311 ns | 23.113 ns | 23.591 ns |  9.03 |    0.07 |      - |         - |          NA |
|                        |            |                           |           |           |           |           |           |           |       |         |        |           |             |
| Field_Get_struct       | Job-KTJOJZ | \FIELD_AFTER\corerun.exe  | 36.907 ns | 0.5991 ns | 0.5604 ns | 36.717 ns | 36.259 ns | 38.036 ns |  1.00 |    0.00 | 0.0022 |      24 B |        1.00 |
| Field_Get_struct       | Job-HGOSQE | \FIELD_BEFORE\corerun.exe | 42.983 ns | 0.3850 ns | 0.3413 ns | 42.990 ns | 42.385 ns | 43.473 ns |  1.17 |    0.02 | 0.0022 |      24 B |        1.00 |
|                        |            |                           |           |           |           |           |           |           |       |         |        |           |             |
| Field_GetStatic_struct | Job-KTJOJZ | \FIELD_AFTER\corerun.exe  | 32.613 ns | 0.3827 ns | 0.3393 ns | 32.534 ns | 32.089 ns | 33.224 ns |  1.00 |    0.00 | 0.0023 |      24 B |        1.00 |
| Field_GetStatic_struct | Job-HGOSQE | \FIELD_BEFORE\corerun.exe | 49.550 ns | 0.5162 ns | 0.4829 ns | 49.586 ns | 48.833 ns | 50.682 ns |  1.52 |    0.02 | 0.0022 |      24 B |        1.00 |
|                        |            |                           |           |           |           |           |           |           |       |         |        |           |             |
| Field_Set_int          | Job-KTJOJZ | \FIELD_AFTER\corerun.exe  |  8.492 ns | 0.2633 ns | 0.2926 ns |  8.354 ns |  8.159 ns |  9.155 ns |  1.00 |    0.00 | 0.0023 |      24 B |        1.00 |
| Field_Set_int          | Job-HGOSQE | \FIELD_BEFORE\corerun.exe | 36.042 ns | 0.2227 ns | 0.2083 ns | 35.982 ns | 35.614 ns | 36.390 ns |  4.23 |    0.16 | 0.0022 |      24 B |        1.00 |
|                        |            |                           |           |           |           |           |           |           |       |         |        |           |             |
| Field_SetStatic_int    | Job-KTJOJZ | \FIELD_AFTER\corerun.exe  |  7.025 ns | 0.0746 ns | 0.0661 ns |  7.008 ns |  6.945 ns |  7.170 ns |  1.00 |    0.00 | 0.0023 |      24 B |        1.00 |
| Field_SetStatic_int    | Job-HGOSQE | \FIELD_BEFORE\corerun.exe | 41.886 ns | 0.3051 ns | 0.2854 ns | 41.981 ns | 41.416 ns | 42.310 ns |  5.97 |    0.06 | 0.0022 |      24 B |        1.00 |
|                        |            |                           |           |           |           |           |           |           |       |         |        |           |             |
| Field_Set_class        | Job-KTJOJZ | \FIELD_AFTER\corerun.exe  | 16.439 ns | 0.1553 ns | 0.1297 ns | 16.473 ns | 16.176 ns | 16.677 ns |  1.00 |    0.00 | 0.0022 |      24 B |        1.00 |
| Field_Set_class        | Job-HGOSQE | \FIELD_BEFORE\corerun.exe | 36.227 ns | 0.5184 ns | 0.4849 ns | 36.005 ns | 35.649 ns | 37.190 ns |  2.21 |    0.03 | 0.0022 |      24 B |        1.00 |
|                        |            |                           |           |           |           |           |           |           |       |         |        |           |             |
| Field_SetStatic_class  | Job-KTJOJZ | \FIELD_AFTER\corerun.exe  | 15.404 ns | 0.1883 ns | 0.1761 ns | 15.388 ns | 15.147 ns | 15.713 ns |  1.00 |    0.00 | 0.0022 |      24 B |        1.00 |
| Field_SetStatic_class  | Job-HGOSQE | \FIELD_BEFORE\corerun.exe | 47.866 ns | 0.3628 ns | 0.3216 ns | 47.870 ns | 47.301 ns | 48.449 ns |  3.11 |    0.04 | 0.0022 |      24 B |        1.00 |
|                        |            |                           |           |           |           |           |           |           |       |         |        |           |             |
| Field_Set_struct       | Job-KTJOJZ | \FIELD_AFTER\corerun.exe  | 29.592 ns | 0.2402 ns | 0.2129 ns | 29.603 ns | 29.322 ns | 30.117 ns |  1.00 |    0.00 | 0.0023 |      24 B |        1.00 |
| Field_Set_struct       | Job-HGOSQE | \FIELD_BEFORE\corerun.exe | 31.048 ns | 0.2436 ns | 0.2279 ns | 31.092 ns | 30.488 ns | 31.332 ns |  1.05 |    0.01 | 0.0022 |      24 B |        1.00 |
|                        |            |                           |           |           |           |           |           |           |       |         |        |           |             |
| Field_SetStatic_struct | Job-OBFLPU | \FIELD_AFTER\corerun.exe  | 38.023 ns | 0.4172 ns | 0.3902 ns | 37.993 ns | 37.409 ns | 38.663 ns |  1.00 |    0.00 | 0.0023 |      24 B |        1.00 |
| Field_SetStatic_struct | Job-BQNCGS | \FIELD_BEFORE\corerun.exe | 38.558 ns | 0.3617 ns | 0.3206 ns | 38.526 ns | 38.109 ns | 39.295 ns |  1.01 |    0.01 | 0.0023 |      24 B |        1.00 |

<\details>

@steveharter steveharter self-assigned this Feb 9, 2024
@ghost
Copy link

ghost commented Feb 9, 2024

Tagging subscribers to this area: @dotnet/area-system-reflection
See info in area-owners.md if you want to be subscribed.

Issue Details

Verifying tests

Author: steveharter
Assignees: steveharter
Labels:

area-System.Reflection, tenet-performance

Milestone: -

src/coreclr/vm/field.h Outdated Show resolved Hide resolved
Copy link
Contributor

@buyaa-n buyaa-n left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JFYI there is an open question in the PR, otherwise LGTM.

@steveharter
Copy link
Member Author

steveharter commented Feb 13, 2024

@jkotas I noticed that the runtime wraps exceptions thrown in FieldInfo.GetValue\SetValue() (such as TypeInitializationException) with TargetInvocationException however that is not done by Mono and NativeAOT.

Unlike invoking a static method, calling GetField\SetField doesn't run any code (fields don't have code) except for the possibility of indirectly calling the class initializer.

So unless I hear otherwise, I'll create issue for Mono and NativeAOT to throw TargetInvocationException if the class initializer throws. I'd consider it a low-priority edge case. The other option is to change the Core runtime to match Mono\NativeAOT.

UPDATE: there doesn't appear to be hard rules when the class initializer is called other than "when first accessed", so either TypeInitializationException or TargetInvocationException seems fine to me. Leaving as-is.

@steveharter steveharter merged commit d3ebc97 into dotnet:main Feb 14, 2024
180 checks passed
@steveharter steveharter deleted the FastFieldAccess2 branch February 14, 2024 20:58
@github-actions github-actions bot locked and limited conversation to collaborators Mar 16, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants