-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reduce allocations for SyntaxListPool and SyntaxListBuilder #76685
base: main
Are you sure you want to change the base?
Changes from all commits
c6fc533
9739e80
d79d3d9
7fd6dac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,7 +37,7 @@ public int Count | |
{ | ||
get | ||
{ | ||
return _builder.Count; | ||
return _builder?.Count ?? 0; | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,24 +2,43 @@ | |
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
// #define LOG | ||
|
||
using System; | ||
#if LOG | ||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using Roslyn.Utilities; | ||
#endif | ||
using Microsoft.CodeAnalysis.PooledObjects; | ||
|
||
namespace Microsoft.CodeAnalysis.Syntax.InternalSyntax | ||
{ | ||
internal class SyntaxListPool | ||
internal sealed class SyntaxListPool | ||
{ | ||
private ArrayElement<SyntaxListBuilder?>[] _freeList = new ArrayElement<SyntaxListBuilder?>[10]; | ||
private static readonly ObjectPool<SyntaxListPool> s_listPool = new ObjectPool<SyntaxListPool>(() => new SyntaxListPool()); | ||
|
||
private const int InitialFreeListSize = 16; | ||
private const int InitialBuilderCapacity = 32; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I played around with these numbers a bit and these seemed to work well for the Roslyn sln |
||
|
||
private ArrayElement<SyntaxListBuilder?>[] _freeList = new ArrayElement<SyntaxListBuilder?>[InitialFreeListSize]; | ||
private int _freeIndex; | ||
|
||
#if DEBUG | ||
#if LOG | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is useful, but only when you are really looking into this code. The overhead seems high enough that it's not worth doing unless it's being investigated, thus the removal from vanilla debug builds. |
||
private readonly List<SyntaxListBuilder> _allocated = new List<SyntaxListBuilder>(); | ||
#endif | ||
|
||
internal SyntaxListPool() | ||
private SyntaxListPool() | ||
{ | ||
} | ||
|
||
public static SyntaxListPool GetInstance() | ||
{ | ||
return s_listPool.Allocate(); | ||
} | ||
|
||
public void Free() | ||
{ | ||
s_listPool.Free(this); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One part of the change I'm having trouble understanding is that before it was always Am I missing something about the change here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think what might be confusing is there is a pool of SyntaxListPools each of which is a pool of SyntaxListBuilders. The GetInstance()/Free() methods operate on the pool of SyntaxListPools. The Allocate*/Free(SyntaxListBuilder) methods operate on the pool of SyntaxListBuilders. _freeIndex represents an index into a SyntaxListPool's pool of SyntaxListBuilder objects. When this value is zero, it indicates there isn't a SyntaxListBuilder in the SyntaxListPool, and a new one needs to be allocated. Because we are pooling these SyntaxListBuilders inside our SyntaxListPool, we don't want to reset _freeIndex when adding this SyntaxListPool back to the SyntaxListPool pool. Maybe having both the pool of SyntaxListPools and pool of SyntaxListBuilders in the same class is leading to confusion? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For me at least it is. I'm still stuck on the idea that we could free a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The very first thing I tried when I noticed these allocations was to simplify the code by using a more standard usage of ObjectPool. However, there was just too much contention across simultaneous parses to use a single ObjectPool. That convinced me that having multiple SyntaxListPool objects provides value. So, I'm fairly comfortable with pooling SyntaxListPool objects. The reason I added the ObjectPool in SyntaxListPool itself was because we used this pattern where a class encapsulates the pooling of itself in a recent compiler PR that I made (also, I wasn't sure where else would be a good place for this). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jaredpar -- Does the latest explanation clear it up a bit? If not, what change would make it more clear? |
||
} | ||
|
||
internal SyntaxListBuilder Allocate() | ||
|
@@ -33,10 +52,10 @@ internal SyntaxListBuilder Allocate() | |
} | ||
else | ||
{ | ||
item = new SyntaxListBuilder(10); | ||
item = new SyntaxListBuilder(InitialBuilderCapacity); | ||
} | ||
|
||
#if DEBUG | ||
#if LOG | ||
Debug.Assert(!_allocated.Contains(item)); | ||
_allocated.Add(item); | ||
#endif | ||
|
@@ -63,16 +82,26 @@ internal void Free(SyntaxListBuilder? item) | |
if (item is null) | ||
return; | ||
|
||
item.Clear(); | ||
#if LOG | ||
Debug.Assert(_allocated.Contains(item)); | ||
|
||
_allocated.Remove(item); | ||
#endif | ||
|
||
// Don't add the builder back to _freelist if the builder has grown too large. | ||
if (item.Capacity > InitialBuilderCapacity * 2) | ||
return; | ||
|
||
if (_freeIndex >= _freeList.Length) | ||
{ | ||
// Don't add the builder back to _freelist if the cache has grown too large. | ||
if (_freeIndex >= InitialFreeListSize * 2) | ||
return; | ||
|
||
this.Grow(); | ||
} | ||
#if DEBUG | ||
Debug.Assert(_allocated.Contains(item)); | ||
|
||
_allocated.Remove(item); | ||
#endif | ||
item.Clear(); | ||
_freeList[_freeIndex].Value = item; | ||
_freeIndex++; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -203,7 +203,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax | |
End Property | ||
|
||
Friend Sub FreeStatements() | ||
_parser._pool.Free(_statements) | ||
If Not _statements.IsNull Then | ||
_parser._pool.Free(_statements) | ||
_statements = Nothing | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
End If | ||
End Sub | ||
|
||
Friend Function Body() As CodeAnalysis.Syntax.InternalSyntax.SyntaxList(Of StatementSyntax) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,7 +33,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax | |
Private _evaluatingConditionCompilationExpression As Boolean | ||
Private ReadOnly _scanner As Scanner | ||
Private ReadOnly _cancellationToken As CancellationToken | ||
Friend ReadOnly _pool As New SyntaxListPool | ||
Friend ReadOnly _pool As SyntaxListPool = SyntaxListPool.GetInstance() | ||
Private ReadOnly _syntaxFactory As ContextAwareSyntaxFactory | ||
|
||
' When parser owns the scanner, it is responsible for disposing it | ||
|
@@ -68,6 +68,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax | |
If _disposeScanner Then | ||
Me._scanner.Dispose() | ||
End If | ||
|
||
' Ensure the context's statements have been freed | ||
Context?.FreeStatements() | ||
|
||
_pool.Free() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is why I had the earlier PR to ensure that the VB parser's dispose is always created |
||
End Sub | ||
|
||
Friend ReadOnly Property IsScript As Boolean | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trying to understand if the comment here is still valid. There is no resetting in the pool and it's a ctor call all the time. But the comment had me digging into the pool to see if we now needed to reset state since there was a pool and not always a
new
.@cston, @CyrusNajmabadi