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
This commit is contained in:
cupsos 2022-09-28 23:03:51 +09:00 committed by GitHub
parent 492ddd555a
commit 19c7076111
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 45 additions and 30 deletions

View File

@ -2,11 +2,11 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFrameworks>netcoreapp3.1;net6.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" /> <PackageReference Include="BenchmarkDotNet" Version="0.13.2" />
<PackageReference Include="ClosedXML" Version="0.95.4" /> <PackageReference Include="ClosedXML" Version="0.95.4" />
<PackageReference Include="ClosedXML.Report" Version="0.2.1" /> <PackageReference Include="ClosedXML.Report" Version="0.2.1" />
<PackageReference Include="DocumentFormat.OpenXml" Version="2.12.3" /> <PackageReference Include="DocumentFormat.OpenXml" Version="2.12.3" />

View File

@ -144,6 +144,7 @@ namespace MiniExcelLibs.OpenXml
#region Generate rows and cells #region Generate rows and cells
int originRowIndex; int originRowIndex;
int rowIndexDiff = 0; int rowIndexDiff = 0;
var rowXml = new StringBuilder();
foreach (var rowInfo in XRowInfos) foreach (var rowInfo in XRowInfos)
{ {
var row = rowInfo.Row; var row = rowInfo.Row;
@ -152,21 +153,32 @@ namespace MiniExcelLibs.OpenXml
originRowIndex = int.Parse(row.GetAttribute("r")); originRowIndex = int.Parse(row.GetAttribute("r"));
var newRowIndex = originRowIndex + rowIndexDiff; var newRowIndex = originRowIndex + rowIndexDiff;
string innerXml = row.InnerXml;
rowXml.Clear()
.AppendFormat(@"<{0}", row.Name);
foreach (var attr in row.Attributes.Cast<XmlAttribute>()
.Where(e => e.Name != "r"))
{
rowXml.AppendFormat(@" {0}=""{1}""", attr.Name, attr.Value);
}
string outerXmlOpen = rowXml.ToString();
if (rowInfo.CellIEnumerableValues != null) if (rowInfo.CellIEnumerableValues != null)
{ {
var first = true; var first = true;
var iEnumerableIndex = 0; var iEnumerableIndex = 0;
foreach (var item in rowInfo.CellIEnumerableValues) foreach (var item in rowInfo.CellIEnumerableValues)
{ {
iEnumerableIndex++; iEnumerableIndex++;
var newRow = row.Clone() as XmlElement; rowXml.Clear()
newRow.SetAttribute("r", newRowIndex.ToString()); .Append(outerXmlOpen)
StringBuilder rowXml = new StringBuilder(newRow.InnerXml); .AppendFormat(@" r=""{0}"">", newRowIndex)
// newRow.InnerXml = row.InnerXml.Replace($"{{{{$rowindex}}}}", newRowIndex.ToString()); .Append(innerXml)
rowXml.Replace($"{{{{$rowindex}}}}", newRowIndex.ToString()); .Replace($"{{{{$rowindex}}}}", newRowIndex.ToString())
.AppendFormat(@"</{0}>", row.Name);
if (rowInfo.IsDictionary) if (rowInfo.IsDictionary)
{ {
@ -176,7 +188,6 @@ namespace MiniExcelLibs.OpenXml
var key = $"{{{{{rowInfo.IEnumerablePropName}.{propInfo.Key}}}}}"; var key = $"{{{{{rowInfo.IEnumerablePropName}.{propInfo.Key}}}}}";
if (item == null) //![image](https://user-images.githubusercontent.com/12729184/114728510-bc3e5900-9d71-11eb-9721-8a414dca21a0.png) 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, ""); rowXml.Replace(key, "");
continue; continue;
} }
@ -184,7 +195,6 @@ namespace MiniExcelLibs.OpenXml
var cellValue = dic[propInfo.Key]; var cellValue = dic[propInfo.Key];
if (cellValue == null) if (cellValue == null)
{ {
// newRow.InnerXml = newRow.InnerXml.Replace(key, "");
rowXml.Replace(key, ""); rowXml.Replace(key, "");
continue; continue;
} }
@ -202,7 +212,6 @@ namespace MiniExcelLibs.OpenXml
} }
//TODO: ![image](https://user-images.githubusercontent.com/12729184/114848248-17735880-9e11-11eb-8258-63266bda0a1a.png) //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); rowXml.Replace(key, cellValueStr);
} }
} }
@ -214,7 +223,6 @@ namespace MiniExcelLibs.OpenXml
var key = $"{{{{{rowInfo.IEnumerablePropName}.{propInfo.Key}}}}}"; var key = $"{{{{{rowInfo.IEnumerablePropName}.{propInfo.Key}}}}}";
if (item == null) //![image](https://user-images.githubusercontent.com/12729184/114728510-bc3e5900-9d71-11eb-9721-8a414dca21a0.png) 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, ""); rowXml.Replace(key, "");
continue; continue;
} }
@ -222,7 +230,6 @@ namespace MiniExcelLibs.OpenXml
var cellValue = datarow[propInfo.Key]; var cellValue = datarow[propInfo.Key];
if (cellValue == null) if (cellValue == null)
{ {
// newRow.InnerXml = newRow.InnerXml.Replace(key, "");
rowXml.Replace(key, ""); rowXml.Replace(key, "");
continue; continue;
} }
@ -240,7 +247,6 @@ namespace MiniExcelLibs.OpenXml
} }
//TODO: ![image](https://user-images.githubusercontent.com/12729184/114848248-17735880-9e11-11eb-8258-63266bda0a1a.png) //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); rowXml.Replace(key, cellValueStr);
} }
} }
@ -253,7 +259,6 @@ namespace MiniExcelLibs.OpenXml
var key = $"{{{{{rowInfo.IEnumerablePropName}.{prop.Name}}}}}"; var key = $"{{{{{rowInfo.IEnumerablePropName}.{prop.Name}}}}}";
if (item == null) //![image](https://user-images.githubusercontent.com/12729184/114728510-bc3e5900-9d71-11eb-9721-8a414dca21a0.png) 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, ""); rowXml.Replace(key, "");
continue; continue;
} }
@ -261,7 +266,6 @@ namespace MiniExcelLibs.OpenXml
var cellValue = prop.GetValue(item); var cellValue = prop.GetValue(item);
if (cellValue == null) if (cellValue == null)
{ {
//newRow.InnerXml = newRow.InnerXml.Replace(key, "");
rowXml.Replace(key, ""); rowXml.Replace(key, "");
continue; continue;
} }
@ -284,14 +288,10 @@ namespace MiniExcelLibs.OpenXml
} }
//TODO: ![image](https://user-images.githubusercontent.com/12729184/114848248-17735880-9e11-11eb-8258-63266bda0a1a.png) //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); 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) // note: only first time need add diff ![image](https://user-images.githubusercontent.com/12729184/114494728-6bceda80-9c4f-11eb-9685-8b5ed054eabe.png)
if (!first) if (!first)
//rowIndexDiff++; //rowIndexDiff++;
@ -300,8 +300,7 @@ namespace MiniExcelLibs.OpenXml
var mergeBaseRowIndex = newRowIndex; var mergeBaseRowIndex = newRowIndex;
newRowIndex = newRowIndex + (rowInfo.IEnumerableMercell == null ? 1 : rowInfo.IEnumerableMercell.Height); newRowIndex = newRowIndex + (rowInfo.IEnumerableMercell == null ? 1 : rowInfo.IEnumerableMercell.Height);
writer.Write(CleanXml(newRow.OuterXml, endPrefix)); writer.Write(CleanXml(rowXml, endPrefix)); // pass StringBuilder for netcoreapp3.0 or above
newRow = null;
//mergecells //mergecells
if (rowInfo.RowMercells != null) if (rowInfo.RowMercells != null)
@ -348,9 +347,13 @@ namespace MiniExcelLibs.OpenXml
} }
else else
{ {
row.SetAttribute("r", newRowIndex.ToString()); rowXml.Clear()
row.InnerXml = new StringBuilder(row.InnerXml).Replace($"{{{{$rowindex}}}}", newRowIndex.ToString()).ToString(); .Append(outerXmlOpen)
writer.Write(CleanXml(row.OuterXml, endPrefix)); .AppendFormat(@" r=""{0}"">", newRowIndex)
.Append(innerXml)
.Replace($"{{{{$rowindex}}}}", newRowIndex.ToString())
.AppendFormat("</{0}>", row.Name);
writer.Write(CleanXml(rowXml, endPrefix)); // pass StringBuilder for netcoreapp3.0 or above
//mergecells //mergecells
if (rowInfo.RowMercells != null) if (rowInfo.RowMercells != null)
@ -408,12 +411,18 @@ namespace MiniExcelLibs.OpenXml
return cellValueStr; 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) private static string CleanXml(string xml, string endPrefix)
{ {
//TODO: need to optimize //TODO: need to optimize
return xml return CleanXml(new StringBuilder(xml), endPrefix)
.Replace("xmlns:x14ac=\"http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac\"", "") .ToString();
.Replace($"xmlns{endPrefix}=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\"", "");
} }
private void ReplaceSharedStringsToStr(IDictionary<int, string> sharedStrings, ref XmlNodeList rows) private void ReplaceSharedStringsToStr(IDictionary<int, string> sharedStrings, ref XmlNodeList rows)

View File

@ -9,7 +9,13 @@
/// </summary> /// </summary>
internal static string EncodeXML(string value) => value == null internal static string EncodeXML(string value) => value == null
? string.Empty ? string.Empty
: XmlEncoder.EncodeString(value).Replace("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;").Replace("\"", "&quot;").Replace("'", "&apos;"); : XmlEncoder.EncodeString(value)
.Replace("&", "&amp;")
.Replace("<", "&lt;")
.Replace(">", "&gt;")
.Replace("\"", "&quot;")
.Replace("'", "&apos;")
.ToString();
/// <summary>X=CellLetter,Y=CellNumber,ex:A1=(1,1),B2=(2,2)</summary> /// <summary>X=CellLetter,Y=CellNumber,ex:A1=(1,1),B2=(2,2)</summary>
internal static string ConvertXyToCell(Tuple<int, int> xy) internal static string ConvertXyToCell(Tuple<int, int> xy)

View File

@ -11,7 +11,7 @@
private static readonly Regex xHHHHRegex = new Regex("_(x[\\dA-Fa-f]{4})_", RegexOptions.Compiled); 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); 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; if (encodeStr == null) return null;
@ -27,7 +27,7 @@
sb.Append(XmlConvert.EncodeName(ch.ToString())); sb.Append(XmlConvert.EncodeName(ch.ToString()));
} }
return sb.ToString(); return sb;
} }
public static string DecodeString(string decodeStr) public static string DecodeString(string decodeStr)