diff --git a/samples/XlsReaderBenchmark.php b/samples/XlsReaderBenchmark.php new file mode 100644 index 000000000..a387e6107 --- /dev/null +++ b/samples/XlsReaderBenchmark.php @@ -0,0 +1,152 @@ +getActiveSheet(); + $sheet->setTitle('Benchmark'); + + // Header row with styling + for ($col = 1; $col <= $columnCount; ++$col) { + $sheet->setCellValue([$col, 1], "Column{$col}"); + $sheet->getStyle([$col, 1])->getFont()->setBold(true); + } + + // Repeating strings exercise the shared string table (SST) + $labels = ['Alpha', 'Bravo', 'Charlie', 'Delta', 'Echo', 'Foxtrot']; + $labelCount = count($labels); + + for ($row = 2; $row <= $rowCount + 1; ++$row) { + // Columns 1-4: numeric (RK / NUMBER records) + $sheet->setCellValue([1, $row], $row); + $sheet->setCellValue([2, $row], $row * 3.14159); + $sheet->setCellValue([3, $row], $row % 100); + $sheet->setCellValue([4, $row], round($row * 1.37, 2)); + + // Columns 5-8: strings (LABELSST records) + $sheet->setCellValue([5, $row], $labels[$row % $labelCount]); + $sheet->setCellValue([6, $row], "Item {$row}"); + $sheet->setCellValue([7, $row], $labels[($row + 3) % $labelCount] . ' ' . ($row % 50)); + $sheet->setCellValue([8, $row], 'Ref-' . str_pad((string) $row, 8, '0', STR_PAD_LEFT)); + + // Columns 9-10: formulas (FORMULA records) + $sheet->setCellValue([9, $row], "=B{$row}*D{$row}"); + $sheet->setCellValue([10, $row], "=A{$row}+C{$row}"); + } + + // Number formats so cells carry non-default XF indexes + $sheet->getStyle("B2:B{$rowCount}")->getNumberFormat()->setFormatCode('#,##0.00'); + $sheet->getStyle("D2:D{$rowCount}")->getNumberFormat()->setFormatCode('0.00'); + + $tempFile = tempnam(sys_get_temp_dir(), 'xls_bench_'); + if ($tempFile === false) { + throw new RuntimeException('Unable to create temporary file'); + } + + $writer = new XlsWriter($spreadsheet); + $writer->save($tempFile); + $spreadsheet->disconnectWorksheets(); + + return $tempFile; +} + +/** @param float[] $values */ +function median(array $values): float +{ + sort($values); + $count = count($values); + $middle = intdiv($count, 2); + + if ($count % 2 === 0) { + return ($values[$middle - 1] + $values[$middle]) / 2; + } + + return $values[$middle]; +} + +$results = []; + +foreach ($cellCounts as $cellCount) { + echo "--- {$cellCount} cells ---\n"; + + echo "Generating test file...\n"; + $tempFile = createXlsFile($cellCount, $columnCount); + $fileSize = filesize($tempFile); + echo sprintf("File size: %.1fKB\n", ($fileSize ?: 0) / 1024); + + $reader = new XlsReader(); + + // Warm up (autoloading, opcache) + $warmup = $reader->load($tempFile); + $warmup->disconnectWorksheets(); + unset($warmup); + gc_collect_cycles(); + + $times = []; + for ($i = 0; $i < $iterations; ++$i) { + $start = hrtime(true); + $spreadsheet = $reader->load($tempFile); + $elapsedMs = (hrtime(true) - $start) / 1e6; + + $times[] = $elapsedMs; + echo sprintf("Iteration %d: %.1fms\n", $i + 1, $elapsedMs); + + $spreadsheet->disconnectWorksheets(); + unset($spreadsheet); + gc_collect_cycles(); + } + + $medianMs = median($times); + $peakMb = memory_get_peak_usage(true) / 1048576; + echo sprintf("Median: %.1fms, Peak memory: %.1fMB\n\n", $medianMs, $peakMb); + + $results[] = [ + 'cells' => $cellCount, + 'median' => $medianMs, + 'peak' => $peakMb, + ]; + + unlink($tempFile); +} + +echo "--- Results ---\n"; +echo sprintf("%10s | %12s | %12s\n", 'Cells', 'Median (ms)', 'Peak (MB)'); +foreach ($results as $result) { + echo sprintf("%10d | %12.1f | %12.1f\n", $result['cells'], $result['median'], $result['peak']); +}