Skip to content

Commit aa0175a

Browse files
committed
Merge pull request #1612 from pdelvo/pools
2 parents 2559298 + 03e81db commit aa0175a

12 files changed

Lines changed: 603 additions & 17 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/FileHeaderCodeFixProvider.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ namespace StyleCop.Analyzers.DocumentationRules
1111
using System.Threading;
1212
using System.Threading.Tasks;
1313
using System.Xml.Linq;
14+
using Helpers.ObjectPools;
1415
using Microsoft.CodeAnalysis;
1516
using Microsoft.CodeAnalysis.CodeActions;
1617
using Microsoft.CodeAnalysis.CodeFixes;
@@ -135,7 +136,7 @@ private static SyntaxNode ReplaceWellFormedMultiLineCommentHeader(Document docum
135136

136137
// Pad line that used to be next to a /*
137138
triviaStringParts[0] = commentIndentation + interlinePadding + " " + triviaStringParts[0];
138-
StringBuilder sb = new StringBuilder();
139+
StringBuilder sb = StringBuilderPool.Allocate();
139140
var copyrightText = commentIndentation + interlinePadding + " " +
140141
GetCopyrightText(commentIndentation + interlinePadding, settings.DocumentationRules.CopyrightText, newLineText);
141142
var newHeader = WrapInXmlComment(commentIndentation + interlinePadding, copyrightText, document.Name, settings, newLineText);
@@ -196,8 +197,7 @@ private static SyntaxNode ReplaceWellFormedMultiLineCommentHeader(Document docum
196197
sb.Append((i == 0 ? string.Empty : newLineText) + lines[i].TrimEnd());
197198
}
198199

199-
var newTrivia = SyntaxFactory.SyntaxTrivia(SyntaxKind.MultiLineCommentTrivia, sb.ToString());
200-
200+
var newTrivia = SyntaxFactory.SyntaxTrivia(SyntaxKind.MultiLineCommentTrivia, StringBuilderPool.ReturnAndFree(sb));
201201
return root.WithLeadingTrivia(trivia.Replace(commentTrivia, newTrivia));
202202
}
203203

StyleCop.Analyzers/StyleCop.Analyzers/Helpers/FileHeaderHelpers.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ namespace StyleCop.Analyzers.Helpers
1010
using System.Xml.Linq;
1111
using Microsoft.CodeAnalysis;
1212
using Microsoft.CodeAnalysis.CSharp;
13+
using ObjectPools;
1314

1415
/// <summary>
1516
/// Helper class used for working with file headers
@@ -30,7 +31,7 @@ internal static FileHeader ParseFileHeader(SyntaxNode root)
3031
return FileHeader.MissingFileHeader;
3132
}
3233

33-
var sb = new StringBuilder();
34+
var sb = StringBuilderPool.Allocate();
3435
var endOfLineCount = 0;
3536
var done = false;
3637
var fileHeaderStart = int.MaxValue;
@@ -92,6 +93,7 @@ internal static FileHeader ParseFileHeader(SyntaxNode root)
9293

9394
if (fileHeaderStart > fileHeaderEnd)
9495
{
96+
StringBuilderPool.Free(sb);
9597
return FileHeader.MissingFileHeader;
9698
}
9799

@@ -102,7 +104,7 @@ internal static FileHeader ParseFileHeader(SyntaxNode root)
102104
sb.Remove(sb.Length - eolLength, eolLength);
103105
}
104106

105-
return new FileHeader(sb.ToString(), fileHeaderStart, fileHeaderEnd);
107+
return new FileHeader(StringBuilderPool.ReturnAndFree(sb), fileHeaderStart, fileHeaderEnd);
106108
}
107109

108110
/// <summary>
@@ -162,7 +164,7 @@ internal static XmlFileHeader ParseXmlFileHeader(SyntaxNode root)
162164

163165
private static string ProcessSingleLineCommentsHeader(SyntaxTriviaList triviaList, int startIndex, out int fileHeaderStart, out int fileHeaderEnd)
164166
{
165-
var sb = new StringBuilder();
167+
var sb = StringBuilderPool.Allocate();
166168
var endOfLineCount = 0;
167169
var done = false;
168170

@@ -212,12 +214,12 @@ private static string ProcessSingleLineCommentsHeader(SyntaxTriviaList triviaLis
212214
}
213215

214216
sb.AppendLine("</root>");
215-
return sb.ToString();
217+
return StringBuilderPool.ReturnAndFree(sb);
216218
}
217219

218220
private static string ProcessMultiLineCommentsHeader(SyntaxTrivia multiLineComment, out int fileHeaderStart, out int fileHeaderEnd)
219221
{
220-
var sb = new StringBuilder();
222+
var sb = StringBuilderPool.Allocate();
221223

222224
// wrap the XML from the file header in a single root element to make XML parsing work.
223225
sb.AppendLine("<root>");
@@ -237,7 +239,7 @@ private static string ProcessMultiLineCommentsHeader(SyntaxTrivia multiLineComme
237239
}
238240

239241
sb.AppendLine("</root>");
240-
return sb.ToString();
242+
return StringBuilderPool.ReturnAndFree(sb);
241243
}
242244
}
243245
}

StyleCop.Analyzers/StyleCop.Analyzers/Helpers/NameSyntaxHelpers.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ namespace StyleCop.Analyzers.Helpers
77
using Microsoft.CodeAnalysis;
88
using Microsoft.CodeAnalysis.CSharp;
99
using Microsoft.CodeAnalysis.CSharp.Syntax;
10+
using ObjectPools;
1011

1112
/// <summary>
1213
/// Class containing the extension methods for the <see cref="NameSyntax"/> class.
@@ -36,11 +37,11 @@ internal static string ToNormalizedString(this NameSyntax nameSyntax)
3637
/// <returns>The name contained in the <see cref="NameSyntax"/>, with its alias removed (if any).</returns>
3738
internal static string ToUnaliasedString(this NameSyntax nameSyntax)
3839
{
39-
var sb = new StringBuilder();
40+
var sb = StringBuilderPool.Allocate();
4041

4142
BuildName(nameSyntax, sb, false);
4243

43-
return sb.ToString();
44+
return StringBuilderPool.ReturnAndFree(sb);
4445
}
4546

4647
private static void BuildName(NameSyntax nameSyntax, StringBuilder builder, bool includeAlias)
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
4+
namespace StyleCop.Analyzers.Helpers.ObjectPools
5+
{
6+
// This code was copied from the Roslyn code base (and slightly modified)
7+
using System;
8+
using System.Diagnostics;
9+
using System.Threading;
10+
11+
/// <summary>
12+
/// Generic implementation of object pooling pattern with predefined pool size limit. The main
13+
/// purpose is that limited number of frequently used objects can be kept in the pool for
14+
/// further recycling.
15+
///
16+
/// Notes:
17+
/// 1) it is not the goal to keep all returned objects. Pool is not meant for storage. If there
18+
/// is no space in the pool, extra returned objects will be dropped.
19+
///
20+
/// 2) it is implied that if object was obtained from a pool, the caller will return it back in
21+
/// a relatively short time. Keeping checked out objects for long durations is ok, but
22+
/// reduces usefulness of pooling. Just new up your own.
23+
///
24+
/// Not returning objects to the pool in not detrimental to the pool's work, but is a bad practice.
25+
/// Rationale:
26+
/// If there is no intent for reusing the object, do not use pool - just use "new".
27+
/// </summary>
28+
/// <typeparam name="T">The type of the objects in this cache.</typeparam>
29+
internal class ObjectPool<T>
30+
where T : class
31+
{
32+
private readonly Element[] items;
33+
34+
// factory is stored for the lifetime of the pool. We will call this only when pool needs to
35+
// expand. compared to "new T()", Func gives more flexibility to implementers and faster
36+
// than "new T()".
37+
private readonly Func<T> factory;
38+
39+
// Storage for the pool objects. The first item is stored in a dedicated field because we
40+
// expect to be able to satisfy most requests from it.
41+
private T firstItem;
42+
43+
internal ObjectPool(Func<T> factory)
44+
: this(factory, Environment.ProcessorCount * 2)
45+
{
46+
}
47+
48+
internal ObjectPool(Func<T> factory, int size)
49+
{
50+
Debug.Assert(size >= 1, "The object pool can't be empty");
51+
this.factory = factory;
52+
this.items = new Element[size - 1];
53+
}
54+
55+
/// <summary>
56+
/// Produces an instance.
57+
/// </summary>
58+
/// <remarks>
59+
/// Search strategy is a simple linear probing which is chosen for it cache-friendliness.
60+
/// Note that Free will try to store recycled objects close to the start thus statistically
61+
/// reducing how far we will typically search.
62+
/// </remarks>
63+
/// <returns>A (poosibly) cached instance of type <typeparamref name="T"/>.</returns>
64+
internal T Allocate()
65+
{
66+
// PERF: Examine the first element. If that fails, AllocateSlow will look at the remaining elements.
67+
// Note that the initial read is optimistically not synchronized. That is intentional.
68+
// We will interlock only when we have a candidate. in a worst case we may miss some
69+
// recently returned objects. Not a big deal.
70+
T inst = this.firstItem;
71+
if (inst == null || inst != Interlocked.CompareExchange(ref this.firstItem, null, inst))
72+
{
73+
inst = this.AllocateSlow();
74+
}
75+
76+
return inst;
77+
}
78+
79+
/// <summary>
80+
/// Returns objects to the pool.
81+
/// </summary>
82+
/// <remarks>
83+
/// Search strategy is a simple linear probing which is chosen for it cache-friendliness.
84+
/// Note that Free will try to store recycled objects close to the start thus statistically
85+
/// reducing how far we will typically search in Allocate.
86+
/// </remarks>
87+
/// <param name="obj">The object to free.</param>
88+
internal void Free(T obj)
89+
{
90+
if (this.firstItem == null)
91+
{
92+
// Intentionally not using interlocked here.
93+
// In a worst case scenario two objects may be stored into same slot.
94+
// It is very unlikely to happen and will only mean that one of the objects will get collected.
95+
this.firstItem = obj;
96+
}
97+
else
98+
{
99+
this.FreeSlow(obj);
100+
}
101+
}
102+
103+
private T CreateInstance()
104+
{
105+
var inst = this.factory();
106+
return inst;
107+
}
108+
109+
private T AllocateSlow()
110+
{
111+
var items = this.items;
112+
113+
for (int i = 0; i < items.Length; i++)
114+
{
115+
// Note that the initial read is optimistically not synchronized. That is intentional.
116+
// We will interlock only when we have a candidate. in a worst case we may miss some
117+
// recently returned objects. Not a big deal.
118+
T inst = items[i].Value;
119+
if (inst != null)
120+
{
121+
if (inst == Interlocked.CompareExchange(ref items[i].Value, null, inst))
122+
{
123+
return inst;
124+
}
125+
}
126+
}
127+
128+
return this.CreateInstance();
129+
}
130+
131+
private void FreeSlow(T obj)
132+
{
133+
var items = this.items;
134+
for (int i = 0; i < items.Length; i++)
135+
{
136+
if (items[i].Value == null)
137+
{
138+
// Intentionally not using interlocked here.
139+
// In a worst case scenario two objects may be stored into same slot.
140+
// It is very unlikely to happen and will only mean that one of the objects will get collected.
141+
items[i].Value = obj;
142+
break;
143+
}
144+
}
145+
}
146+
147+
[DebuggerDisplay("{Value,nq}")]
148+
private struct Element
149+
{
150+
internal T Value;
151+
}
152+
}
153+
}

0 commit comments

Comments
 (0)