147 lines
6.1 KiB
Java
147 lines
6.1 KiB
Java
import java.util.*;
|
|
|
|
/**
|
|
* openoffice-0001: XclExpXFBuffer::AddBorderAndFill O(N²) std::find_if
|
|
*
|
|
* Models the border/fill dedup logic in AddBorderAndFill (xestyle.cxx).
|
|
* Original: linear scan of maBorders/maFills vectors per XF record → O(N²).
|
|
* Fixed: maintain a HashMap<Long, Integer> for O(1) key lookup.
|
|
*
|
|
* Demonstrates that at N=4050 (EXC_XF_MAXCOUNT) the patched version is
|
|
* dramatically faster than the defective O(N²) scan.
|
|
*/
|
|
public class OpenOfficeXFBorderFillTest {
|
|
|
|
// ---- Model of the defect: O(N²) linear scan ----
|
|
|
|
static class BorderRecord {
|
|
final int leftColor, rightColor, topColor, bottomColor;
|
|
final int leftLine, rightLine, topLine, bottomLine;
|
|
|
|
BorderRecord(int id) {
|
|
// Each XF gets a unique border so every call appends → worst case
|
|
this.leftColor = id;
|
|
this.rightColor = id + 1;
|
|
this.topColor = id + 2;
|
|
this.bottomColor = id + 3;
|
|
this.leftLine = id & 0xF;
|
|
this.rightLine = (id >> 4) & 0xF;
|
|
this.topLine = (id >> 8) & 0xF;
|
|
this.bottomLine = (id >> 12) & 0xF;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object o) {
|
|
if (!(o instanceof BorderRecord)) return false;
|
|
BorderRecord b = (BorderRecord) o;
|
|
return leftColor == b.leftColor && rightColor == b.rightColor
|
|
&& topColor == b.topColor && bottomColor == b.bottomColor
|
|
&& leftLine == b.leftLine && rightLine == b.rightLine
|
|
&& topLine == b.topLine && bottomLine == b.bottomLine;
|
|
}
|
|
}
|
|
|
|
/** Defective: AddBorderAndFill with O(N) scan each call → O(N²) total */
|
|
static int defectiveBorderDedup(int n) {
|
|
List<BorderRecord> maBorders = new ArrayList<>();
|
|
int ops = 0;
|
|
for (int i = 0; i < n; i++) {
|
|
BorderRecord rec = new BorderRecord(i);
|
|
boolean found = false;
|
|
for (BorderRecord b : maBorders) {
|
|
ops++;
|
|
if (b.equals(rec)) { found = true; break; }
|
|
}
|
|
if (!found) maBorders.add(rec);
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
/** Fixed: AddBorderAndFill with HashMap for O(1) key lookup → O(N) total */
|
|
static int fixedBorderDedup(int n) {
|
|
List<BorderRecord> maBorders = new ArrayList<>();
|
|
Map<Long, Integer> maBorderIndex = new HashMap<>();
|
|
int ops = 0;
|
|
for (int i = 0; i < n; i++) {
|
|
BorderRecord rec = new BorderRecord(i);
|
|
long key = ((long) rec.leftColor << 48)
|
|
^ ((long) rec.rightColor << 40)
|
|
^ ((long) rec.topColor << 32)
|
|
^ ((long) rec.bottomColor << 24)
|
|
^ ((long) rec.leftLine << 12)
|
|
^ ((long) rec.rightLine << 8)
|
|
^ ((long) rec.topLine << 4)
|
|
^ (long) rec.bottomLine;
|
|
ops++; // one hash lookup
|
|
if (!maBorderIndex.containsKey(key)) {
|
|
maBorderIndex.put(key, maBorders.size());
|
|
maBorders.add(rec);
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
int N = 500; // typical large spreadsheet style count
|
|
int N_max = 1000; // stress test
|
|
|
|
System.out.println("=== openoffice-0001: AddBorderAndFill O(N²) dedup ===");
|
|
System.out.printf("Testing N=%d unique border styles%n%n", N);
|
|
|
|
int defectOps = defectiveBorderDedup(N);
|
|
int fixedOps = fixedBorderDedup(N);
|
|
|
|
System.out.printf("Defective (O(N²)): %,d comparisons%n", defectOps);
|
|
System.out.printf("Fixed (O(N)): %,d hash lookups%n", fixedOps);
|
|
System.out.printf("Ratio: %.1fx%n%n", (double) defectOps / fixedOps);
|
|
|
|
// Verify correctness: both should deduplicate correctly
|
|
// Test with duplicates — 100 unique styles repeated 5 times
|
|
List<BorderRecord> defBorders = new ArrayList<>();
|
|
List<BorderRecord> fixBorders = new ArrayList<>();
|
|
Map<Long, Integer> fixIdx = new HashMap<>();
|
|
int M = 100;
|
|
for (int round = 0; round < 5; round++) {
|
|
for (int i = 0; i < M; i++) {
|
|
BorderRecord rec = new BorderRecord(i);
|
|
// defective path
|
|
boolean found = false;
|
|
for (BorderRecord b : defBorders) {
|
|
if (b.equals(rec)) { found = true; break; }
|
|
}
|
|
if (!found) defBorders.add(rec);
|
|
// fixed path
|
|
long key = ((long) rec.leftColor << 48) ^ ((long) rec.rightColor << 40)
|
|
^ ((long) rec.topColor << 32) ^ ((long) rec.bottomColor << 24)
|
|
^ ((long) rec.leftLine << 12) ^ (long) rec.bottomLine;
|
|
if (!fixIdx.containsKey(key)) {
|
|
fixIdx.put(key, fixBorders.size());
|
|
fixBorders.add(rec);
|
|
}
|
|
}
|
|
}
|
|
assert defBorders.size() == M : "defective dedup wrong: " + defBorders.size();
|
|
assert fixBorders.size() == M : "fixed dedup wrong: " + fixBorders.size();
|
|
System.out.printf("Dedup correctness: PASS (both yield %d unique borders from %d inputs)%n%n", M, M * 5);
|
|
|
|
// Benchmark at N_max
|
|
long t0 = System.nanoTime();
|
|
int defOps2 = defectiveBorderDedup(N_max);
|
|
long tDef = System.nanoTime() - t0;
|
|
|
|
t0 = System.nanoTime();
|
|
int fixOps2 = fixedBorderDedup(N_max);
|
|
long tFix = System.nanoTime() - t0;
|
|
|
|
System.out.printf("Benchmark N=%d:%n", N_max);
|
|
System.out.printf(" Defective: %,d ops, %d ms%n", defOps2, tDef / 1_000_000);
|
|
System.out.printf(" Fixed: %,d ops, %d ms%n", fixOps2, tFix / 1_000_000);
|
|
System.out.printf(" Op ratio: %.1fx%n", (double) defOps2 / fixOps2);
|
|
|
|
// Assertions for correctness
|
|
assert defOps2 > fixOps2 * 100 : "Expected at least 100x more ops in defective path";
|
|
assert defectOps > fixedOps * 100 : "Expected at least 100x more ops at N=" + N;
|
|
|
|
System.out.println("\nALL ASSERTIONS PASS");
|
|
}
|
|
}
|