Skip to content

Commit 4e38e58

Browse files
authored
Bug/s912 performance (#2064)
* Made the same simple operation about 12 times faster * Added bug fixes for failing tests * Fixed bugs * Slight fix for when columns are deleted * Made dictionary resizable * Added assert for performance test * Overhauled a bit * Resolved several bugs relating to styling on columns * Issue att delete. Values are updated but not positions * Fixed multiple issues with the performance solution * Cleanup. Removed unnecesary comments. Added clarification * Resolved some visual bugs that did not fail tests * Cleaned up, fixed bugs, added asserts to tests * Cleaned up unnecesary comments/files
1 parent 5bd3382 commit 4e38e58

File tree

9 files changed

+433
-39
lines changed

9 files changed

+433
-39
lines changed

src/EPPlus/Core/ChangableDictionary.cs

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,38 @@ internal T this[int key]
4343
return default(T);
4444
}
4545
}
46+
set
47+
{
48+
var pos = Array.BinarySearch(_index[0], 0, _count, key);
49+
if (pos >= 0)
50+
{
51+
_items[_index[1][pos]] = value;
52+
}
53+
else
54+
{
55+
throw new InvalidOperationException($"The key'{key}' cannot be found. ChangableDictionary cannot set value of non-existant key.");
56+
}
57+
}
4658
}
4759

48-
internal void InsertAndShift(int fromPosition, int add)
60+
internal virtual void InsertAndShift(int fromPosition, int add)
4961
{
5062
var pos = Array.BinarySearch(_index[0], 0, _count, fromPosition);
51-
if(pos<0)
63+
64+
if (pos + 1 >= _index[0].Length - 1)
65+
{
66+
Array.Resize(ref _index[0], _index[0].Length << 1);
67+
Array.Resize(ref _index[1], _index[1].Length << 1);
68+
}
69+
70+
if (pos<0)
5271
{
5372
pos = ~pos;
5473
}
74+
5575
Array.Copy(_index[0], pos, _index[0], pos + 1, _count - pos);
5676
Array.Copy(_index[1], pos, _index[1], pos + 1, _count - pos);
77+
5778
_count++;
5879
for (int i=pos;i<Count;i++)
5980
{
@@ -66,8 +87,8 @@ internal void InsertAndShift(int fromPosition, int add)
6687
/// <summary>
6788
/// To keep track of if the collection has changed. Must be increased on each change operation.
6889
/// </summary>
69-
internal int Version { get; private set; }
70-
public void Add(int key, T value)
90+
internal int Version { get; set; }
91+
public virtual void Add(int key, T value)
7192
{
7293
var pos = Array.BinarySearch(_index[0], 0, _count, key);
7394
if (pos >= 0)
@@ -123,8 +144,13 @@ public void Clear()
123144
Version=0;
124145
}
125146

126-
public bool ContainsKey(int key)
147+
public virtual bool ContainsKey(int key)
127148
{
149+
if(_index[0].Length - 1 < _count)
150+
{
151+
Array.Resize(ref _index[0], _index[0].Length << 1);
152+
Array.Resize(ref _index[1], _index[1].Length << 1);
153+
}
128154
return Array.BinarySearch(_index[0], 0, _count, key) >= 0;
129155
}
130156

@@ -133,11 +159,11 @@ public IEnumerator<T> GetEnumerator()
133159
return new ChangeableDictionaryEnumerator<T>(this);
134160
}
135161

136-
public bool RemoveAndShift(int key)
162+
public virtual bool RemoveAndShift(int key)
137163
{
138164
return RemoveAndShift(key, true);
139165
}
140-
private bool RemoveAndShift(int key, bool dispose)
166+
internal bool RemoveAndShift(int key, bool dispose)
141167
{
142168
var pos = Array.BinarySearch(_index[0], 0, _count, key);
143169
if (pos >= 0)
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
using OfficeOpenXml.Core.CellStore;
2+
using OfficeOpenXml.Core.Worksheet.XmlWriter;
3+
using OfficeOpenXml.FormulaParsing.Excel.Functions.Database;
4+
using OfficeOpenXml.FormulaParsing.Excel.Functions.MathFunctions;
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
using System.Text;
9+
10+
namespace OfficeOpenXml.Core
11+
{
12+
internal class ColumnSpanDictionary<T>: ChangeableDictionary<T> where T : ExcelColumn
13+
{
14+
internal int FindInternalIndex(int col)
15+
{
16+
int closestIndex = -1;
17+
18+
if(_items.Count <= 0)
19+
{
20+
return closestIndex;
21+
}
22+
int columnIndex = -1;
23+
try
24+
{
25+
columnIndex = Array.BinarySearch(_index[0], 0, _count, col);
26+
}
27+
catch (Exception ex)
28+
{
29+
var msg = ex.Message;
30+
}
31+
32+
var indexExists = columnIndex >= 0;
33+
var indexNotFound = columnIndex < 0;
34+
35+
if (indexNotFound)
36+
{
37+
var inverted = ~columnIndex;
38+
39+
bool indexBetweenTwoColMin = inverted < _items.Count && inverted > 0;
40+
bool indexLargerThanLargestColMin = inverted >= _items.Count;
41+
42+
if (indexBetweenTwoColMin || indexLargerThanLargestColMin)
43+
{
44+
/*
45+
* In Array.Binary search:
46+
* "If value is not found and value is less than one or more elements in array;
47+
* the negative number returned is the bitwise complement of the index of the first element that is larger than value"
48+
* And since we store based on colMin. It will find the index above the one we want.
49+
*
50+
* Similarily even if we are larger than the largest colMin the colMax could still be larger
51+
*/
52+
inverted -= 1;
53+
}
54+
55+
if (inverted < _index[1].Length - 1)
56+
{
57+
closestIndex = _index[1][inverted];
58+
}
59+
else
60+
{
61+
//if inverted is less than collection maximum it does not exist. index is already -1.
62+
}
63+
}
64+
else
65+
{
66+
closestIndex = _index[1][columnIndex];
67+
}
68+
return closestIndex;
69+
}
70+
71+
internal ExcelColumn GetExcelColumn(int col)
72+
{
73+
var internalIndex = FindInternalIndex(col);
74+
if (internalIndex > -1 && _items.Count > 0)
75+
{
76+
var closestCol = _items[internalIndex];
77+
if(closestCol != null)
78+
{
79+
if (closestCol.ColumnMin <= col && col <= closestCol.ColumnMax)
80+
{
81+
return closestCol;
82+
}
83+
}
84+
}
85+
return null;
86+
}
87+
88+
internal bool TryGetExcelColumn(int col, out ExcelColumn columnValue)
89+
{
90+
columnValue = GetExcelColumn(col);
91+
return columnValue != null;
92+
}
93+
94+
/// <summary>
95+
/// Note: "Update" really just means "Add if does not exist"
96+
/// This as the ExcelColumn value is a reference and updates automatically.
97+
/// </summary>
98+
/// <param name="col"></param>
99+
/// <param name="columnValue"></param>
100+
internal void UpdateColumn(int col, ExcelColumn columnValue)
101+
{
102+
if(TryGetExcelColumn(col, out ExcelColumn existingColumn))
103+
{
104+
var pos = Array.BinarySearch(_index[0], 0, _count, col);
105+
if (pos < 0)
106+
{
107+
//Column exists but only as part of another column
108+
Add(col, (T)columnValue);
109+
}
110+
else
111+
{
112+
//Column is already updated as the reference is updated. If an insert other functions handle that.
113+
}
114+
}
115+
else
116+
{
117+
var pos = Array.BinarySearch(_index[0], 0, _count, columnValue.ColumnMin);
118+
if (pos < 0)
119+
{
120+
//Column exists but only as part of another column
121+
Add(columnValue.ColumnMin, (T)columnValue);
122+
}
123+
}
124+
if (_count >= _index[0].Length)
125+
{
126+
Array.Resize(ref _index[0], _index[0].Length << 1);
127+
Array.Resize(ref _index[1], _index[1].Length << 1);
128+
}
129+
}
130+
/// On insert/delete the positions update After the cell values have been inserted/deleted to final positions.
131+
/// Using This method for deletions and the standard InsertAndShift after inserts.
132+
internal void UpdateDeletedPositions(int columnFrom, int columnTo)
133+
{
134+
//Iterate and delete backwards so that columns further down need not be updated.
135+
for (int i = columnTo; i > columnFrom; i--)
136+
{
137+
var index = FindInternalIndex(i);
138+
if(index > -1)
139+
{
140+
RemoveAndShift(index, false);
141+
}
142+
}
143+
}
144+
}
145+
}

src/EPPlus/Core/Worksheet/WorksheetRangeDeleteHelper.cs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Date Author Change
1616
using OfficeOpenXml.DataValidation;
1717
using OfficeOpenXml.DataValidation.Formulas.Contracts;
1818
using OfficeOpenXml.Drawing;
19+
using OfficeOpenXml.FormulaParsing.Excel.Functions.MathFunctions;
1920
using OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup;
2021
using OfficeOpenXml.Sorting.Internal;
2122
using OfficeOpenXml.Sparkline;
@@ -100,7 +101,7 @@ internal static void DeleteColumn(ExcelWorksheet ws, int columnFrom, int columns
100101
ws.Drawings.ReadPositionsAndSize();
101102
AdjustColumnMinMaxDelete(ws, columnFrom, columns);
102103
var delRange = new ExcelAddressBase(1, columnFrom, ExcelPackage.MaxRows, columnFrom + columns - 1);
103-
WorksheetRangeHelper.ConvertEffectedSharedFormulasToCellFormulas(ws, delRange, false);
104+
WorksheetRangeHelper.ConvertEffectedSharedFormulasToCellFormulas(ws, delRange,false);
104105

105106
DeleteCellStores(ws, 0, columnFrom, 0, columns);
106107

@@ -200,24 +201,25 @@ private static void AdjustColumnMinMaxDelete(ExcelWorksheet ws, int columnFrom,
200201
{
201202
if (column.ColumnMin > toCol)
202203
{
203-
column._columnMin -= columns;
204-
if (column._columnMax < ExcelPackage.MaxColumns)
204+
column.InternalColMinSetter(column.ColumnMin - columns);
205+
if (column.ColumnMax < ExcelPackage.MaxColumns)
205206
{
206-
column._columnMax -= columns;
207+
column.InternalColMaxSetter(column.ColumnMax - columns);
207208
}
208209
}
209210
else if (column.ColumnMax > toCol)
210211
{
211-
if (column._columnMax < ExcelPackage.MaxColumns)
212+
if (column.ColumnMax < ExcelPackage.MaxColumns)
212213
{
213-
column._columnMax -= columns;
214+
column.InternalColMaxSetter(column.ColumnMax - columns);
214215
}
215-
if (column._columnMin > columnFrom) column._columnMin = columnFrom;
216+
if (column.ColumnMin > columnFrom) column.InternalColMinSetter(columnFrom);
216217
moveValue = cse.Value;
217218
}
218219
}
219220
}
220221
if (moveValue._styleId != int.MaxValue) ws._values.SetValue(0, toCol + 1, moveValue);
222+
ws.ColumnLookup.UpdateDeletedPositions(columnFrom, columns);
221223
}
222224

223225
private static void ValidateRow(ExcelWorksheet ws, int rowFrom, int rows, int columnFrom = 1, int columns = ExcelPackage.MaxColumns)
@@ -302,12 +304,13 @@ private static void AdjustColumnMinMax(ExcelWorksheet ws, int columnFrom, int co
302304
if (column is ExcelColumn)
303305
{
304306
var c = (ExcelColumn)column;
305-
if (c._columnMin >= columnFrom)
307+
if (c.ColumnMin >= columnFrom)
306308
{
307-
c._columnMin += columns;
308-
c._columnMax += columns;
309+
c.InternalColMinSetter(c.ColumnMin + columns);
310+
c.InternalColMaxSetter(c.ColumnMax + columns);
309311
}
310312
}
313+
311314
}
312315
}
313316
static void FixFormulasDeleteRow(ExcelWorksheet ws, int rowFrom, int rows, string workSheetName)

src/EPPlus/Core/Worksheet/WorksheetRangeInsertHelper.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ internal static void InsertColumn(ExcelWorksheet ws, int columnFrom, int columns
124124
lock (ws)
125125
{
126126
ws.Drawings.ReadPositionsAndSize();
127-
127+
128128
InsertCellStores(ws, 0, columnFrom, 0, columns);
129129

130130
FixFormulasInsertColumn(ws, columnFrom, columns);
@@ -696,30 +696,30 @@ private static void AdjustColumns(ExcelWorksheet ws, int columnFrom, int columns
696696
for (int i = lst.Count - 1; i >= 0; i--)
697697
{
698698
var c = lst[i];
699-
if (c._columnMin >= columnFrom)
699+
if (c.ColumnMin >= columnFrom)
700700
{
701-
if (c._columnMin + columns <= ExcelPackage.MaxColumns)
701+
if (c.ColumnMin + columns <= ExcelPackage.MaxColumns)
702702
{
703-
c._columnMin += columns;
703+
c.InternalColMinSetter(c.ColumnMin + columns);
704704
}
705705
else
706706
{
707-
c._columnMin = ExcelPackage.MaxColumns;
707+
c.InternalColMinSetter(ExcelPackage.MaxColumns);
708708
}
709709

710-
if (c._columnMax + columns <= ExcelPackage.MaxColumns)
710+
if (c.ColumnMax + columns <= ExcelPackage.MaxColumns)
711711
{
712-
c._columnMax += columns;
712+
c.InternalColMaxSetter(c.ColumnMax + columns);
713713
}
714714
else
715715
{
716-
c._columnMax = ExcelPackage.MaxColumns;
716+
c.InternalColMaxSetter(ExcelPackage.MaxColumns);
717717
}
718718
}
719-
else if (c._columnMax >= columnFrom)
719+
else if (c.ColumnMax >= columnFrom)
720720
{
721-
var cc = c._columnMax - columnFrom;
722-
c._columnMax = columnFrom - 1;
721+
var cc = c.ColumnMax - columnFrom;
722+
c.InternalColMaxSetter(columnFrom - 1);
723723
ws.CopyColumn(c, columnFrom + columns, columnFrom + columns + cc);
724724
}
725725
}

src/EPPlus/ExcelColumn.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ protected internal ExcelColumn(ExcelWorksheet Worksheet, int col)
3939
_width = _worksheet.DefaultColWidth;
4040
}
4141
#endregion
42-
internal int _columnMin;
42+
private int _columnMin;
4343
/// <summary>
4444
/// Sets the first column the definition refers to.
4545
/// </summary>
@@ -75,6 +75,17 @@ public int ColumnMax
7575
_columnMax = value;
7676
}
7777
}
78+
79+
//Internal for delete/insert operations
80+
internal void InternalColMinSetter(int colMin)
81+
{
82+
_columnMin = colMin;
83+
}
84+
//Internal for delete/insert operations
85+
internal void InternalColMaxSetter(int colMax)
86+
{
87+
_columnMax = colMax;
88+
}
7889
/// <summary>
7990
/// Internal range id for the column
8091
/// </summary>

0 commit comments

Comments
 (0)