Skip to content

Commit fd40510

Browse files
authored
Reduce allocations during checksum creation. (#76524)
* Reduce allocations during checksum creation. From the csharp editing scrolling speedometer, I see about 0.3% of total allocations in the VS process occurring in Checksum.Create. A large part of these allocations are stream/writer constructions. Instead, this PR attempts to use a small pool to reuse and reset these objects.
1 parent 6b25538 commit fd40510

File tree

3 files changed

+49
-6
lines changed

3 files changed

+49
-6
lines changed

src/Workspaces/Core/Portable/Workspace/Solution/Checksum_Factory.cs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ internal readonly partial record struct Checksum
2323
private static readonly ObjectPool<XxHash128> s_incrementalHashPool =
2424
new(() => new(), size: 20);
2525

26+
// Pool of ObjectWriters to reduce allocations. The pool size is intentionally small as the writers are used for such
27+
// a short period that concurrent usage of different items from the pool is infrequent.
28+
private static readonly ObjectPool<ObjectWriter> s_objectWriterPool =
29+
new(() => new(SerializableBytes.CreateWritableStream(), leaveOpen: true, writeValidationBytes: true), size: 4);
30+
2631
public static Checksum Create(IEnumerable<string?> values)
2732
{
2833
using var pooledHash = s_incrementalHashPool.GetPooledObject();
@@ -57,15 +62,25 @@ public static Checksum Create(Stream stream)
5762

5863
public static Checksum Create<T>(T @object, Action<T, ObjectWriter> writeObject)
5964
{
60-
using var stream = SerializableBytes.CreateWritableStream();
65+
// Obtain a writer from the pool
66+
var objectWriter = s_objectWriterPool.Allocate();
6167

62-
using (var objectWriter = new ObjectWriter(stream, leaveOpen: true))
63-
{
64-
writeObject(@object, objectWriter);
65-
}
68+
// Invoke the callback to Write object into objectWriter
69+
writeObject(@object, objectWriter);
6670

71+
// Include validation bytes in the new checksum from the stream
72+
var stream = objectWriter.BaseStream;
6773
stream.Position = 0;
68-
return Create(stream);
74+
var newChecksum = Create(stream);
75+
76+
// Reset object writer back to it's initial state, including the validation bytes
77+
objectWriter.Reset();
78+
objectWriter.WriteValidationBytes();
79+
80+
// Release the writer back to the pool
81+
s_objectWriterPool.Free(objectWriter);
82+
83+
return newChecksum;
6984
}
7085

7186
public static Checksum Create(Checksum checksum1, Checksum checksum2)

src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectWriter.WriterReferenceMap.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ public readonly void Dispose()
4242
}
4343
}
4444

45+
public void Reset()
46+
{
47+
_valueToIdMap.Clear();
48+
_nextId = 0;
49+
}
50+
4551
public bool TryGetReferenceId(string value, out int referenceId)
4652
=> _valueToIdMap.TryGetValue(value, out referenceId);
4753

src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectWriter.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,28 @@ public void Dispose()
129129
public void WriteUInt16(ushort value) => _writer.Write(value);
130130
public void WriteString(string? value) => WriteStringValue(value);
131131

132+
public Stream BaseStream => _writer.BaseStream;
133+
134+
public void Reset()
135+
{
136+
_stringReferenceMap.Reset();
137+
138+
// Reset the position and length back to zero
139+
_writer.BaseStream.Position = 0;
140+
141+
if (_writer.BaseStream is SerializableBytes.ReadWriteStream pooledStream)
142+
{
143+
// ReadWriteStream.SetLength allows us to indicate to not truncate, allowing
144+
// reuse of the backing arrays.
145+
pooledStream.SetLength(0, truncate: false);
146+
}
147+
else
148+
{
149+
// Otherwise, set the new length via the standard Stream.SetLength
150+
_writer.BaseStream.SetLength(0);
151+
}
152+
}
153+
132154
/// <summary>
133155
/// Used so we can easily grab the low/high 64bits of a guid for serialization.
134156
/// </summary>

0 commit comments

Comments
 (0)