From c05f1ec3f34d0565ac83e41dec938465c78652fc Mon Sep 17 00:00:00 2001 From: Ilya Date: Wed, 18 Apr 2018 19:15:36 +0500 Subject: [PATCH] Reduce allocations in TableWriter (#6648) * [Feature] Reduce allocations in TableWriter * Revert iterator ReadOnlySpan is not supported in iterators * Use columnsThresHold with stackalloc --- .../common/BaseOutputtingCommand.cs | 18 ++++++------ .../common/ColumnWidthManager.cs | 12 ++++---- .../FormatAndOutput/common/ComplexWriter.cs | 6 +++- .../FormatAndOutput/common/TableWriter.cs | 28 +++++++++++-------- 4 files changed, 38 insertions(+), 26 deletions(-) diff --git a/src/System.Management.Automation/FormatAndOutput/common/BaseOutputtingCommand.cs b/src/System.Management.Automation/FormatAndOutput/common/BaseOutputtingCommand.cs index 9399ee5fc2..365270b8c1 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/BaseOutputtingCommand.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/BaseOutputtingCommand.cs @@ -744,8 +744,8 @@ namespace Microsoft.PowerShell.Commands.Internal.Format } /// - /// In cases like implicit remoting, there is no console so reading the console width results in an exception. - /// Instead of handling exception every time we cache this value to increase performance. + /// In cases like implicit remoting, there is no console so reading the console width results in an exception. + /// Instead of handling exception every time we cache this value to increase performance. /// static private bool _noConsole = false; @@ -940,6 +940,8 @@ namespace Microsoft.PowerShell.Commands.Internal.Format { TableFormattingHint tableHint = this.InnerCommand.RetrieveFormattingHint() as TableFormattingHint; int[] columnWidthsHint = null; + // We expect that console width is less then 120. + const int columnsThresHold = 120; if (tableHint != null) { @@ -955,10 +957,10 @@ namespace Microsoft.PowerShell.Commands.Internal.Format } // create arrays for widths and alignment - int[] columnWidths = new int[columns]; - int[] alignment = new int[columns]; - int k = 0; + Span columnWidths = columns < columnsThresHold ? stackalloc int[columns] : new int[columns]; + Span alignment = columns < columnsThresHold ? stackalloc int[columns] : new int[columns]; + int k = 0; foreach (TableColumnInfo tci in this.CurrentTableHeaderInfo.tableColumnInfoList) { columnWidths[k] = (columnWidthsHint != null) ? columnWidthsHint[k] : tci.width; @@ -1006,7 +1008,7 @@ namespace Microsoft.PowerShell.Commands.Internal.Format // need to make sure we have matching counts: the header count will have to prevail string[] values = new string[headerColumns]; - int[] alignment = new int[headerColumns]; + Span alignment = stackalloc int[headerColumns]; int fieldCount = tre.formatPropertyFieldList.Count; @@ -1168,8 +1170,8 @@ namespace Microsoft.PowerShell.Commands.Internal.Format _buffer = new StringValuesBuffer(itemsPerRow); // initialize the writer - int[] columnWidths = new int[itemsPerRow]; - int[] alignment = new int[itemsPerRow]; + Span columnWidths = stackalloc int[itemsPerRow]; + Span alignment = stackalloc int[itemsPerRow]; for (int k = 0; k < itemsPerRow; k++) { diff --git a/src/System.Management.Automation/FormatAndOutput/common/ColumnWidthManager.cs b/src/System.Management.Automation/FormatAndOutput/common/ColumnWidthManager.cs index 38fc778f58..a059f8c9d3 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/ColumnWidthManager.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/ColumnWidthManager.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; + namespace Microsoft.PowerShell.Commands.Internal.Format { /// @@ -29,7 +31,7 @@ namespace Microsoft.PowerShell.Commands.Internal.Format /// with column elimination, starting from the right most column /// /// array of column widths to appropriately size - internal void CalculateColumnWidths(int[] columnWidths) + internal void CalculateColumnWidths(Span columnWidths) { if (AssignColumnWidths(columnWidths)) { @@ -47,7 +49,7 @@ namespace Microsoft.PowerShell.Commands.Internal.Format /// /// columns to process /// true if there was a fit, false if there is need for trimming - private bool AssignColumnWidths(int[] columnWidths) + private bool AssignColumnWidths(Span columnWidths) { // run a quick check to see if all the columns have a specified width, // if so, we are done @@ -127,7 +129,7 @@ namespace Microsoft.PowerShell.Commands.Internal.Format /// trim columns if the total column width is too much for the screen. /// /// column widths to trim - private void TrimToFit(int[] columnWidths) + private void TrimToFit(Span columnWidths) { while (true) { @@ -166,7 +168,7 @@ namespace Microsoft.PowerShell.Commands.Internal.Format /// /// column widths array /// - private int CurrentTableWidth(int[] columnWidths) + private int CurrentTableWidth(Span columnWidths) { int sum = 0; int visibleColumns = 0; @@ -188,7 +190,7 @@ namespace Microsoft.PowerShell.Commands.Internal.Format /// /// column widths array /// index of the last visible column, -1 if none - private static int GetLastVisibleColumn(int[] columnWidths) + private static int GetLastVisibleColumn(Span columnWidths) { for (int k = 0; k < columnWidths.Length; k++) { diff --git a/src/System.Management.Automation/FormatAndOutput/common/ComplexWriter.cs b/src/System.Management.Automation/FormatAndOutput/common/ComplexWriter.cs index ba922a7c31..17b06f7f65 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/ComplexWriter.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/ComplexWriter.cs @@ -700,12 +700,16 @@ namespace Microsoft.PowerShell.Commands.Internal.Format internal static string TruncateAtNewLine(string s) { if (string.IsNullOrEmpty(s)) - return s; + { + return ""; + } int lineBreak = s.IndexOfAny(s_lineBreakChars); if (lineBreak < 0) + { return s; + } return s.Substring(0, lineBreak) + PSObjectHelper.ellipses; } diff --git a/src/System.Management.Automation/FormatAndOutput/common/TableWriter.cs b/src/System.Management.Automation/FormatAndOutput/common/TableWriter.cs index a6cbf31d0f..07f87dc105 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/TableWriter.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/TableWriter.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; +using System.Collections.Generic; using System.Collections.Specialized; using System.Management.Automation.Internal; using System.Text; @@ -75,7 +77,7 @@ namespace Microsoft.PowerShell.Commands.Internal.Format /// array of specified column widths /// array of alignment flags /// if true, suppress header printing - internal void Initialize(int leftMarginIndent, int screenColumns, int[] columnWidths, int[] alignment, bool suppressHeader) + internal void Initialize(int leftMarginIndent, int screenColumns, Span columnWidths, ReadOnlySpan alignment, bool suppressHeader) { //Console.WriteLine(" 1 2 3 4 5 6 7"); //Console.WriteLine("01234567890123456789012345678901234567890123456789012345678901234567890123456789"); @@ -161,7 +163,7 @@ namespace Microsoft.PowerShell.Commands.Internal.Format // generate an array of "--" as header markers below // the column header labels string[] breakLine = new string[values.Length]; - for (int k = 0; k < _si.columnInfo.Length; k++) + for (int k = 0; k < breakLine.Length; k++) { // the column can be hidden if (_si.columnInfo[k].width <= 0) @@ -185,25 +187,25 @@ namespace Microsoft.PowerShell.Commands.Internal.Format GenerateRow(breakLine, lo, false, null, lo.DisplayCells); } - internal void GenerateRow(string[] values, LineOutput lo, bool multiLine, int[] alignment, DisplayCells dc) + internal void GenerateRow(string[] values, LineOutput lo, bool multiLine, ReadOnlySpan alignment, DisplayCells dc) { if (_disabled) return; // build the current row alignment settings int cols = _si.columnInfo.Length; - int[] currentAlignment = new int[cols]; + Span currentAlignment = stackalloc int[cols]; if (alignment == null) { - for (int i = 0; i < cols; i++) + for (int i = 0; i < currentAlignment.Length; i++) { currentAlignment[i] = _si.columnInfo[i].alignment; } } else { - for (int i = 0; i < cols; i++) + for (int i = 0; i < currentAlignment.Length; i++) { if (alignment[i] == TextAlignment.Undefined) currentAlignment[i] = _si.columnInfo[i].alignment; @@ -227,10 +229,10 @@ namespace Microsoft.PowerShell.Commands.Internal.Format } } - private string[] GenerateTableRow(string[] values, int[] alignment, DisplayCells ds) + private string[] GenerateTableRow(string[] values, ReadOnlySpan alignment, DisplayCells ds) { // select the active columns (skip hidden ones) - int[] validColumnArray = new int[_si.columnInfo.Length]; + Span validColumnArray = stackalloc int[_si.columnInfo.Length]; int validColumnCount = 0; for (int k = 0; k < _si.columnInfo.Length; k++) { @@ -241,7 +243,9 @@ namespace Microsoft.PowerShell.Commands.Internal.Format } if (validColumnCount == 0) + { return null; + } StringCollection[] scArray = new StringCollection[validColumnCount]; bool addPadding = true; @@ -353,7 +357,7 @@ namespace Microsoft.PowerShell.Commands.Internal.Format // finally, build an array of strings string[] rows = new string[screenRows]; - for (int row = 0; row < rows.Length; row++) + for (int row = 0; row < screenRows; row++) { StringBuilder sb = new StringBuilder(); // for a given row, walk the columns @@ -371,6 +375,7 @@ namespace Microsoft.PowerShell.Commands.Internal.Format } rows[row] = sb.ToString(); } + return rows; } @@ -390,7 +395,7 @@ namespace Microsoft.PowerShell.Commands.Internal.Format return sc; } - private string GenerateRow(string[] values, int[] alignment, DisplayCells dc) + private string GenerateRow(string[] values, ReadOnlySpan alignment, DisplayCells dc) { StringBuilder sb = new StringBuilder(); @@ -408,7 +413,6 @@ namespace Microsoft.PowerShell.Commands.Internal.Format // skip columns that are not at least a single character wide continue; } - int newRowIndex = sb.Length; // NOTE: the following padding operations assume that we // pad with a blank (or any character that ALWAYS maps to a single screen cell @@ -432,7 +436,7 @@ namespace Microsoft.PowerShell.Commands.Internal.Format private static string GenerateRowField(string val, int width, int alignment, DisplayCells dc, bool addPadding) { // make sure the string does not have any embedded in it - string s = StringManipulationHelper.TruncateAtNewLine(val) ?? ""; + string s = StringManipulationHelper.TruncateAtNewLine(val); string currentValue = s; int currentValueDisplayLength = dc.Length(currentValue);