Skip to content

Commit 7850daa

Browse files
committed
Improve performance when working with large amounts of cells
1 parent 0a63194 commit 7850daa

File tree

2 files changed

+85
-5
lines changed

2 files changed

+85
-5
lines changed

src/PhpSpreadsheet/Collection/Cells.php

Lines changed: 64 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,18 @@ 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+
}
137169

138-
return array_keys($this->index);
170+
// Build or rebuild index keys cache
171+
if ($this->indexKeysCache === null) {
172+
$this->indexKeysCache = array_keys($this->index);
173+
}
174+
175+
return $this->indexKeysCache;
139176
}
140177

141178
/**
@@ -145,9 +182,16 @@ public function getSortedCoordinates(): array
145182
*/
146183
public function getSortedCoordinatesInt(): array
147184
{
148-
asort($this->index);
185+
if (!$this->indexSorted) {
186+
asort($this->index);
187+
$this->indexSorted = true;
188+
}
149189

150-
return array_values($this->index);
190+
if ($this->indexValuesCache === null) {
191+
$this->indexValuesCache = array_values($this->index);
192+
}
193+
194+
return $this->indexValuesCache;
151195
}
152196

153197
/**
@@ -300,6 +344,11 @@ public function cloneCellCollection(Worksheet $worksheet): static
300344
}
301345
}
302346

347+
// Clear index sorted flag and index caches
348+
$newCollection->indexSorted = false;
349+
$newCollection->indexKeysCache = null;
350+
$newCollection->indexValuesCache = null;
351+
303352
return $newCollection;
304353
}
305354

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

442+
// Clear index sorted flag and index caches
443+
$this->indexSorted = false;
444+
$this->indexKeysCache = null;
445+
$this->indexValuesCache = null;
446+
393447
$this->currentCoordinate = $cellCoordinate;
394448
$this->currentCell = $cell;
395449
$this->currentCellIsDirty = true;
@@ -444,6 +498,11 @@ public function unsetWorksheetCells(): void
444498

445499
$this->index = [];
446500

501+
// Clear index sorted flag and index caches
502+
$this->indexSorted = false;
503+
$this->indexKeysCache = null;
504+
$this->indexValuesCache = null;
505+
447506
// detach ourself from the worksheet, so that it can then delete this object successfully
448507
$this->parent = null;
449508
}

src/PhpSpreadsheet/Worksheet/Worksheet.php

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

31053105
$index = ($row - 1) * AddressRange::MAX_COLUMN_INT + 1;
31063106
$indexPlus = $index + AddressRange::MAX_COLUMN_INT - 1;
3107+
3108+
// Binary search to quickly approach the correct index
3109+
$keyIndex = intdiv($keysCount, 2);
3110+
$boundLow = 0;
3111+
$boundHigh = $keysCount - 1;
3112+
while ($boundLow <= $boundHigh) {
3113+
$keyIndex = intdiv($boundLow + $boundHigh, 2);
3114+
if ($keys[$keyIndex] < $index) {
3115+
$boundLow = $keyIndex + 1;
3116+
} elseif ($keys[$keyIndex] > $index) {
3117+
$boundHigh = $keyIndex - 1;
3118+
} else {
3119+
break;
3120+
}
3121+
}
3122+
3123+
// Realign to the proper index value
3124+
while ($keyIndex > 0 && $keys[$keyIndex] > $index) {
3125+
--$keyIndex;
3126+
}
31073127
while ($keyIndex < $keysCount && $keys[$keyIndex] < $index) {
31083128
++$keyIndex;
31093129
}
3130+
31103131
while ($keyIndex < $keysCount && $keys[$keyIndex] <= $indexPlus) {
31113132
$key = $keys[$keyIndex];
31123133
$thisRow = intdiv($key - 1, AddressRange::MAX_COLUMN_INT) + 1;

0 commit comments

Comments
 (0)