Skip to content

Commit e0cd8b8

Browse files
authored
Merge pull request #859 from mjbvz/string-intern-memory-improvements
Analysis String interning cleanup
2 parents 8b7931e + bfffd5c commit e0cd8b8

File tree

4 files changed

+77
-126
lines changed

4 files changed

+77
-126
lines changed

Nodejs/Product/Analysis/Analysis.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@
191191
<Compile Include="Helpers.cs" />
192192
<Compile Include="JavaScript\activationobject.cs" />
193193
<Compile Include="JavaScript\arrayliteral.cs" />
194-
<Compile Include="JavaScript\CacheDict.cs" />
194+
<Compile Include="JavaScript\StringInternPool.cs" />
195195
<Compile Include="JavaScript\EncodedSpan.cs" />
196196
<Compile Include="JavaScript\Node.cs" />
197197
<Compile Include="JavaScript\binaryop.cs" />

Nodejs/Product/Analysis/JavaScript/CacheDict.cs

Lines changed: 0 additions & 114 deletions
This file was deleted.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//*********************************************************//
2+
// Copyright (c) Microsoft. All rights reserved.
3+
//
4+
// Apache 2.0 License
5+
//
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12+
// implied. See the License for the specific language governing
13+
// permissions and limitations under the License.
14+
//
15+
//*********************************************************//
16+
17+
using System.Collections.Generic;
18+
using System.Diagnostics;
19+
20+
namespace Microsoft.NodejsTools.Parsing {
21+
/// <summary>
22+
/// Pool that caches strings for memory reuse.
23+
///
24+
/// Used over `String.Intern` since that cannot release held strings.
25+
///
26+
/// This class is not thread safe.
27+
/// </summary>
28+
internal class StringInternPool {
29+
private readonly Dictionary<string, LinkedListNode<string>> _dict = new Dictionary<string, LinkedListNode<string>>();
30+
private readonly LinkedList<string> _list = new LinkedList<string>();
31+
private readonly int _maxSize;
32+
33+
/// <summary>
34+
/// Creates a new string pool.
35+
/// </summary>
36+
/// <param name="maxSize">The maximum number of elements to store.</param>
37+
internal StringInternPool(int maxSize) {
38+
_maxSize = maxSize;
39+
}
40+
41+
/// <summary>
42+
/// Interns a string.
43+
/// Returns the reused string if the string is already in the cache, otherwise adds the string to the cache.
44+
/// </summary>
45+
internal string Intern(string str) {
46+
if (string.IsNullOrEmpty(str)) {
47+
return string.Empty;
48+
}
49+
50+
LinkedListNode<string> existingNode;
51+
if (_dict.TryGetValue(str, out existingNode) && existingNode != null) {
52+
// Move node to head of list
53+
_list.Remove(existingNode);
54+
_list.AddFirst(existingNode);
55+
return existingNode.Value;
56+
}
57+
58+
// Trim the size of the cache.
59+
while (_list.Count >= _maxSize) {
60+
var last = _list.Last;
61+
_list.RemoveLast();
62+
bool res = _dict.Remove(last.Value);
63+
Debug.Assert(res);
64+
}
65+
// Add new value to head of list
66+
var newNode = new LinkedListNode<string>(str);
67+
_list.AddFirst(newNode);
68+
_dict[newNode.Value] = newNode;
69+
return newNode.Value;
70+
}
71+
}
72+
}

Nodejs/Product/Analysis/JavaScript/jsscanner.cs

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ internal sealed class JSScanner
5050
private readonly ErrorSink _errorSink;
5151
private readonly CodeSettings _settings;
5252

53-
private static CacheDict<string, string> _internTable = new CacheDict<string, string>(4096);
53+
private static StringInternPool _internTable = new StringInternPool(4096);
5454

5555
#endregion
5656

@@ -80,10 +80,8 @@ public string StringLiteralValue {
8080
// Whether the current multiline comment is a doclet.
8181
public bool IsDoclet { get; private set; }
8282

83-
internal string Identifier
84-
{
85-
get
86-
{
83+
internal string Identifier {
84+
get {
8785
return _identifier.Length > 0
8886
? InternString(_identifier.ToString()) :
8987
CurrentTokenString();
@@ -92,12 +90,7 @@ internal string Identifier
9290

9391
private static string InternString(string value) {
9492
lock (_internTable) {
95-
string res;
96-
if (_internTable.TryGetValue(value, out res)) {
97-
return res;
98-
}
99-
_internTable.Add(value, value);
100-
return value;
93+
return _internTable.Intern(value);
10194
}
10295
}
10396

0 commit comments

Comments
 (0)