From 3987da64db1dc0e2834d0489940a010398b22693 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 9 Mar 2016 16:29:49 -0800 Subject: [PATCH 1/3] Improve performance of repl window output Currently, printing large structures to the repl window can take a very long time. I believe the primary cause is we end up appending output almost one character at a time, repeating the same operations over and over again. This change updates ReplWindow to take a set of outputs to be appended instead of a single output. This allows moving some of the computations out of the inner loops and reusing previously computed values. --- .../Product/ReplWindow/Repl/OutputBuffer.cs | 12 +-- Common/Product/ReplWindow/Repl/ReplWindow.cs | 76 +++++++++++-------- 2 files changed, 48 insertions(+), 40 deletions(-) diff --git a/Common/Product/ReplWindow/Repl/OutputBuffer.cs b/Common/Product/ReplWindow/Repl/OutputBuffer.cs index 9f6a37d00..236579b4b 100644 --- a/Common/Product/ReplWindow/Repl/OutputBuffer.cs +++ b/Common/Product/ReplWindow/Repl/OutputBuffer.cs @@ -241,11 +241,7 @@ public void Flush() { } if (entries.Length > 0) { - for (int i = 0; i < entries.Length; i++) { - var entry = entries[i]; - - _window.AppendOutput(entry.Properties.Color, entry.Buffer.ToString(), i == entries.Length - 1); - } + _window.AppendOutput(entries); _window.TextView.Caret.EnsureVisible(); } } @@ -254,7 +250,7 @@ public void Dispose() { _timer.IsEnabled = false; } - struct OutputEntry { + public struct OutputEntry { public readonly StringBuilder Buffer; public readonly OutputEntryProperties Properties; @@ -267,7 +263,7 @@ public OutputEntry(OutputEntryProperties properties) { /// /// Properties for a run of text - includes destination (stdout/stderr) and color /// - struct OutputEntryProperties { + public struct OutputEntryProperties { public readonly OutputEntryKind Kind; public readonly ConsoleColor Color; @@ -298,7 +294,7 @@ public override int GetHashCode() { } } - enum OutputEntryKind { + public enum OutputEntryKind { StdOut, StdErr } diff --git a/Common/Product/ReplWindow/Repl/ReplWindow.cs b/Common/Product/ReplWindow/Repl/ReplWindow.cs index 757287b2c..4541140bd 100644 --- a/Common/Product/ReplWindow/Repl/ReplWindow.cs +++ b/Common/Product/ReplWindow/Repl/ReplWindow.cs @@ -2110,14 +2110,18 @@ private void Write(object text, bool error = false) { /// /// Appends text to the output buffer and updates projection buffer to include it. /// - internal void AppendOutput(ConsoleColor color, string text, bool lastOutput) { + internal void AppendOutput(OutputBuffer.OutputEntry[] entries) { + if (!entries.Any()) + return; + + var spans = new List>(entries.Length); + int oldBufferLength = _outputBuffer.CurrentSnapshot.Length; int oldLineCount = _outputBuffer.CurrentSnapshot.LineCount; RemoveProtection(_outputBuffer, _outputProtection); // append the text to output buffer and make sure it ends with a line break: - int newOutputLength = text.Length; using (var edit = _outputBuffer.CreateEdit()) { if (_addedLineBreakOnLastOutput) { // appending additional output, remove the line break we previously injected @@ -2126,18 +2130,28 @@ internal void AppendOutput(ConsoleColor color, string text, bool lastOutput) { Debug.Assert(_outputBuffer.CurrentSnapshot.GetText(deleteSpan) == lineBreak); edit.Delete(deleteSpan); oldBufferLength -= lineBreak.Length; + _addedLineBreakOnLastOutput = false; } - edit.Insert(oldBufferLength, text); - if (lastOutput && !_readingStdIn && !EndsWithLineBreak(text)) { + var text = String.Empty; + var startPosition = oldBufferLength; + foreach (var entry in entries) { + text = entry.Buffer.ToString(); + edit.Insert(oldBufferLength, text); + + var span = new Span(startPosition, text.Length); + spans.Add(new KeyValuePair(span, entry.Properties)); + startPosition += text.Length; + } + if (!_readingStdIn && !EndsWithLineBreak(text)) { var lineBreak = GetLineBreak(); - edit.Insert(oldBufferLength, lineBreak); - newOutputLength += lineBreak.Length; + edit.Insert(startPosition, lineBreak); _addedLineBreakOnLastOutput = true; - } else { - _addedLineBreakOnLastOutput = false; + // Adust last span. + var last = spans.Last(); + var newLastSpan = new Span(last.Key.Start, last.Key.Length + lineBreak.Length); + spans[spans.Count() - 1] = new KeyValuePair(newLastSpan, last.Value); } - edit.Apply(); } @@ -2145,28 +2159,30 @@ internal void AppendOutput(ConsoleColor color, string text, bool lastOutput) { int newLineCount = _outputBuffer.CurrentSnapshot.LineCount; - var span = new Span(oldBufferLength, newOutputLength); - var trackingSpan = new CustomTrackingSpan( - _outputBuffer.CurrentSnapshot, - span, - PointTrackingMode.Negative, - PointTrackingMode.Negative - ); - - var outputSpan = new ReplSpan(trackingSpan, ReplSpanKind.Output); - _outputColors.Add(new ColoredSpan(span, color)); - - bool appended = false; - + int insertBeforePrompt = -1; if (!_isRunning) { - // insert output span immediately before the last primary span - int lastPrimaryPrompt, lastPrompt; + IndexOfLastPrompt(out lastPrimaryPrompt, out lastPrompt); // If the last prompt is STDIN prompt insert output before it, otherwise before the primary prompt: - int insertBeforePrompt = (lastPrompt != -1 && _projectionSpans[lastPrompt].Kind == ReplSpanKind.StandardInputPrompt) ? lastPrompt : lastPrimaryPrompt; + insertBeforePrompt = (lastPrompt != -1 && _projectionSpans[lastPrompt].Kind == ReplSpanKind.StandardInputPrompt) ? lastPrompt : lastPrimaryPrompt; + } + + foreach (var entry in spans) { + var span = entry.Key; + var props = entry.Value; + var trackingSpan = new CustomTrackingSpan( + _outputBuffer.CurrentSnapshot, + span, + PointTrackingMode.Negative, + PointTrackingMode.Negative); + + var outputSpan = new ReplSpan(trackingSpan, ReplSpanKind.Output); + _outputColors.Add(new ColoredSpan(span, props.Color)); + + // insert output span immediately before the last primary span if (insertBeforePrompt >= 0) { if (oldLineCount != newLineCount) { int delta = newLineCount - oldLineCount; @@ -2176,19 +2192,15 @@ internal void AppendOutput(ConsoleColor color, string text, bool lastOutput) { var lastMaplet = _promptLineMapping.Last(); _promptLineMapping[_promptLineMapping.Count - 1] = new KeyValuePair( lastMaplet.Key + delta, - lastMaplet.Value + 1 - ); + lastMaplet.Value + 1); } // Projection buffer change might trigger events that access prompt line mapping, so do it last: InsertProjectionSpan(insertBeforePrompt, outputSpan); - appended = true; + } else { + AppendProjectionSpan(outputSpan); } } - - if (!appended) { - AppendProjectionSpan(outputSpan); - } } /// From d38382b5101929d8bb428f482df992837ad5b62c Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 9 Mar 2016 16:45:58 -0800 Subject: [PATCH 2/3] Fixed printing of syntax errors --- Common/Product/ReplWindow/Repl/ReplWindow.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Common/Product/ReplWindow/Repl/ReplWindow.cs b/Common/Product/ReplWindow/Repl/ReplWindow.cs index 4541140bd..2d4f5f039 100644 --- a/Common/Product/ReplWindow/Repl/ReplWindow.cs +++ b/Common/Product/ReplWindow/Repl/ReplWindow.cs @@ -2145,7 +2145,7 @@ internal void AppendOutput(OutputBuffer.OutputEntry[] entries) { } if (!_readingStdIn && !EndsWithLineBreak(text)) { var lineBreak = GetLineBreak(); - edit.Insert(startPosition, lineBreak); + edit.Insert(oldBufferLength, lineBreak); _addedLineBreakOnLastOutput = true; // Adust last span. var last = spans.Last(); @@ -2158,7 +2158,6 @@ internal void AppendOutput(OutputBuffer.OutputEntry[] entries) { ApplyProtection(_outputBuffer, _outputProtection); int newLineCount = _outputBuffer.CurrentSnapshot.LineCount; - int insertBeforePrompt = -1; if (!_isRunning) { int lastPrimaryPrompt, lastPrompt; From ea50763d6cf0845a76b1d168bd59e7d75c8547cb Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 24 May 2016 14:25:32 -0700 Subject: [PATCH 3/3] Address comments --- Common/Product/ReplWindow/Repl/OutputBuffer.cs | 4 ++-- Common/Product/ReplWindow/Repl/ReplWindow.cs | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Common/Product/ReplWindow/Repl/OutputBuffer.cs b/Common/Product/ReplWindow/Repl/OutputBuffer.cs index 236579b4b..eb8bdb796 100644 --- a/Common/Product/ReplWindow/Repl/OutputBuffer.cs +++ b/Common/Product/ReplWindow/Repl/OutputBuffer.cs @@ -263,7 +263,7 @@ public OutputEntry(OutputEntryProperties properties) { /// /// Properties for a run of text - includes destination (stdout/stderr) and color /// - public struct OutputEntryProperties { + internal struct OutputEntryProperties { public readonly OutputEntryKind Kind; public readonly ConsoleColor Color; @@ -294,7 +294,7 @@ public override int GetHashCode() { } } - public enum OutputEntryKind { + internal enum OutputEntryKind { StdOut, StdErr } diff --git a/Common/Product/ReplWindow/Repl/ReplWindow.cs b/Common/Product/ReplWindow/Repl/ReplWindow.cs index 2d4f5f039..c649b5de4 100644 --- a/Common/Product/ReplWindow/Repl/ReplWindow.cs +++ b/Common/Product/ReplWindow/Repl/ReplWindow.cs @@ -2111,16 +2111,17 @@ private void Write(object text, bool error = false) { /// Appends text to the output buffer and updates projection buffer to include it. /// internal void AppendOutput(OutputBuffer.OutputEntry[] entries) { - if (!entries.Any()) + if (!entries.Any()) { return; - - var spans = new List>(entries.Length); + } int oldBufferLength = _outputBuffer.CurrentSnapshot.Length; int oldLineCount = _outputBuffer.CurrentSnapshot.LineCount; RemoveProtection(_outputBuffer, _outputProtection); + var spans = new List>(entries.Length); + // append the text to output buffer and make sure it ends with a line break: using (var edit = _outputBuffer.CreateEdit()) { if (_addedLineBreakOnLastOutput) { @@ -2147,7 +2148,7 @@ internal void AppendOutput(OutputBuffer.OutputEntry[] entries) { var lineBreak = GetLineBreak(); edit.Insert(oldBufferLength, lineBreak); _addedLineBreakOnLastOutput = true; - // Adust last span. + // Adust last span to include line break var last = spans.Last(); var newLastSpan = new Span(last.Key.Start, last.Key.Length + lineBreak.Length); spans[spans.Count() - 1] = new KeyValuePair(newLastSpan, last.Value);