Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Use caching in Reflection.Emit member accessors
  • Loading branch information
eiriktsarpalis committed Feb 10, 2022
commit 76c90692025cadb5cea41fc2aff9a3a3047cb9f3
2 changes: 2 additions & 0 deletions src/libraries/System.Text.Json/src/System.Text.Json.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,8 @@ System.Text.Json.Nodes.JsonValue</PackageDescription>
<Compile Include="System\Text\Json\Serialization\Metadata\MemberAccessor.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\ParameterRef.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\PropertyRef.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\ReflectionEmitCachingMemberAccessor.Cache.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\ReflectionEmitCachingMemberAccessor.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\ReflectionEmitMemberAccessor.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\ReflectionMemberAccessor.cs" />
<Compile Include="System\Text\Json\Serialization\MetadataPropertyName.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -568,10 +568,10 @@ internal MemberAccessor MemberAccessorStrategy
#if NETCOREAPP
// if dynamic code isn't supported, fallback to reflection
_memberAccessorStrategy = RuntimeFeature.IsDynamicCodeSupported ?
new ReflectionEmitMemberAccessor() :
new ReflectionEmitCachingMemberAccessor() :
new ReflectionMemberAccessor();
#elif NETFRAMEWORK
_memberAccessorStrategy = new ReflectionEmitMemberAccessor();
_memberAccessorStrategy = new ReflectionEmitCachingMemberAccessor();
#else
_memberAccessorStrategy = new ReflectionMemberAccessor();
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Reflection.Metadata;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;

[assembly: MetadataUpdateHandler(typeof(JsonSerializerOptionsUpdateHandler))]

Expand All @@ -19,6 +20,9 @@ public static void ClearCache(Type[]? types)
{
options.Key.ClearClasses();
}

// Flush the dynamic method cache
ReflectionEmitCachingMemberAccessor.Clear();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#if NETFRAMEWORK || NETCOREAPP
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;

namespace System.Text.Json.Serialization.Metadata
{
internal sealed partial class ReflectionEmitCachingMemberAccessor
{
private sealed class Cache<TKey> where TKey : notnull
{
private int _lock;
private long _lastEvictedTicks; // tracks total number of invocations to the cache; can be allowed to overflow.
private readonly long _evictionIntervalTicks; // number of cache invocations needed before triggering an eviction run.
private readonly long _slidingExpirationTicks; // max timespan allowed for cache entries to remain inactive.
private readonly ConcurrentDictionary<TKey, CacheEntry> _cache = new();

public Cache(TimeSpan slidingExpiration, TimeSpan evictionInterval)
{
_slidingExpirationTicks = slidingExpiration.Ticks;
_evictionIntervalTicks = evictionInterval.Ticks;
_lastEvictedTicks = DateTime.UtcNow.Ticks;
}

public TValue GetOrAdd<TValue>(TKey key, Func<TKey, TValue> valueFactory) where TValue : class?
{
CacheEntry entry = _cache.GetOrAdd(key,
#if NETCOREAPP
static (TKey key, Func<TKey, TValue> valueFactory) => new(valueFactory(key)),
valueFactory);
#else
key => new(valueFactory(key)));
#endif
long utcNowTicks = DateTime.UtcNow.Ticks;
Volatile.Write(ref entry.LastUsedTicks, utcNowTicks);

if (utcNowTicks - Volatile.Read(ref _lastEvictedTicks) > _evictionIntervalTicks)
{
if (Interlocked.CompareExchange(ref _lock, 1, 0) == 0)
{
if (utcNowTicks - _lastEvictedTicks >= _evictionIntervalTicks)
{
EvictStaleCacheEntries(utcNowTicks);
Volatile.Write(ref _lastEvictedTicks, utcNowTicks);
Volatile.Write(ref _lock, 0);
}
}
}

return (TValue)entry.Value!;
}

public void Clear()
{
_cache.Clear();
_lastEvictedTicks = DateTime.UtcNow.Ticks;
}

private void EvictStaleCacheEntries(long utcNowTicks)
{
foreach (KeyValuePair<TKey, CacheEntry> kvp in _cache)
{
if (utcNowTicks - Volatile.Read(ref kvp.Value.LastUsedTicks) >= _slidingExpirationTicks)
{
_cache.TryRemove(kvp.Key, out _);
}
}
}

private class CacheEntry
{
public readonly object? Value;
public long LastUsedTicks;

public CacheEntry(object? value)
{
Value = value;
}
}
}
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#if NETFRAMEWORK || NETCOREAPP
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;

namespace System.Text.Json.Serialization.Metadata
{
internal sealed partial class ReflectionEmitCachingMemberAccessor : MemberAccessor
{
private static readonly ReflectionEmitMemberAccessor s_sourceAccessor = new();
private static readonly Cache<(string id, Type declaringType, MemberInfo? member)> s_cache =
new(slidingExpiration: TimeSpan.FromSeconds(5), evictionInterval: TimeSpan.FromSeconds(1));

public static void Clear() => s_cache.Clear();

public override Action<TCollection, object?> CreateAddMethodDelegate<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] TCollection>()
=> s_cache.GetOrAdd((nameof(CreateAddMethodDelegate), typeof(TCollection), null),
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2091:UnrecognizedReflectionPattern",
Justification = "DynamicallyAccessedMembersAttribute is not inherited by scoped generic parameter in lambda body.")]
static (_) => s_sourceAccessor.CreateAddMethodDelegate<TCollection>());

public override JsonTypeInfo.ConstructorDelegate? CreateConstructor([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type classType)
=> s_cache.GetOrAdd((nameof(CreateConstructor), classType, null),
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2077:UnrecognizedReflectionPattern",
Justification = "Cannot apply DynamicallyAccessedMembersAttribute to tuple properties.")]
static (key) => s_sourceAccessor.CreateConstructor(key.declaringType));

public override Func<object, TProperty> CreateFieldGetter<TProperty>(FieldInfo fieldInfo)
=> s_cache.GetOrAdd((nameof(CreateFieldGetter), typeof(TProperty), fieldInfo), static key => s_sourceAccessor.CreateFieldGetter<TProperty>((FieldInfo)key.member!));

public override Action<object, TProperty> CreateFieldSetter<TProperty>(FieldInfo fieldInfo)
=> s_cache.GetOrAdd((nameof(CreateFieldSetter), typeof(TProperty), fieldInfo), static key => s_sourceAccessor.CreateFieldSetter<TProperty>((FieldInfo)key.member!));

[RequiresUnreferencedCode(IEnumerableConverterFactoryHelpers.ImmutableConvertersUnreferencedCodeMessage)]
public override Func<IEnumerable<KeyValuePair<TKey, TValue>>, TCollection> CreateImmutableDictionaryCreateRangeDelegate<TCollection, TKey, TValue>()
=> s_cache.GetOrAdd((nameof(CreateImmutableDictionaryCreateRangeDelegate), typeof((TCollection, TKey, TValue)), null),
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "Factory method calls into chained RequiresUnreferencedCode method.")]
static (_) => s_sourceAccessor.CreateImmutableDictionaryCreateRangeDelegate<TCollection, TKey, TValue>());

[RequiresUnreferencedCode(IEnumerableConverterFactoryHelpers.ImmutableConvertersUnreferencedCodeMessage)]
public override Func<IEnumerable<TElement>, TCollection> CreateImmutableEnumerableCreateRangeDelegate<TCollection, TElement>()
=> s_cache.GetOrAdd((nameof(CreateImmutableEnumerableCreateRangeDelegate), typeof((TCollection, TElement)), null),
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "Factory method calls into chained RequiresUnreferencedCode method.")]
static (_) => s_sourceAccessor.CreateImmutableEnumerableCreateRangeDelegate<TCollection, TElement>());

public override Func<object[], T>? CreateParameterizedConstructor<T>(ConstructorInfo constructor)
=> s_cache.GetOrAdd((nameof(CreateParameterizedConstructor), typeof(T), constructor), static key => s_sourceAccessor.CreateParameterizedConstructor<T>((ConstructorInfo)key.member!));

public override JsonTypeInfo.ParameterizedConstructorDelegate<T, TArg0, TArg1, TArg2, TArg3>? CreateParameterizedConstructor<T, TArg0, TArg1, TArg2, TArg3>(ConstructorInfo constructor)
=> s_cache.GetOrAdd((nameof(CreateParameterizedConstructor), typeof(T), constructor), static key => s_sourceAccessor.CreateParameterizedConstructor<T, TArg0, TArg1, TArg2, TArg3>((ConstructorInfo)key.member!));

public override Func<object, TProperty> CreatePropertyGetter<TProperty>(PropertyInfo propertyInfo)
=> s_cache.GetOrAdd((nameof(CreatePropertyGetter), typeof(TProperty), propertyInfo), static key => s_sourceAccessor.CreatePropertyGetter<TProperty>((PropertyInfo)key.member!));

public override Action<object, TProperty> CreatePropertySetter<TProperty>(PropertyInfo propertyInfo)
=> s_cache.GetOrAdd((nameof(CreatePropertySetter), typeof(TProperty), propertyInfo), static key => s_sourceAccessor.CreatePropertySetter<TProperty>((PropertyInfo)key.member!));
}
}
#endif