Skip to content

Commit 7f93c22

Browse files
perf(serialization): optimize dictionary and array loops
Improve Dictionary and List serialization hot paths by iterating entries via ref views and remove enumerator overhead. Co-authored-by: OpenAI Codex <codex@openai.com>
1 parent 672c2c3 commit 7f93c22

3 files changed

Lines changed: 108 additions & 24 deletions

File tree

src/Nino.Core/TypeCollector.cs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Diagnostics.CodeAnalysis;
34
using System.Runtime.CompilerServices;
45

@@ -34,5 +35,29 @@ public class ListView<T>
3435
public T[] _items; // Do not rename (binary serialization)
3536
public int _size; // Do not rename (binary serialization)
3637
}
38+
39+
[SuppressMessage("ReSharper", "InconsistentNaming")]
40+
public class DictionaryView<TKey, TValue>
41+
{
42+
public int[]? _buckets; // Do not rename (binary serialization)
43+
public Entry[]? _entries; // Do not rename (binary serialization)
44+
public int _count; // Do not rename (binary serialization)
45+
public int _version; // Do not rename (binary serialization)
46+
public int _freeList; // Do not rename (binary serialization)
47+
public int _freeCount; // Do not rename (binary serialization)
48+
public IEqualityComparer<TKey>? _comparer; // Do not rename (binary serialization)
49+
public Dictionary<TKey, TValue>.KeyCollection? _keys; // Do not rename (binary serialization)
50+
public Dictionary<TKey, TValue>.ValueCollection? _values; // Do not rename (binary serialization)
51+
public object? _syncRoot; // Do not rename (binary serialization)
52+
53+
[SuppressMessage("ReSharper", "InconsistentNaming")]
54+
public struct Entry
55+
{
56+
public int hashCode; // Do not rename (binary serialization)
57+
public int next; // Do not rename (binary serialization)
58+
public TKey key; // Do not rename (binary serialization)
59+
public TValue value; // Do not rename (binary serialization)
60+
}
61+
}
3762
}
38-
}
63+
}

src/Nino.Generator/BuiltInType/ArrayGenerator.cs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -78,21 +78,22 @@ protected override void GenerateSerializer(ITypeSymbol typeSymbol, Writer writer
7878
writer.AppendLine(" return;");
7979
writer.AppendLine(" }");
8080
writer.AppendLine();
81-
// Optimized array serialization - use span for better performance
82-
writer.AppendLine(" var span = value.AsSpan();");
83-
writer.AppendLine(" int cnt = span.Length;");
81+
// Optimized array serialization - use direct ref access to avoid bounds checks
82+
writer.AppendLine(" int cnt = value.Length;");
8483
writer.AppendLine(" writer.Write(TypeCollector.GetCollectionHeader(cnt));");
8584
writer.AppendLine();
86-
// Optimized array serialization loop
87-
writer.AppendLine(" for (int i = 0; i < cnt; i++)");
85+
writer.AppendLine($" ref var cur = ref System.Runtime.InteropServices.MemoryMarshal.GetArrayDataReference(value);");
86+
writer.AppendLine(" ref var end = ref System.Runtime.CompilerServices.Unsafe.Add(ref cur, cnt);");
87+
writer.AppendLine(" while (!System.Runtime.CompilerServices.Unsafe.AreSame(ref cur, ref end))");
8888
writer.AppendLine(" {");
8989
IfDirective(NinoTypeHelper.WeakVersionToleranceSymbol, writer,
9090
w => { w.AppendLine(" var pos = writer.Advance(4);"); });
9191

9292
writer.Append(" ");
93-
writer.AppendLine(GetSerializeString(elementType, "span[i]"));
93+
writer.AppendLine(GetSerializeString(elementType, "cur"));
9494
IfDirective(NinoTypeHelper.WeakVersionToleranceSymbol, writer,
9595
w => { w.AppendLine(" writer.PutLength(pos);"); });
96+
writer.AppendLine(" cur = ref System.Runtime.CompilerServices.Unsafe.Add(ref cur, 1);");
9697
writer.AppendLine(" }");
9798
}
9899
else
@@ -501,4 +502,4 @@ protected override void GenerateDeserializer(ITypeSymbol typeSymbol, Writer writ
501502

502503
writer.AppendLine("}");
503504
}
504-
}
505+
}

src/Nino.Generator/BuiltInType/DictionaryGenerator.cs

Lines changed: 74 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -91,30 +91,88 @@ protected override void GenerateSerializer(ITypeSymbol typeSymbol, Writer writer
9191
writer.AppendLine(" int cnt = value.Count;");
9292
writer.AppendLine(" writer.Write(TypeCollector.GetCollectionHeader(cnt));");
9393
writer.AppendLine();
94-
writer.AppendLine(" foreach (var item in value)");
95-
writer.AppendLine(" {");
9694

97-
if (kvpIsUnmanaged)
95+
var originalDef = namedType.OriginalDefinition.ToDisplayString();
96+
bool isDictionary = originalDef == "System.Collections.Generic.Dictionary<TKey, TValue>";
97+
98+
if (isDictionary)
9899
{
99-
// For unmanaged KVP, use UnsafeWrite directly (no WeakVersionTolerance needed)
100-
writer.AppendLine(" writer.UnsafeWrite(item);");
100+
var dictViewTypeName = $"TypeCollector.DictionaryView<{keyType.GetDisplayString()}, {valueType.GetDisplayString()}>";
101+
writer.Append(" ref var dict = ref System.Runtime.CompilerServices.Unsafe.As<");
102+
writer.Append(typeName);
103+
writer.Append(", ");
104+
writer.Append(dictViewTypeName);
105+
writer.AppendLine(">(ref value);");
106+
writer.AppendLine(" var entries = dict._entries;");
107+
writer.AppendLine(" if (entries == null)");
108+
writer.AppendLine(" {");
109+
writer.AppendLine(" return;");
110+
writer.AppendLine(" }");
111+
writer.AppendLine(" int count = dict._count;");
112+
writer.AppendLine(" // Iterate entries via direct ref to avoid bounds checks");
113+
writer.AppendLine(" ref var entryRef = ref System.Runtime.InteropServices.MemoryMarshal.GetArrayDataReference(entries);");
114+
writer.AppendLine(" int index = 0;");
115+
writer.AppendLine(" while ((uint)index < (uint)count)");
116+
writer.AppendLine(" {");
117+
writer.AppendLine(" ref var entry = ref System.Runtime.CompilerServices.Unsafe.Add(ref entryRef, index++);");
118+
writer.AppendLine(" if (entry.next < -1)");
119+
writer.AppendLine(" {");
120+
writer.AppendLine(" continue;");
121+
writer.AppendLine(" }");
122+
123+
if (kvpIsUnmanaged)
124+
{
125+
writer.Append(" var kvp = new System.Collections.Generic.KeyValuePair<");
126+
writer.Append(keyType.GetDisplayString());
127+
writer.Append(", ");
128+
writer.Append(valueType.GetDisplayString());
129+
writer.AppendLine(">(entry.key, entry.value);");
130+
writer.AppendLine(" writer.UnsafeWrite(kvp);");
131+
}
132+
else
133+
{
134+
// For managed KVP, serialize Key and Value separately with WeakVersionTolerance
135+
IfDirective(NinoTypeHelper.WeakVersionToleranceSymbol, writer,
136+
w => { w.AppendLine(" var pos = writer.Advance(4);"); });
137+
138+
writer.Append(" ");
139+
writer.AppendLine(GetSerializeString(keyType, "entry.key"));
140+
writer.Append(" ");
141+
writer.AppendLine(GetSerializeString(valueType, "entry.value"));
142+
143+
IfDirective(NinoTypeHelper.WeakVersionToleranceSymbol, writer,
144+
w => { w.AppendLine(" writer.PutLength(pos);"); });
145+
}
146+
147+
writer.AppendLine(" }");
101148
}
102149
else
103150
{
104-
// For managed KVP, serialize Key and Value separately with WeakVersionTolerance
105-
IfDirective(NinoTypeHelper.WeakVersionToleranceSymbol, writer,
106-
w => { w.AppendLine(" var pos = writer.Advance(4);"); });
151+
writer.AppendLine(" foreach (var item in value)");
152+
writer.AppendLine(" {");
107153

108-
writer.Append(" ");
109-
writer.AppendLine(GetSerializeString(keyType, "item.Key"));
110-
writer.Append(" ");
111-
writer.AppendLine(GetSerializeString(valueType, "item.Value"));
154+
if (kvpIsUnmanaged)
155+
{
156+
// For unmanaged KVP, use UnsafeWrite directly (no WeakVersionTolerance needed)
157+
writer.AppendLine(" writer.UnsafeWrite(item);");
158+
}
159+
else
160+
{
161+
// For managed KVP, serialize Key and Value separately with WeakVersionTolerance
162+
IfDirective(NinoTypeHelper.WeakVersionToleranceSymbol, writer,
163+
w => { w.AppendLine(" var pos = writer.Advance(4);"); });
112164

113-
IfDirective(NinoTypeHelper.WeakVersionToleranceSymbol, writer,
114-
w => { w.AppendLine(" writer.PutLength(pos);"); });
115-
}
165+
writer.Append(" ");
166+
writer.AppendLine(GetSerializeString(keyType, "item.Key"));
167+
writer.Append(" ");
168+
writer.AppendLine(GetSerializeString(valueType, "item.Value"));
116169

117-
writer.AppendLine(" }");
170+
IfDirective(NinoTypeHelper.WeakVersionToleranceSymbol, writer,
171+
w => { w.AppendLine(" writer.PutLength(pos);"); });
172+
}
173+
174+
writer.AppendLine(" }");
175+
}
118176

119177
writer.AppendLine("}");
120178
}

0 commit comments

Comments
 (0)