Skip to content

Commit 2571e25

Browse files
committed
updated reflection benchmarks
1 parent 445357b commit 2571e25

File tree

2 files changed

+127
-27
lines changed

2 files changed

+127
-27
lines changed

docs/Posts/c# cost of reflection.md

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,17 @@ When I did the benchmarks for setting a value using reflection, I saw using an i
7474

7575
As this worked out very well, and I had too many model's so I built our own source generator using a console app as I was using .NET6 where the source generators are still experimental. And this whole implementation brought down our app launch time from 60+ seconds to less than 20 seconds. Btw, the properties inside the Model used to change very rarely. So when they did, we used to run the generator again and copy the files. And we ensure these generated files should never be changed manually and named them something like Foo.g.cs which helps me review the PR's easily from the team. And ofcourse, I wrote an API to return the properties and their metadata in the way that I need so that my generator can generate the classes for me.
7676

77+
## Version 2: (Update on  16th March 2025)
78+
Uses `ReadOnlySpan<char>` but same like V3.
7779

78-
## Version 2: (Update on  16th March 2025)
80+
## Version 3: (Update on  22th March 2025)
7981

8082
I read in multiple blogs that .NET invested a lot in performance improvement using ReadOnlySpan over the years. I was wondering if we can still make this better. So I tried again with ChatGPT help. Now compared to V1, V2 had at least 40% improvement over V1.
8183

8284
I renamed the interface like
8385

8486
```csharp
85-
public interface INoReflection
87+
public interface IObject
8688
{
8789
bool SetValue(string propertyName, object value);
8890
object GetValue(string propertyName);
@@ -93,7 +95,7 @@ I renamed the interface like
9395
Nothing changed in my interface. But the implementation changed a bit.
9496

9597
```csharp
96-
[TypedAccessorAttribute]
98+
[DynamicIObject]
9799
public partial class Foo
98100
{
99101
public int Integer { get; set; } = int.MaxValue;
@@ -102,7 +104,7 @@ public partial class Foo
102104
}
103105
```
104106

105-
Notice the `TypedAccessorAttribute` on top of class. And yes this time I used Incremental Source Generators as they are stable.
107+
Notice the `DynamicIObject` on top of class. And yes this time I used Incremental Source Generators as they are stable.
106108

107109
And my implementation is like
108110

@@ -111,48 +113,49 @@ And my implementation is like
111113
[MethodImpl(MethodImplOptions.AggressiveInlining)]
112114
public bool SetValue(string propertyName, object value)
113115
{
114-
ReadOnlySpan<char> span = propertyName;
115-
switch (span.Length)
116+
switch (propertyName.Length)
116117
{
117-
case 7 when span.SequenceEqual("Integer"):
118-
Integer = (int)value;
119-
return true;
120-
case 6 when span.SequenceEqual("String"):
118+
case 6 when string.CompareOrdinal(propertyName, "String") == 0:
121119
String = (string)value;
122120
return true;
121+
case 7 when string.CompareOrdinal(propertyName, "Integer") == 0:
122+
Integer = (int)value;
123+
return true;
123124
}
124125
return false;
125126
}
126127

127128
[MethodImpl(MethodImplOptions.AggressiveInlining)]
128129
public object GetValue(string propertyName)
129130
{
130-
ReadOnlySpan<char> span = propertyName;
131-
switch (span.Length)
131+
return propertyName.Length switch
132132
{
133-
case 7 when span.SequenceEqual("Integer"):
134-
return Integer;
135-
case 6 when span.SequenceEqual("String"):
136-
return String;
137-
}
138-
return null;
133+
6 when string.CompareOrdinal(propertyName, "String") == 0 => String,
134+
7 when string.CompareOrdinal(propertyName, "Integer") == 0 => Integer,
135+
_ => Unsafe.NullRef<object>(),
136+
};
139137
}
140138

141139
```
142140

143-
As I said, I took ChatGPT help, I am not 100% sure how `MethodImpl` helps. But this code brought down time taken to set a property from V1 to a min of 40% and max of 55%.
141+
As I said, I took ChatGPT help, I am not 100% sure how `MethodImpl` helps.
142+
143+
### Here are the benchmarks
144144

145-
### Here are the V2 Benchmarks:
145+
1. Reflection_Without_Cache
146+
2. Reflection_With_Cache
147+
3. V1 - use `nameof(Integer)` inside switch
148+
4. V2 - use `case 8 when span.SequenceEqual("DateTime")` inside switch
149+
5. V3 - use ` case 4 when string.CompareOrdinal(propertyName, "Guid") == 0` inside switch
146150

147-
.NET 8 only.
148-
{% include_relative reflection-benchmarks-v2-net8.md %}
151+
For some scenarios, V1 and V2 are better than V3. But ultimately V1, V2, V3 are found to be much faster than reflection.
149152

150-
.NET6, .NET7, .NET8, .NET9
151-
{% include_relative reflection-benchmarks-v2-net6-net7-net8-net9.md %}
152153

153-
[V2 Source Code](https://github.com/DotNetExtended/Default/tree/main/src/DotNetExtended.NoReflection)
154-
[V2 Benchmarks Source Code](https://github.com/DotNetExtended/Default/tree/main/src/DotNetExtended.NoReflection.Tests)
155-
[V2 Source Generator](https://github.com/DotNetExtended/Default/tree/main/src/DotNetExtended.NoReflection.SourceGenerators)
154+
.NET 8 only.
155+
{% include_relative reflection-benchmarks-v1-v2-v3-net8.md %}
156+
[Source Code](https://github.com/DotNetExtended/Default/tree/main/src/DotNetExtended.NoReflection)
157+
[Benchmarks Source Code](https://github.com/DotNetExtended/Default/tree/main/src/DotNetExtended.NoReflection.Tests)
158+
[Source Generator](https://github.com/DotNetExtended/Default/tree/main/src/DotNetExtended.NoReflection.SourceGenerators)
156159

157160

158161
This is one of my best performance improvements I have ever done. This might not be significant in many places  but for situations like mine, this saves a lot of time during run time.

0 commit comments

Comments
 (0)