XLSX comparison takes minutes even for identical files; time grows about cubically with cell count

  • Product: GroupDocs.Comparison for Java 26.10
  • Runtime: Zulu OpenJDK 21.0.11, macOS 26.6.2, Apple M4 Pro, 48 GB
  • License: metered and evaluation mode, same timings

Summary

Comparing XLSX files is very slow, even when the two workbooks are identical. Compare time grows roughly with the cube of the number of cells: doubling the cells makes it about 7 times slower. Two identical copies of a plain 200 × 20 grid of short text take 2.5 minutes, and a 300-row × 23-column report takes 3.5 to 12 minutes.

Steps to reproduce

  1. grid-200x20.xlsx has one sheet with a header row and 200 × 20 cells of short text (r12c3). No styles, links or formulas. grid-200x20-copy.xlsx is an identical copy.
  2. Compare them with default options (SlowCompareRepro.java in the attachment):
try (Comparer comparer = new Comparer("grid-200x20.xlsx")) {
  comparer.add("grid-200x20-copy.xlsx");
  comparer.compare("result.xlsx", new CompareOptions());
}

Expected

A few seconds at most for 4,000 short cells, especially with no differences.

Actual

compare() takes 153.8 s and reports 0 changes. Opening the files and add() take under 100 ms.

Timings

Identical files, compare() only, 0 changes in every run. Plain grids made with Grid.java (attached):

Rows × columns Cells Compare time
1000 × 1 1,000 2.8 s
100 × 5 500 1.8 s
200 × 5 1,000 8.0 s
400 × 5 2,000 58.7 s
100 × 20 2,000 22.5 s
200 × 20 4,000 153.8 s

A synthetic report shaped like our real documents, sbom-shaped-300.xlsx (300 rows × 23 columns, about 3,200 non-empty cells, 563 hyperlinks, some multi-line cells), takes 705.9 s (11 min 46 s) compared with itself. A real report of the same shape, which we can’t share, takes 203.9 s.

We varied the synthetic report to see what drives the time. Hyperlinks, wrapped text, extra sheets and long text make little difference. The number of cells does: at 50 rows it takes 5.5 s, at 100 rows 31 s, and at 300 rows 706 s.

Impact

We run one comparison per job in a container, so every Excel job takes minutes of CPU. Generated release documents (SBOM, risk tables, traceability matrices) routinely have hundreds of rows and 20+ columns.

Question

Is this expected for spreadsheet comparison, and is there an option that avoids it, for example skipping unchanged rows or limiting how far rows are searched for a match? If not, could the cell comparison be made to scale closer to linearly?

Related: XLSX comparison matches rows by position, so one deleted row marks every following row as changed

Attachment

groupdocs-xlsx-speed-repro.zip: grid-200x20.xlsx, grid-200x20-copy.xlsx, sbom-shaped-300.xlsx, SlowCompareRepro.java, Grid.java (generates grids of other sizes, needs Apache POI), timings.txt

groupdocs-xlsx-speed-repro.zip (18.2 KB)

Hello @ristomattip,

Thank you for the detailed report and the timing table. The scaling you measured pointed straight at the cause. We reproduced it, found the cause and fixed it. The fix will be included in GroupDocs.Comparison for Java 26.10.1, which we plan to release next week.

What was slow

A worksheet is aligned twice: rows first and columns first. Each alignment scores every pair of rows and every pair of columns against each other. Each score ran a word-level diff of the two texts, and that diff searched for the longest common run of words by trying every matching pair, then repeated the search on both sides of it. Rows that share little more than separators pushed that repetition very deep, so a single pair cost roughly the cube of its length. On top of that, the case-insensitive word comparison lower-cased both strings on every comparison; in our profile, 94% of the time was spent in Character.toLowerCase. None of this depended on whether the files differed, which is why identical workbooks were just as slow.

What changed in 26.10.1

  • The diff now numbers the words once and finds the longest common run in time proportional to the product of the two lengths, not its cube. It returns exactly the same result as before. We checked this against the previous implementation on 6,000 random inputs. The same diff is used by Word, PDF and text comparison, so they get faster as well.
  • Rows and columns that are the same at the start and at the end of a sheet are now matched directly, and only the part between them is aligned. Two identical sheets are no longer aligned at all.
  • Very long texts compared during alignment, such as a column of a few hundred cells, are now scored by how many words they have in common instead of by a full diff.

On all 240 spreadsheet pairs in our regression suite, every reported change — type, row, column and text — is identical to the previous version. Only the time changed.

Measurements

Measured on: Intel Core i7-1365U laptop (10 cores / 12 threads, 1.8 GHz base), 32 GB RAM, Windows 11 Pro (build 26200), Oracle JDK 19.0.2. Licensed mode, default CompareOptions, timing of Comparer.compare() only. These are grids of the same shape as your Grid.java output (header row plus “r12c3” values), compared with an identical copy.

This laptop is considerably slower than your M4 Pro: with 26.10, 100 × 20 took 80.3 s here versus your 22.5 s. So please compare the “before” and “after” columns within each row, not our numbers against yours.

Rows × columns 26.10 on this machine 26.10.1 on this machine Your 26.10 timing
1000 × 1 – 2.0 s 2.8 s
200 × 5 33.9 s 2.0 s 8.0 s
400 × 5 – 2.1 s 58.7 s
100 × 20 80.3 s 2.2 s 22.5 s
200 × 20 – 2.3 s 153.8 s
400 × 20 – 2.9 s –
300 × 23 – 2.6 s –

The 200 × 20 row uses your attached grid-200x20.xlsx and its copy. sbom-shaped-300.xlsx was not in the archive we received, so the 300 × 23 row is a grid of the same size.

What 26.10.1 does not solve yet

When rows are inserted into or deleted from a large sheet, rows are still paired by position. That is the issue from your other thread. Every row after the edit then counts as changed, so such comparisons are slower and report far too many changes. For example, 400 × 20 with one row deleted takes 21.3 s and reports 12,020 changes. Your real report with 4 added rows falls into this case.

We are working on matching rows by content. In our tests it brings the same 400 × 20 case down to 7.1 s and to the 20 changes that are actually there. It needs more verification than this fix, so it will come in a later release, and we will update both threads when it does.

To your question: in 26.10.1 there is no need for an option to skip unchanged rows. Unchanged rows at the start and the end of a sheet are skipped automatically.

Best regards,
Aleksei