From 19c70761115f0aa872884922296d944235214aa6 Mon Sep 17 00:00:00 2001 From: cupsos Date: Wed, 28 Sep 2022 23:03:51 +0900 Subject: [PATCH] Reduce string memory allocation when template save (#439) * Bump BenchmarkDotNet 0.12.1 -> 0.13.2 * Add TFM net6.0 to benchmark * Reduce string memory allocation when template save --- .../MiniExcel.Benchmarks.csproj | 4 +- .../OpenXml/ExcelOpenXmlTemplate.Impl.cs | 59 +++++++++++-------- src/MiniExcel/OpenXml/ExcelOpenXmlUtils.cs | 8 ++- src/MiniExcel/Utils/XmlEncoder.cs | 4 +- 4 files changed, 45 insertions(+), 30 deletions(-) diff --git a/benchmarks/MiniExcel.Benchmarks/MiniExcel.Benchmarks.csproj b/benchmarks/MiniExcel.Benchmarks/MiniExcel.Benchmarks.csproj index f87a560..05d2e5b 100644 --- a/benchmarks/MiniExcel.Benchmarks/MiniExcel.Benchmarks.csproj +++ b/benchmarks/MiniExcel.Benchmarks/MiniExcel.Benchmarks.csproj @@ -2,11 +2,11 @@ Exe - netcoreapp3.1 + netcoreapp3.1;net6.0 - + diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlTemplate.Impl.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlTemplate.Impl.cs index 3655efa..c58403b 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlTemplate.Impl.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlTemplate.Impl.cs @@ -144,6 +144,7 @@ namespace MiniExcelLibs.OpenXml #region Generate rows and cells int originRowIndex; int rowIndexDiff = 0; + var rowXml = new StringBuilder(); foreach (var rowInfo in XRowInfos) { var row = rowInfo.Row; @@ -152,21 +153,32 @@ namespace MiniExcelLibs.OpenXml originRowIndex = int.Parse(row.GetAttribute("r")); var newRowIndex = originRowIndex + rowIndexDiff; + string innerXml = row.InnerXml; + rowXml.Clear() + .AppendFormat(@"<{0}", row.Name); + foreach (var attr in row.Attributes.Cast() + .Where(e => e.Name != "r")) + { + rowXml.AppendFormat(@" {0}=""{1}""", attr.Name, attr.Value); + } + string outerXmlOpen = rowXml.ToString(); if (rowInfo.CellIEnumerableValues != null) { var first = true; var iEnumerableIndex = 0; + foreach (var item in rowInfo.CellIEnumerableValues) { iEnumerableIndex++; - var newRow = row.Clone() as XmlElement; - newRow.SetAttribute("r", newRowIndex.ToString()); - StringBuilder rowXml = new StringBuilder(newRow.InnerXml); - // newRow.InnerXml = row.InnerXml.Replace($"{{{{$rowindex}}}}", newRowIndex.ToString()); - rowXml.Replace($"{{{{$rowindex}}}}", newRowIndex.ToString()); + rowXml.Clear() + .Append(outerXmlOpen) + .AppendFormat(@" r=""{0}"">", newRowIndex) + .Append(innerXml) + .Replace($"{{{{$rowindex}}}}", newRowIndex.ToString()) + .AppendFormat(@"", row.Name); if (rowInfo.IsDictionary) { @@ -176,7 +188,6 @@ namespace MiniExcelLibs.OpenXml var key = $"{{{{{rowInfo.IEnumerablePropName}.{propInfo.Key}}}}}"; if (item == null) //![image](https://user-images.githubusercontent.com/12729184/114728510-bc3e5900-9d71-11eb-9721-8a414dca21a0.png) { - // newRow.InnerXml = newRow.InnerXml.Replace(key, ""); rowXml.Replace(key, ""); continue; } @@ -184,7 +195,6 @@ namespace MiniExcelLibs.OpenXml var cellValue = dic[propInfo.Key]; if (cellValue == null) { - // newRow.InnerXml = newRow.InnerXml.Replace(key, ""); rowXml.Replace(key, ""); continue; } @@ -202,7 +212,6 @@ namespace MiniExcelLibs.OpenXml } //TODO: ![image](https://user-images.githubusercontent.com/12729184/114848248-17735880-9e11-11eb-8258-63266bda0a1a.png) - //newRow.InnerXml = newRow.InnerXml.Replace(key, cellValueStr); rowXml.Replace(key, cellValueStr); } } @@ -214,7 +223,6 @@ namespace MiniExcelLibs.OpenXml var key = $"{{{{{rowInfo.IEnumerablePropName}.{propInfo.Key}}}}}"; if (item == null) //![image](https://user-images.githubusercontent.com/12729184/114728510-bc3e5900-9d71-11eb-9721-8a414dca21a0.png) { - // newRow.InnerXml = newRow.InnerXml.Replace(key, ""); rowXml.Replace(key, ""); continue; } @@ -222,7 +230,6 @@ namespace MiniExcelLibs.OpenXml var cellValue = datarow[propInfo.Key]; if (cellValue == null) { - // newRow.InnerXml = newRow.InnerXml.Replace(key, ""); rowXml.Replace(key, ""); continue; } @@ -240,7 +247,6 @@ namespace MiniExcelLibs.OpenXml } //TODO: ![image](https://user-images.githubusercontent.com/12729184/114848248-17735880-9e11-11eb-8258-63266bda0a1a.png) - //newRow.InnerXml = newRow.InnerXml.Replace(key, cellValueStr); rowXml.Replace(key, cellValueStr); } } @@ -253,7 +259,6 @@ namespace MiniExcelLibs.OpenXml var key = $"{{{{{rowInfo.IEnumerablePropName}.{prop.Name}}}}}"; if (item == null) //![image](https://user-images.githubusercontent.com/12729184/114728510-bc3e5900-9d71-11eb-9721-8a414dca21a0.png) { - //newRow.InnerXml = newRow.InnerXml.Replace(key, ""); rowXml.Replace(key, ""); continue; } @@ -261,7 +266,6 @@ namespace MiniExcelLibs.OpenXml var cellValue = prop.GetValue(item); if (cellValue == null) { - //newRow.InnerXml = newRow.InnerXml.Replace(key, ""); rowXml.Replace(key, ""); continue; } @@ -284,14 +288,10 @@ namespace MiniExcelLibs.OpenXml } //TODO: ![image](https://user-images.githubusercontent.com/12729184/114848248-17735880-9e11-11eb-8258-63266bda0a1a.png) - // newRow.InnerXml = newRow.InnerXml.Replace(key, cellValueStr); rowXml.Replace(key, cellValueStr); - StringBuilder stringBuilder = new StringBuilder(); } } - newRow.InnerXml = rowXml.ToString(); - // note: only first time need add diff ![image](https://user-images.githubusercontent.com/12729184/114494728-6bceda80-9c4f-11eb-9685-8b5ed054eabe.png) if (!first) //rowIndexDiff++; @@ -300,8 +300,7 @@ namespace MiniExcelLibs.OpenXml var mergeBaseRowIndex = newRowIndex; newRowIndex = newRowIndex + (rowInfo.IEnumerableMercell == null ? 1 : rowInfo.IEnumerableMercell.Height); - writer.Write(CleanXml(newRow.OuterXml, endPrefix)); - newRow = null; + writer.Write(CleanXml(rowXml, endPrefix)); // pass StringBuilder for netcoreapp3.0 or above //mergecells if (rowInfo.RowMercells != null) @@ -348,9 +347,13 @@ namespace MiniExcelLibs.OpenXml } else { - row.SetAttribute("r", newRowIndex.ToString()); - row.InnerXml = new StringBuilder(row.InnerXml).Replace($"{{{{$rowindex}}}}", newRowIndex.ToString()).ToString(); - writer.Write(CleanXml(row.OuterXml, endPrefix)); + rowXml.Clear() + .Append(outerXmlOpen) + .AppendFormat(@" r=""{0}"">", newRowIndex) + .Append(innerXml) + .Replace($"{{{{$rowindex}}}}", newRowIndex.ToString()) + .AppendFormat("", row.Name); + writer.Write(CleanXml(rowXml, endPrefix)); // pass StringBuilder for netcoreapp3.0 or above //mergecells if (rowInfo.RowMercells != null) @@ -408,12 +411,18 @@ namespace MiniExcelLibs.OpenXml return cellValueStr; } + private static StringBuilder CleanXml(StringBuilder xml, string endPrefix) + { + return xml + .Replace("xmlns:x14ac=\"http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac\"", "") + .Replace($"xmlns{endPrefix}=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\"", ""); + } + private static string CleanXml(string xml, string endPrefix) { //TODO: need to optimize - return xml - .Replace("xmlns:x14ac=\"http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac\"", "") - .Replace($"xmlns{endPrefix}=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\"", ""); + return CleanXml(new StringBuilder(xml), endPrefix) + .ToString(); } private void ReplaceSharedStringsToStr(IDictionary sharedStrings, ref XmlNodeList rows) diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlUtils.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlUtils.cs index 306e169..18284b8 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlUtils.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlUtils.cs @@ -9,7 +9,13 @@ /// internal static string EncodeXML(string value) => value == null ? string.Empty - : XmlEncoder.EncodeString(value).Replace("&", "&").Replace("<", "<").Replace(">", ">").Replace("\"", """).Replace("'", "'"); + : XmlEncoder.EncodeString(value) + .Replace("&", "&") + .Replace("<", "<") + .Replace(">", ">") + .Replace("\"", """) + .Replace("'", "'") + .ToString(); /// X=CellLetter,Y=CellNumber,ex:A1=(1,1),B2=(2,2) internal static string ConvertXyToCell(Tuple xy) diff --git a/src/MiniExcel/Utils/XmlEncoder.cs b/src/MiniExcel/Utils/XmlEncoder.cs index 388b638..8bf0937 100644 --- a/src/MiniExcel/Utils/XmlEncoder.cs +++ b/src/MiniExcel/Utils/XmlEncoder.cs @@ -11,7 +11,7 @@ private static readonly Regex xHHHHRegex = new Regex("_(x[\\dA-Fa-f]{4})_", RegexOptions.Compiled); private static readonly Regex Uppercase_X_HHHHRegex = new Regex("_(X[\\dA-Fa-f]{4})_", RegexOptions.Compiled); - public static string EncodeString(string encodeStr) + public static StringBuilder EncodeString(string encodeStr) { if (encodeStr == null) return null; @@ -27,7 +27,7 @@ sb.Append(XmlConvert.EncodeName(ch.ToString())); } - return sb.ToString(); + return sb; } public static string DecodeString(string decodeStr)