Skip to content

Commit 89a6426

Browse files
authored
perf: Use YamlDeserializer as singleton instance (#10567)
* chore: use YamlDeserializer as singleton * chore: fix non-threadsafe classes
1 parent 36f8552 commit 89a6426

File tree

9 files changed

+25
-22
lines changed

9 files changed

+25
-22
lines changed

src/Docfx.Build/ApiPage/ApiPageProcessor.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ namespace Docfx.Build.ApiPage;
1111

1212
class ApiPageDocumentProcessor(IMarkdownService markdownService) : IDocumentProcessor
1313
{
14+
private static IDeserializer deserializer = new DeserializerBuilder().WithAttemptingUnquotedStringTypeDeserialization().Build();
15+
1416
IEnumerable<IDocumentBuildStep> IDocumentProcessor.BuildSteps => Array.Empty<IDocumentBuildStep>();
1517
void IDocumentProcessor.UpdateHref(FileModel model, IDocumentBuildContext context) { }
1618

@@ -35,7 +37,6 @@ public ProcessingPriority GetProcessingPriority(FileAndType file)
3537

3638
public FileModel Load(FileAndType file, ImmutableDictionary<string, object> metadata)
3739
{
38-
var deserializer = new DeserializerBuilder().WithAttemptingUnquotedStringTypeDeserialization().Build();
3940
var yml = EnvironmentContext.FileAbstractLayer.ReadAllText(file.File);
4041
var json = JsonSerializer.Serialize(deserializer.Deserialize<object>(yml));
4142
var data = JsonSerializer.Deserialize<ApiPage>(json, ApiPage.JsonSerializerOptions);

src/Docfx.Common/YamlUtility.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ namespace Docfx.Common;
1212

1313
public static class YamlUtility
1414
{
15-
private static readonly ThreadLocal<YamlSerializer> serializer = new(() => new YamlSerializer(SerializationOptions.DisableAliases));
16-
private static readonly ThreadLocal<YamlDeserializer> deserializer = new(() => new YamlDeserializer(ignoreUnmatched: true));
15+
private static readonly YamlSerializer serializer = new(SerializationOptions.DisableAliases);
16+
private static readonly YamlDeserializer deserializer = new(ignoreUnmatched: true);
1717

1818
public static void Serialize(TextWriter writer, object graph)
1919
{
@@ -30,7 +30,7 @@ public static void Serialize(TextWriter writer, object graph, string comments)
3030
writer.WriteLine(comment.TrimEnd('\r'));
3131
}
3232
}
33-
serializer.Value.Serialize(writer, graph);
33+
serializer.Serialize(writer, graph);
3434
}
3535

3636
public static void Serialize(string path, object graph, string comments)
@@ -41,7 +41,7 @@ public static void Serialize(string path, object graph, string comments)
4141

4242
public static T Deserialize<T>(TextReader reader)
4343
{
44-
return deserializer.Value.Deserialize<T>(reader);
44+
return deserializer.Deserialize<T>(reader);
4545
}
4646

4747
public static T Deserialize<T>(string path)
@@ -55,8 +55,8 @@ public static T ConvertTo<T>(object obj)
5555
var sb = new StringBuilder();
5656
using (var writer = new StringWriter(sb))
5757
{
58-
serializer.Value.Serialize(writer, obj);
58+
serializer.Serialize(writer, obj);
5959
}
60-
return deserializer.Value.Deserialize<T>(new StringReader(sb.ToString()));
60+
return deserializer.Deserialize<T>(new StringReader(sb.ToString()));
6161
}
6262
}

src/Docfx.Dotnet/DotnetApiCatalog.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ namespace Docfx.Dotnet;
1515
/// </summary>
1616
public static partial class DotnetApiCatalog
1717
{
18+
private static IDeserializer deserializer = new DeserializerBuilder().WithAttemptingUnquotedStringTypeDeserialization().Build();
19+
1820
/// <summary>
1921
/// Generates metadata reference YAML files using docfx.json config.
2022
/// </summary>
@@ -96,13 +98,12 @@ void WriteMarkdown(string outputFolder, string id, Build.ApiPage.ApiPage apiPage
9698
break;
9799

98100
case MetadataOutputFormat.ApiPage:
99-
var serializer = new DeserializerBuilder().WithAttemptingUnquotedStringTypeDeserialization().Build();
100101
CreatePages(WriteYaml, assemblies, config, options);
101102

102103
void WriteYaml(string outputFolder, string id, Build.ApiPage.ApiPage apiPage)
103104
{
104105
var json = JsonSerializer.Serialize(apiPage, Docfx.Build.ApiPage.ApiPage.JsonSerializerOptions);
105-
var obj = serializer.Deserialize(json);
106+
var obj = deserializer.Deserialize(json);
106107
YamlUtility.Serialize(Path.Combine(outputFolder, $"{id}.yml"), obj, "YamlMime:ApiPage");
107108
}
108109
break;

src/Docfx.YamlSerialization/NodeDeserializers/EmitGenericCollectionNodeDeserializer.cs

Lines changed: 3 additions & 2 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.Reflection;
56
using System.Reflection.Emit;
67
using Docfx.YamlSerialization.Helpers;
@@ -20,9 +21,9 @@ public class EmitGenericCollectionNodeDeserializer : INodeDeserializer
2021
private readonly IObjectFactory _objectFactory;
2122
private readonly INamingConvention _enumNamingConvention;
2223
private readonly ITypeInspector _typeDescriptor;
23-
private readonly Dictionary<Type, Type?> _gpCache =
24+
private readonly ConcurrentDictionary<Type, Type?> _gpCache =
2425
new();
25-
private readonly Dictionary<Type, Action<IParser, Type, Func<IParser, Type, object?>, object?, INamingConvention, ITypeInspector>> _actionCache =
26+
private readonly ConcurrentDictionary<Type, Action<IParser, Type, Func<IParser, Type, object?>, object?, INamingConvention, ITypeInspector>> _actionCache =
2627
new();
2728

2829
public EmitGenericCollectionNodeDeserializer(IObjectFactory objectFactory, INamingConvention enumNamingConvention, ITypeInspector typeDescriptor)

src/Docfx.YamlSerialization/NodeDeserializers/EmitGenericDictionaryNodeDeserializer.cs

Lines changed: 3 additions & 2 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.ComponentModel;
56
using System.Reflection;
67
using System.Reflection.Emit;
@@ -16,8 +17,8 @@ public class EmitGenericDictionaryNodeDeserializer : INodeDeserializer
1617
private static readonly MethodInfo DeserializeHelperMethod =
1718
typeof(EmitGenericDictionaryNodeDeserializer).GetMethod(nameof(DeserializeHelper))!;
1819
private readonly IObjectFactory _objectFactory;
19-
private readonly Dictionary<Type, Type[]?> _gpCache = [];
20-
private readonly Dictionary<Tuple<Type, Type>, Action<IParser, Type, Func<IParser, Type, object?>, object?>> _actionCache = [];
20+
private readonly ConcurrentDictionary<Type, Type[]?> _gpCache = [];
21+
private readonly ConcurrentDictionary<Tuple<Type, Type>, Action<IParser, Type, Func<IParser, Type, object?>, object?>> _actionCache = [];
2122

2223
public EmitGenericDictionaryNodeDeserializer(IObjectFactory objectFactory)
2324
{

src/Docfx.YamlSerialization/ObjectFactories/DefaultEmitObjectFactory.cs

Lines changed: 2 additions & 1 deletion
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.Reflection;
56
using System.Reflection.Emit;
67

@@ -10,7 +11,7 @@ namespace Docfx.YamlSerialization.ObjectFactories;
1011

1112
public class DefaultEmitObjectFactory : ObjectFactoryBase
1213
{
13-
private readonly Dictionary<Type, Func<object>> _cache = [];
14+
private readonly ConcurrentDictionary<Type, Func<object>> _cache = [];
1415
private static Type[] EmptyTypes => Type.EmptyTypes;
1516

1617
public override object Create(Type type)

src/Docfx.YamlSerialization/ObjectGraphTraversalStrategies/FullObjectGraphTraversalStrategy.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Collections;
5+
using System.Collections.Concurrent;
56
using System.ComponentModel;
67
using System.Diagnostics;
78
using System.Reflection;
@@ -32,7 +33,7 @@ public class FullObjectGraphTraversalStrategy : IObjectGraphTraversalStrategy
3233
private readonly IObjectFactory _objectFactory;
3334

3435
// private readonly Dictionary<Tuple<Type, Type>, Action<IPropertyDescriptor?, IObjectDescriptor, IObjectGraphVisitor, Type, Type, IObjectGraphVisitorContext, Stack<FullObjectGraphTraversalStrategy.ObjectPathSegment>, ObjectSerializer>> _behaviorCache = new();
35-
private readonly Dictionary<Tuple<Type, Type, Type>, Action<FullObjectGraphTraversalStrategy, object, IObjectGraphVisitor, INamingConvention, IObjectGraphVisitorContext, Stack<ObjectPathSegment>, ObjectSerializer>> _traverseGenericDictionaryCache = new();
36+
private readonly ConcurrentDictionary<Tuple<Type, Type, Type>, Action<FullObjectGraphTraversalStrategy, object, IObjectGraphVisitor, INamingConvention, IObjectGraphVisitorContext, Stack<ObjectPathSegment>, ObjectSerializer>> _traverseGenericDictionaryCache = new();
3637

3738
protected YamlSerializer Serializer { get; }
3839

src/Docfx.YamlSerialization/YamlSerializer.cs

Lines changed: 2 additions & 1 deletion
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 Docfx.YamlSerialization.Helpers;
56
using Docfx.YamlSerialization.ObjectDescriptors;
67
using Docfx.YamlSerialization.ObjectGraphTraversalStrategies;
@@ -141,7 +142,7 @@ private IEventEmitter CreateEventEmitter()
141142
{
142143
return new TypeAssigningEventEmitter(
143144
writer,
144-
new Dictionary<Type, TagName>(),
145+
new ConcurrentDictionary<Type, TagName>(),
145146
quoteNecessaryStrings: false,
146147
quoteYaml1_1Strings: false,
147148
defaultScalarStyle: ScalarStyle.Any,

test/docfx.Tests/SerializationTests/YamlSerializationTest.ApiPage.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ namespace docfx.Tests;
1212

1313
public partial class YamlSerializationTest
1414
{
15+
private static IDeserializer deserializer = new DeserializerBuilder().WithAttemptingUnquotedStringTypeDeserialization().Build();
16+
1517
[Theory]
1618
[TestData<ApiPage>]
1719
public void YamlSerializationTest_ApiPage(string path)
@@ -30,9 +32,6 @@ private static ApiPage LoadApiPage(string path)
3032
{
3133
path = PathHelper.ResolveTestDataPath(path);
3234

33-
var deserializer = new DeserializerBuilder().WithAttemptingUnquotedStringTypeDeserialization()
34-
.Build();
35-
3635
// 1. Deserialize ApiPage yaml as Dictionary
3736
// 2. Serialize to json
3837
// 3. Deserialize as ApiPage instance
@@ -56,8 +55,6 @@ private static void ValidateApiPageRoundTrip(ApiPage model)
5655

5756
private static string ToYaml(ApiPage model)
5857
{
59-
var deserializer = new DeserializerBuilder().WithAttemptingUnquotedStringTypeDeserialization().Build();
60-
6158
var json = JsonSerializer.Serialize(model, Docfx.Build.ApiPage.ApiPage.JsonSerializerOptions);
6259
var obj = deserializer.Deserialize(json);
6360

@@ -68,7 +65,6 @@ private static string ToYaml(ApiPage model)
6865

6966
private static ApiPage ToApiPage(string yaml)
7067
{
71-
var deserializer = new DeserializerBuilder().WithAttemptingUnquotedStringTypeDeserialization().Build();
7268
var dict = deserializer.Deserialize<Dictionary<object, object>>(new StringReader(yaml));
7369
var json = JsonSerializer.Serialize(dict);
7470
return JsonSerializer.Deserialize<ApiPage>(json, ApiPage.JsonSerializerOptions);

0 commit comments

Comments
 (0)