Skip to content

Commit 069b409

Browse files
committed
Fix perf issue in LineTrackingStringbuffer.ScanLines.
I noticed several hundred ms spent in this method from a customer profile. Primarilly, the method was doing a linear scan of all lines trying to find one that contained the requested position. I changed this to a binary search, but kept/improved the optimization around checking next/previous lines before instigating the search. Note, there was also a bug where the old code did: else if (absoluteIndex > _currentLine.Index && _currentLine.Index + 1 < _lines.Count) but it should have been coparing absoluteIndex with _currentLine.Start \n\nCommit migrated from dotnet/razor@32a0f28
1 parent 7eca4ab commit 069b409

File tree

1 file changed

+57
-18
lines changed

1 file changed

+57
-18
lines changed

src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/LineTrackingStringBuffer.cs

Lines changed: 57 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -82,44 +82,83 @@ private TextLine FindLine(int absoluteIndex)
8282
{
8383
TextLine selected = null;
8484

85-
if (_currentLine != null)
85+
if (_currentLine == null)
8686
{
87-
if (_currentLine.Contains(absoluteIndex))
87+
// Scan from line 0
88+
selected = ScanLines(absoluteIndex, 0, _lines.Count);
89+
}
90+
else if (absoluteIndex >= _currentLine.End)
91+
{
92+
if (_currentLine.Index + 1 < _lines.Count)
8893
{
89-
// This index is on the last read line
90-
selected = _currentLine;
94+
// This index is after the last read line
95+
var nextLine = _lines[_currentLine.Index + 1];
96+
97+
// Optimization to not search if it's the common case where the line after _currentLine is being requested.
98+
if (nextLine.Contains(absoluteIndex))
99+
{
100+
selected = nextLine;
101+
}
102+
else
103+
{
104+
selected = ScanLines(absoluteIndex, _currentLine.Index, _lines.Count);
105+
}
91106
}
92-
else if (absoluteIndex > _currentLine.Index && _currentLine.Index + 1 < _lines.Count)
107+
}
108+
else if (absoluteIndex < _currentLine.Start)
109+
{
110+
if (_currentLine.Index > 0)
93111
{
94-
// This index is ahead of the last read line
95-
selected = ScanLines(absoluteIndex, _currentLine.Index);
112+
// This index is before the last read line
113+
var prevLine = _lines[_currentLine.Index - 1];
114+
115+
// Optimization to not search if it's the common case where the line before _currentLine is being requested.
116+
if (prevLine.Contains(absoluteIndex))
117+
{
118+
selected = prevLine;
119+
}
120+
else
121+
{
122+
selected = ScanLines(absoluteIndex, 0, _currentLine.Index);
123+
}
96124
}
97125
}
98-
99-
// Have we found a line yet?
100-
if (selected == null)
126+
else
101127
{
102-
// Scan from line 0
103-
selected = ScanLines(absoluteIndex, 0);
128+
// This index is on the last read line
129+
selected = _currentLine;
104130
}
105131

106132
Debug.Assert(selected == null || selected.Contains(absoluteIndex));
107133
_currentLine = selected;
108134
return selected;
109135
}
110136

111-
private TextLine ScanLines(int absoluteIndex, int startPos)
137+
private TextLine ScanLines(int absoluteIndex, int startLineIndex, int endLineIndex)
112138
{
113-
for (int i = 0; i < _lines.Count; i++)
139+
// binary search for the line containing absoluteIndex
140+
var lowIndex = startLineIndex;
141+
var highIndex = _lines.Count;
142+
143+
while (lowIndex != highIndex)
114144
{
115-
var idx = (i + startPos) % _lines.Count;
116-
Debug.Assert(idx >= 0 && idx < _lines.Count);
145+
var midIndex = (lowIndex + highIndex) / 2;
146+
var midLine = _lines[midIndex];
117147

118-
if (_lines[idx].Contains(absoluteIndex))
148+
if (absoluteIndex >= midLine.End)
149+
{
150+
lowIndex = midIndex + 1;
151+
}
152+
else if (absoluteIndex < midLine.Start)
119153
{
120-
return _lines[idx];
154+
highIndex = midIndex;
155+
}
156+
else
157+
{
158+
return midLine;
121159
}
122160
}
161+
123162
return null;
124163
}
125164

0 commit comments

Comments
 (0)