Skip to content

Commit 66ea405

Browse files
authored
Merge pull request #4609 from Fuzuki785/fix/4607
Performance improvement when working with large amounts of cells
2 parents 889d26a + 5474bd7 commit 66ea405

File tree

2 files changed

+91
-5
lines changed

2 files changed

+91
-5
lines changed

src/PhpSpreadsheet/Collection/Cells.php

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,25 @@ class Cells
4343
*/
4444
private array $index = [];
4545

46+
/**
47+
* Flag to avoid sorting the index every time.
48+
*/
49+
private bool $indexSorted = false;
50+
51+
/**
52+
* Index keys cache to avoid recalculating on large arrays.
53+
*
54+
* @var null|string[]
55+
*/
56+
private ?array $indexKeysCache = null;
57+
58+
/**
59+
* Index values cache to avoid recalculating on large arrays.
60+
*
61+
* @var null|int[]
62+
*/
63+
private ?array $indexValuesCache = null;
64+
4665
/**
4766
* Prefix used to uniquely identify cache data for this worksheet.
4867
*/
@@ -112,6 +131,10 @@ public function delete(string $cellCoordinate): void
112131

113132
unset($this->index[$cellCoordinate]);
114133

134+
// Clear index caches
135+
$this->indexKeysCache = null;
136+
$this->indexValuesCache = null;
137+
115138
// Delete the entry from cache
116139
$this->cache->delete($this->cachePrefix . $cellCoordinate);
117140
}
@@ -123,7 +146,12 @@ public function delete(string $cellCoordinate): void
123146
*/
124147
public function getCoordinates(): array
125148
{
126-
return array_keys($this->index);
149+
// Build or rebuild index keys cache
150+
if ($this->indexKeysCache === null) {
151+
$this->indexKeysCache = array_keys($this->index);
152+
}
153+
154+
return $this->indexKeysCache;
127155
}
128156

129157
/**
@@ -133,9 +161,21 @@ public function getCoordinates(): array
133161
*/
134162
public function getSortedCoordinates(): array
135163
{
136-
asort($this->index);
164+
// Sort only when required
165+
if (!$this->indexSorted) {
166+
asort($this->index);
167+
$this->indexSorted = true;
168+
// Clear unsorted cache
169+
$this->indexKeysCache = null;
170+
$this->indexValuesCache = null;
171+
}
137172

138-
return array_keys($this->index);
173+
// Build or rebuild index keys cache
174+
if ($this->indexKeysCache === null) {
175+
$this->indexKeysCache = array_keys($this->index);
176+
}
177+
178+
return $this->indexKeysCache;
139179
}
140180

141181
/**
@@ -145,9 +185,19 @@ public function getSortedCoordinates(): array
145185
*/
146186
public function getSortedCoordinatesInt(): array
147187
{
148-
asort($this->index);
188+
if (!$this->indexSorted) {
189+
asort($this->index);
190+
$this->indexSorted = true;
191+
// Clear unsorted cache
192+
$this->indexKeysCache = null;
193+
$this->indexValuesCache = null;
194+
}
149195

150-
return array_values($this->index);
196+
if ($this->indexValuesCache === null) {
197+
$this->indexValuesCache = array_values($this->index);
198+
}
199+
200+
return $this->indexValuesCache;
151201
}
152202

153203
/**
@@ -300,6 +350,11 @@ public function cloneCellCollection(Worksheet $worksheet): static
300350
}
301351
}
302352

353+
// Clear index sorted flag and index caches
354+
$newCollection->indexSorted = false;
355+
$newCollection->indexKeysCache = null;
356+
$newCollection->indexValuesCache = null;
357+
303358
return $newCollection;
304359
}
305360

@@ -390,6 +445,11 @@ public function add(string $cellCoordinate, Cell $cell): Cell
390445
/** @var int $row */
391446
$this->index[$cellCoordinate] = (--$row * self::MAX_COLUMN_ID) + Coordinate::columnIndexFromString((string) $column);
392447

448+
// Clear index sorted flag and index caches
449+
$this->indexSorted = false;
450+
$this->indexKeysCache = null;
451+
$this->indexValuesCache = null;
452+
393453
$this->currentCoordinate = $cellCoordinate;
394454
$this->currentCell = $cell;
395455
$this->currentCellIsDirty = true;
@@ -444,6 +504,11 @@ public function unsetWorksheetCells(): void
444504

445505
$this->index = [];
446506

507+
// Clear index sorted flag and index caches
508+
$this->indexSorted = false;
509+
$this->indexKeysCache = null;
510+
$this->indexValuesCache = null;
511+
447512
// detach ourself from the worksheet, so that it can then delete this object successfully
448513
$this->parent = null;
449514
}

src/PhpSpreadsheet/Worksheet/Worksheet.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3100,9 +3100,30 @@ public function rangeToArrayYieldRows(
31003100

31013101
$index = ($row - 1) * AddressRange::MAX_COLUMN_INT + 1;
31023102
$indexPlus = $index + AddressRange::MAX_COLUMN_INT - 1;
3103+
3104+
// Binary search to quickly approach the correct index
3105+
$keyIndex = intdiv($keysCount, 2);
3106+
$boundLow = 0;
3107+
$boundHigh = $keysCount - 1;
3108+
while ($boundLow <= $boundHigh) {
3109+
$keyIndex = intdiv($boundLow + $boundHigh, 2);
3110+
if ($keys[$keyIndex] < $index) {
3111+
$boundLow = $keyIndex + 1;
3112+
} elseif ($keys[$keyIndex] > $index) {
3113+
$boundHigh = $keyIndex - 1;
3114+
} else {
3115+
break;
3116+
}
3117+
}
3118+
3119+
// Realign to the proper index value
3120+
while ($keyIndex > 0 && $keys[$keyIndex] > $index) {
3121+
--$keyIndex;
3122+
}
31033123
while ($keyIndex < $keysCount && $keys[$keyIndex] < $index) {
31043124
++$keyIndex;
31053125
}
3126+
31063127
while ($keyIndex < $keysCount && $keys[$keyIndex] <= $indexPlus) {
31073128
$key = $keys[$keyIndex];
31083129
$thisRow = intdiv($key - 1, AddressRange::MAX_COLUMN_INT) + 1;

0 commit comments

Comments
 (0)