Skip to content

Commit 21f3bbe

Browse files
Reduce contention for Datacontract Serialization (#73893)
* Use ConcurrentDictionary to avoid lock contention when serializing using DataContractSerializer * Improve concurrency for GetId * Prevent allocating multiple slots in s_dataContractCache for the same typeHandle * Use RuntimeTypeHandle as key in dictionary * add readonly * Remove usage of lazy * revert whitespace changes * Use RuntimeTypeHandle.Value as key instead of RuntimeTypeHandle * Apply same perf improvement to JDCS; Remove Int/TypeHandleRef silliness that we no longer need. --------- Co-authored-by: Steve Molloy <[email protected]>
1 parent 2d9acc6 commit 21f3bbe

File tree

2 files changed

+34
-126
lines changed

2 files changed

+34
-126
lines changed

src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/DataContract.cs

Lines changed: 22 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Buffers.Binary;
66
using System.Collections;
7+
using System.Collections.Concurrent;
78
using System.Collections.Generic;
89
using System.Collections.ObjectModel;
910
using System.Diagnostics;
@@ -297,19 +298,16 @@ internal virtual bool IsValidContract()
297298

298299
internal class DataContractCriticalHelper
299300
{
300-
private static readonly Hashtable s_typeToIDCache = new Hashtable(new HashTableEqualityComparer());
301+
private static readonly ConcurrentDictionary<nint, int> s_typeToIDCache = new();
301302
private static DataContract[] s_dataContractCache = new DataContract[32];
302303
private static int s_dataContractID;
303-
private static Dictionary<Type, DataContract?>? s_typeToBuiltInContract;
304+
private static readonly ConcurrentDictionary<Type, DataContract?> s_typeToBuiltInContract = new();
304305
private static Dictionary<XmlQualifiedName, DataContract?>? s_nameToBuiltInContract;
305306
private static Dictionary<string, DataContract?>? s_typeNameToBuiltInContract;
306307
private static readonly Hashtable s_namespaces = new Hashtable();
307308
private static Dictionary<string, XmlDictionaryString>? s_clrTypeStrings;
308309
private static XmlDictionary? s_clrTypeStringsDictionary;
309310

310-
[ThreadStatic]
311-
private static TypeHandleRef? s_typeHandleRef;
312-
313311
private static readonly object s_cacheLock = new object();
314312
private static readonly object s_createDataContractLock = new object();
315313
private static readonly object s_initBuiltInContractsLock = new object();
@@ -393,36 +391,29 @@ private static bool ContractMatches(DataContract contract, DataContract cachedCo
393391
internal static int GetId(RuntimeTypeHandle typeHandle)
394392
{
395393
typeHandle = GetDataContractAdapterTypeHandle(typeHandle);
396-
s_typeHandleRef ??= new TypeHandleRef();
397-
s_typeHandleRef.Value = typeHandle;
398394

399-
object? value = s_typeToIDCache[s_typeHandleRef];
400-
if (value != null)
401-
return ((IntRef)value).Value;
395+
if (s_typeToIDCache.TryGetValue(typeHandle.Value, out int id))
396+
return id;
402397

403398
try
404399
{
405400
lock (s_cacheLock)
406401
{
407-
value = s_typeToIDCache[s_typeHandleRef];
408-
if (value != null)
409-
return ((IntRef)value).Value;
410-
411-
int nextId = s_dataContractID++;
412-
if (nextId >= s_dataContractCache.Length)
402+
return s_typeToIDCache.GetOrAdd(typeHandle.Value, static _ =>
413403
{
414-
int newSize = (nextId < int.MaxValue / 2) ? nextId * 2 : int.MaxValue;
415-
if (newSize <= nextId)
404+
int nextId = s_dataContractID++;
405+
if (nextId >= s_dataContractCache.Length)
416406
{
417-
Debug.Fail("DataContract cache overflow");
418-
throw new SerializationException(SR.DataContractCacheOverflow);
407+
int newSize = (nextId < int.MaxValue / 2) ? nextId * 2 : int.MaxValue;
408+
if (newSize <= nextId)
409+
{
410+
Debug.Fail("DataContract cache overflow");
411+
throw new SerializationException(SR.DataContractCacheOverflow);
412+
}
413+
Array.Resize<DataContract>(ref s_dataContractCache, newSize);
419414
}
420-
Array.Resize<DataContract>(ref s_dataContractCache, newSize);
421-
}
422-
IntRef id = new IntRef(nextId);
423-
424-
s_typeToIDCache.Add(new TypeHandleRef(typeHandle), id);
425-
return id.Value;
415+
return nextId;
416+
});
426417
}
427418
}
428419
catch (Exception ex) when (!ExceptionUtility.IsFatal(ex))
@@ -589,17 +580,11 @@ private static RuntimeTypeHandle GetDataContractAdapterTypeHandle(RuntimeTypeHan
589580
if (type.IsInterface && !CollectionDataContract.IsCollectionInterface(type))
590581
type = Globals.TypeOfObject;
591582

592-
lock (s_initBuiltInContractsLock)
583+
return s_typeToBuiltInContract.GetOrAdd(type, static (Type key) =>
593584
{
594-
s_typeToBuiltInContract ??= new Dictionary<Type, DataContract?>();
595-
596-
if (!s_typeToBuiltInContract.TryGetValue(type, out DataContract? dataContract))
597-
{
598-
TryCreateBuiltInDataContract(type, out dataContract);
599-
s_typeToBuiltInContract.Add(type, dataContract);
600-
}
585+
TryCreateBuiltInDataContract(key, out DataContract? dataContract);
601586
return dataContract;
602-
}
587+
});
603588
}
604589

605590
[RequiresDynamicCode(DataContract.SerializerAOTWarning)]
@@ -945,19 +930,8 @@ internal static void ThrowInvalidDataContractException(string? message, Type? ty
945930
{
946931
if (type != null)
947932
{
948-
lock (s_cacheLock)
949-
{
950-
s_typeHandleRef ??= new TypeHandleRef();
951-
s_typeHandleRef.Value = GetDataContractAdapterTypeHandle(type.TypeHandle);
952-
953-
if (s_typeToIDCache.ContainsKey(s_typeHandleRef))
954-
{
955-
lock (s_cacheLock)
956-
{
957-
s_typeToIDCache.Remove(s_typeHandleRef);
958-
}
959-
}
960-
}
933+
RuntimeTypeHandle runtimeTypeHandle = GetDataContractAdapterTypeHandle(type.TypeHandle);
934+
s_typeToIDCache.TryRemove(runtimeTypeHandle.Value, out _);
961935
}
962936

963937
throw new InvalidDataContractException(message);
@@ -2515,62 +2489,4 @@ public override int GetHashCode()
25152489
return _object1.GetHashCode() ^ _object2.GetHashCode();
25162490
}
25172491
}
2518-
2519-
internal sealed class HashTableEqualityComparer : IEqualityComparer
2520-
{
2521-
bool IEqualityComparer.Equals(object? x, object? y)
2522-
{
2523-
return ((TypeHandleRef)x!).Value.Equals(((TypeHandleRef)y!).Value);
2524-
}
2525-
2526-
public int GetHashCode(object obj)
2527-
{
2528-
return ((TypeHandleRef)obj).Value.GetHashCode();
2529-
}
2530-
}
2531-
2532-
internal sealed class TypeHandleRefEqualityComparer : IEqualityComparer<TypeHandleRef>
2533-
{
2534-
public bool Equals(TypeHandleRef? x, TypeHandleRef? y)
2535-
{
2536-
return x!.Value.Equals(y!.Value);
2537-
}
2538-
2539-
public int GetHashCode(TypeHandleRef obj)
2540-
{
2541-
return obj.Value.GetHashCode();
2542-
}
2543-
}
2544-
2545-
internal sealed class TypeHandleRef
2546-
{
2547-
private RuntimeTypeHandle _value;
2548-
2549-
public TypeHandleRef()
2550-
{
2551-
}
2552-
2553-
public TypeHandleRef(RuntimeTypeHandle value)
2554-
{
2555-
_value = value;
2556-
}
2557-
2558-
public RuntimeTypeHandle Value
2559-
{
2560-
get => _value;
2561-
set => _value = value;
2562-
}
2563-
}
2564-
2565-
internal sealed class IntRef
2566-
{
2567-
private readonly int _value;
2568-
2569-
public IntRef(int value)
2570-
{
2571-
_value = value;
2572-
}
2573-
2574-
public int Value => _value;
2575-
}
25762492
}

src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/Json/JsonDataContract.cs

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Collections.Concurrent;
45
using System.Collections.Generic;
56
using System.Diagnostics;
67
using System.Diagnostics.CodeAnalysis;
@@ -151,8 +152,7 @@ internal class JsonDataContractCriticalHelper
151152
private static JsonDataContract[] s_dataContractCache = new JsonDataContract[32];
152153
private static int s_dataContractID;
153154

154-
private static readonly TypeHandleRef s_typeHandleRef = new TypeHandleRef();
155-
private static readonly Dictionary<TypeHandleRef, IntRef> s_typeToIDCache = new Dictionary<TypeHandleRef, IntRef>(new TypeHandleRefEqualityComparer());
155+
private static readonly ConcurrentDictionary<nint, int> s_typeToIDCache = new();
156156
private DataContractDictionary? _knownDataContracts;
157157
private readonly DataContract _traditionalDataContract;
158158
private readonly string _typeName;
@@ -188,34 +188,26 @@ public static JsonDataContract GetJsonDataContract(DataContract traditionalDataC
188188

189189
internal static int GetId(RuntimeTypeHandle typeHandle)
190190
{
191+
if (s_typeToIDCache.TryGetValue(typeHandle.Value, out int id))
192+
return id;
193+
191194
lock (s_cacheLock)
192195
{
193-
IntRef? id;
194-
s_typeHandleRef.Value = typeHandle;
195-
if (!s_typeToIDCache.TryGetValue(s_typeHandleRef, out id))
196+
return s_typeToIDCache.GetOrAdd(typeHandle.Value, static _ =>
196197
{
197-
int value = s_dataContractID++;
198-
if (value >= s_dataContractCache.Length)
198+
int nextId = s_dataContractID++;
199+
if (nextId >= s_dataContractCache.Length)
199200
{
200-
int newSize = (value < int.MaxValue / 2) ? value * 2 : int.MaxValue;
201-
if (newSize <= value)
201+
int newSize = (nextId < int.MaxValue / 2) ? nextId * 2 : int.MaxValue;
202+
if (newSize <= nextId)
202203
{
203204
Debug.Fail("DataContract cache overflow");
204205
throw new SerializationException(SR.DataContractCacheOverflow);
205206
}
206207
Array.Resize<JsonDataContract>(ref s_dataContractCache, newSize);
207208
}
208-
id = new IntRef(value);
209-
try
210-
{
211-
s_typeToIDCache.Add(new TypeHandleRef(typeHandle), id);
212-
}
213-
catch (Exception ex) when (!ExceptionUtility.IsFatal(ex))
214-
{
215-
throw new Exception(ex.Message, ex);
216-
}
217-
}
218-
return id.Value;
209+
return nextId;
210+
});
219211
}
220212
}
221213

0 commit comments

Comments
 (0)