libreoffice-0001: SavePivotTableXml member-to-cache O(M*C) std::find sc/source/filter/excel/xepivotxml.cxx SavePivotTableXml() for (member : aMembers) std::find(aCacheFieldItems) -> O(M*C) Fix: unordered_map<OUString,size_t> index built once -> O(M+C) Measured: 7.1x speedup at M=C=2000 calligra-0001: KoShapeManager addShape QList::contains O(N^2) dedup libs/flake/KoShapeManager.cpp addShape() called from setShapes() loop QList<KoShape*>::contains is O(N); N calls = O(N^2) total Fix: change d->shapes to QSet<KoShape*> for O(1) membership Measured: 12.6x speedup at N=5000 MOADs 0002-0005: CLEAN for both targets (see README.md per defect) Both unit tests: PASS
130 lines
5.3 KiB
Java
130 lines
5.3 KiB
Java
import java.util.*;
|
|
|
|
/**
|
|
* Unit test for libreoffice-0001: SavePivotTableXml member-to-cache O(M*C) linear scan.
|
|
*
|
|
* Defect: sc/source/filter/excel/xepivotxml.cxx SavePivotTableXml()
|
|
* for (member : aMembers)
|
|
* std::find(aCacheFieldItems.begin(), aCacheFieldItems.end(), member.maName)
|
|
*
|
|
* Fix: build std::unordered_map<OUString, size_t> from aCacheFieldItems once,
|
|
* then look up each member in O(1).
|
|
*
|
|
* Complexity: O(M*C) -> O(M+C) where M = member count, C = cache item count.
|
|
*/
|
|
public class LibreOfficePivotMemberLookupTest {
|
|
|
|
// --- Defective implementation: std::find equivalent ---
|
|
static int linearFind(List<String> cacheItems, String name) {
|
|
return cacheItems.indexOf(name); // O(C)
|
|
}
|
|
|
|
static List<Integer> buildMemberSequenceLinear(List<String> members, List<String> cacheItems) {
|
|
List<Integer> result = new ArrayList<>();
|
|
Set<Integer> used = new HashSet<>();
|
|
for (String member : members) {
|
|
int pos = linearFind(cacheItems, member); // O(C) each -> O(M*C) total
|
|
if (pos >= 0 && used.add(pos)) {
|
|
result.add(pos);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// --- Fixed implementation: unordered_map equivalent ---
|
|
static List<Integer> buildMemberSequenceFixed(List<String> members, List<String> cacheItems) {
|
|
// Build O(1) lookup map once
|
|
Map<String, Integer> indexMap = new HashMap<>(cacheItems.size() * 2);
|
|
for (int i = 0; i < cacheItems.size(); i++) {
|
|
indexMap.put(cacheItems.get(i), i);
|
|
}
|
|
List<Integer> result = new ArrayList<>();
|
|
Set<Integer> used = new HashSet<>();
|
|
for (String member : members) {
|
|
Integer pos = indexMap.get(member); // O(1) each
|
|
if (pos != null && used.add(pos)) {
|
|
result.add(pos);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// --- Benchmark ---
|
|
static long benchmarkLinear(int M, int C) {
|
|
List<String> cacheItems = new ArrayList<>(C);
|
|
for (int i = 0; i < C; i++) cacheItems.add("item_" + i);
|
|
List<String> members = new ArrayList<>(M);
|
|
for (int i = 0; i < M; i++) members.add("item_" + (i % C));
|
|
|
|
long start = System.nanoTime();
|
|
buildMemberSequenceLinear(members, cacheItems);
|
|
return System.nanoTime() - start;
|
|
}
|
|
|
|
static long benchmarkFixed(int M, int C) {
|
|
List<String> cacheItems = new ArrayList<>(C);
|
|
for (int i = 0; i < C; i++) cacheItems.add("item_" + i);
|
|
List<String> members = new ArrayList<>(M);
|
|
for (int i = 0; i < M; i++) members.add("item_" + (i % C));
|
|
|
|
long start = System.nanoTime();
|
|
buildMemberSequenceFixed(members, cacheItems);
|
|
return System.nanoTime() - start;
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
System.out.println("=== libreoffice-0001: SavePivotTableXml pivot member lookup ===");
|
|
|
|
// Correctness test: both must produce same result
|
|
{
|
|
List<String> cache = Arrays.asList("alpha", "beta", "gamma", "delta");
|
|
List<String> members = Arrays.asList("gamma", "alpha", "delta", "alpha");
|
|
List<Integer> linearResult = buildMemberSequenceLinear(members, cache);
|
|
List<Integer> fixedResult = buildMemberSequenceFixed(members, cache);
|
|
if (!linearResult.equals(fixedResult)) {
|
|
System.err.println("FAIL: correctness mismatch: linear=" + linearResult + " fixed=" + fixedResult);
|
|
System.exit(1);
|
|
}
|
|
// gamma=2, alpha=0, delta=3 (alpha duplicate skipped)
|
|
List<Integer> expected = Arrays.asList(2, 0, 3);
|
|
if (!linearResult.equals(expected)) {
|
|
System.err.println("FAIL: wrong result: got=" + linearResult + " expected=" + expected);
|
|
System.exit(1);
|
|
}
|
|
System.out.println("PASS: correctness verified, result=" + fixedResult);
|
|
}
|
|
|
|
// Missing member test
|
|
{
|
|
List<String> cache = Arrays.asList("x", "y", "z");
|
|
List<String> members = Arrays.asList("y", "missing", "x");
|
|
List<Integer> linearResult = buildMemberSequenceLinear(members, cache);
|
|
List<Integer> fixedResult = buildMemberSequenceFixed(members, cache);
|
|
if (!linearResult.equals(fixedResult)) {
|
|
System.err.println("FAIL: missing-member mismatch");
|
|
System.exit(1);
|
|
}
|
|
System.out.println("PASS: missing-member handled correctly: " + fixedResult);
|
|
}
|
|
|
|
// Performance test: M=2000, C=2000
|
|
int M = 2000, C = 2000;
|
|
// Warmup
|
|
for (int w = 0; w < 3; w++) { benchmarkLinear(M/10, C/10); benchmarkFixed(M/10, C/10); }
|
|
|
|
long linearNs = benchmarkLinear(M, C);
|
|
long fixedNs = benchmarkFixed(M, C);
|
|
double ratio = (double) linearNs / fixedNs;
|
|
|
|
System.out.printf("Linear M=%d C=%d: %,d ns%n", M, C, linearNs);
|
|
System.out.printf("Fixed M=%d C=%d: %,d ns%n", M, C, fixedNs);
|
|
System.out.printf("Speedup: %.1fx%n", ratio);
|
|
|
|
if (ratio < 3.0) {
|
|
System.err.printf("FAIL: expected >= 3x speedup, got %.1fx%n", ratio);
|
|
System.exit(1);
|
|
}
|
|
System.out.println("PASS: speedup >= 3x");
|
|
System.out.println("PASS: all tests passed");
|
|
}
|
|
}
|