239 lines
10 KiB
Java
239 lines
10 KiB
Java
package unit;
|
||
|
||
import java.util.*;
|
||
|
||
/**
|
||
* foundationdb-0001: canLaunchSrc std::count nested loop O(S × R × S')
|
||
*
|
||
* Models the data structures in DDRelocationQueue.actor.cpp:
|
||
* outer loop: relocation.src (S servers)
|
||
* inner loop: cancellableRelocations (R entries)
|
||
* innermost: std::count on servers vector (S')
|
||
*
|
||
* Compile: javac -d . FoundationdbTest.java
|
||
* Run: java unit.FoundationdbTest
|
||
*/
|
||
public class FoundationdbTest {
|
||
|
||
// Simulate a UID (storage server ID)
|
||
static class UID {
|
||
final long v;
|
||
UID(long v) { this.v = v; }
|
||
@Override public boolean equals(Object o) {
|
||
return o instanceof UID && ((UID)o).v == v;
|
||
}
|
||
@Override public int hashCode() { return Long.hashCode(v); }
|
||
@Override public String toString() { return "UID(" + v + ")"; }
|
||
}
|
||
|
||
// Simulate RelocateData.src (list of source server UIDs)
|
||
static class RelocateData {
|
||
final List<UID> src;
|
||
final int priority;
|
||
final int workFactor;
|
||
RelocateData(List<UID> src, int priority, int workFactor) {
|
||
this.src = src; this.priority = priority; this.workFactor = workFactor;
|
||
}
|
||
}
|
||
|
||
// Simulate Busyness.addWork / removeWork
|
||
static class Busyness {
|
||
int load = 0;
|
||
Busyness(int load) { this.load = load; }
|
||
Busyness copy() { return new Busyness(load); }
|
||
void removeWork(int priority, int workFactor) { load = Math.max(0, load - workFactor); }
|
||
boolean canLaunch(int priority, int workFactor) { return load + workFactor <= 100; }
|
||
}
|
||
|
||
// ---- DEFECTIVE implementation (O(S × R × S')) ----
|
||
static boolean canLaunchSrc_defective(
|
||
RelocateData relocation,
|
||
Map<UID, Busyness> busymap,
|
||
List<RelocateData> cancellableRelocations,
|
||
int[] comparisonCount) {
|
||
int workFactor = 10;
|
||
int neededServers = Math.max(1, relocation.src.size() - 3 + 1);
|
||
|
||
for (int i = 0; i < relocation.src.size(); i++) { // O(S)
|
||
Busyness busyCopy = busymap.get(relocation.src.get(i)).copy();
|
||
for (int j = 0; j < cancellableRelocations.size(); j++) { // O(R)
|
||
List<UID> servers = cancellableRelocations.get(j).src;
|
||
// std::count equivalent — O(S') linear scan
|
||
boolean found = false;
|
||
for (UID uid : servers) { // O(S')
|
||
comparisonCount[0]++;
|
||
if (uid.equals(relocation.src.get(i))) { found = true; break; }
|
||
}
|
||
if (found) {
|
||
busyCopy.removeWork(cancellableRelocations.get(j).priority,
|
||
cancellableRelocations.get(j).workFactor);
|
||
}
|
||
}
|
||
if (busyCopy.canLaunch(relocation.priority, workFactor)) {
|
||
--neededServers;
|
||
if (neededServers == 0) return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// ---- FIXED implementation: pre-build map, O(R×S' + S) ----
|
||
static boolean canLaunchSrc_fixed(
|
||
RelocateData relocation,
|
||
Map<UID, Busyness> busymap,
|
||
List<RelocateData> cancellableRelocations,
|
||
int[] comparisonCount) {
|
||
int workFactor = 10;
|
||
int neededServers = Math.max(1, relocation.src.size() - 3 + 1);
|
||
|
||
// Build inverted index: UID -> list of cancellable relocation indices O(R×S')
|
||
Map<UID, List<Integer>> cancellableByServer = new HashMap<>();
|
||
for (int j = 0; j < cancellableRelocations.size(); j++) {
|
||
for (UID uid : cancellableRelocations.get(j).src) {
|
||
comparisonCount[0]++;
|
||
cancellableByServer.computeIfAbsent(uid, k -> new ArrayList<>()).add(j);
|
||
}
|
||
}
|
||
|
||
for (int i = 0; i < relocation.src.size(); i++) { // O(S)
|
||
Busyness busyCopy = busymap.get(relocation.src.get(i)).copy();
|
||
List<Integer> indices = cancellableByServer.get(relocation.src.get(i));
|
||
if (indices != null) {
|
||
for (int j : indices) {
|
||
busyCopy.removeWork(cancellableRelocations.get(j).priority,
|
||
cancellableRelocations.get(j).workFactor);
|
||
}
|
||
}
|
||
if (busyCopy.canLaunch(relocation.priority, workFactor)) {
|
||
--neededServers;
|
||
if (neededServers == 0) return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// Build a test scenario
|
||
static Map<UID, Busyness> buildBusymap(List<UID> allServers) {
|
||
Map<UID, Busyness> map = new HashMap<>();
|
||
for (UID uid : allServers) map.put(uid, new Busyness(50));
|
||
return map;
|
||
}
|
||
|
||
public static void main(String[] args) {
|
||
int pass = 0, fail = 0;
|
||
|
||
// -- Test 1: correctness — small scenario
|
||
{
|
||
List<UID> allServers = new ArrayList<>();
|
||
for (int i = 0; i < 5; i++) allServers.add(new UID(i));
|
||
|
||
// The relocation being considered
|
||
RelocateData candidate = new RelocateData(
|
||
Arrays.asList(allServers.get(0), allServers.get(1), allServers.get(2)),
|
||
100, 10);
|
||
|
||
// Some cancellable in-flight moves that involve the same servers
|
||
List<RelocateData> cancellable = new ArrayList<>();
|
||
cancellable.add(new RelocateData(Arrays.asList(allServers.get(0), allServers.get(3)), 50, 30));
|
||
cancellable.add(new RelocateData(Arrays.asList(allServers.get(1), allServers.get(4)), 50, 30));
|
||
|
||
Map<UID, Busyness> busymap = buildBusymap(allServers);
|
||
|
||
int[] cmpDefective = {0};
|
||
int[] cmpFixed = {0};
|
||
boolean resultDef = canLaunchSrc_defective(candidate, busymap, cancellable, cmpDefective);
|
||
boolean resultFix = canLaunchSrc_fixed(candidate, busymap, cancellable, cmpFixed);
|
||
|
||
if (resultDef == resultFix) {
|
||
System.out.printf("PASS test1: correctness — defective=%b fixed=%b%n", resultDef, resultFix);
|
||
pass++;
|
||
} else {
|
||
System.out.printf("FAIL test1: defective=%b fixed=%b%n", resultDef, resultFix);
|
||
fail++;
|
||
}
|
||
}
|
||
|
||
// -- Test 2: complexity — large R queue demonstrates O(S×R×S') vs O(R×S' + S)
|
||
// Use servers at max load so canLaunch always fails, forcing the full loop
|
||
{
|
||
int S = 3; // team size (relocation.src)
|
||
int R = 500; // queue depth (cancellable relocations)
|
||
int S2 = 3; // src team size of each cancellable relocation
|
||
|
||
// Need enough servers: 3 (candidate) + R*S2 (cancellable) = 3 + 500*3 = 1503
|
||
List<UID> servers = new ArrayList<>();
|
||
for (int i = 0; i < 3000; i++) servers.add(new UID(i));
|
||
|
||
RelocateData candidate = new RelocateData(
|
||
servers.subList(0, S), 100, 10);
|
||
|
||
// cancellable relocations — use disjoint servers so no work is removed
|
||
// and servers stay busy, forcing all S iterations to complete
|
||
List<RelocateData> cancellable = new ArrayList<>();
|
||
for (int j = 0; j < R; j++) {
|
||
int base = 100 + j * S2; // disjoint from candidate servers (0,1,2)
|
||
cancellable.add(new RelocateData(
|
||
servers.subList(base, base + S2), 50, 5));
|
||
}
|
||
|
||
// Overload the source servers so canLaunch never returns true early
|
||
Map<UID, Busyness> busymapDef = new HashMap<>();
|
||
Map<UID, Busyness> busymapFix = new HashMap<>();
|
||
for (UID uid : servers) {
|
||
busymapDef.put(uid, new Busyness(95)); // high load — can't launch
|
||
busymapFix.put(uid, new Busyness(95));
|
||
}
|
||
|
||
int[] cmpDefective = {0};
|
||
int[] cmpFixed = {0};
|
||
|
||
boolean resDef = canLaunchSrc_defective(candidate, busymapDef, cancellable, cmpDefective);
|
||
boolean resFix = canLaunchSrc_fixed(candidate, busymapFix, cancellable, cmpFixed);
|
||
|
||
System.out.printf("test2: defective comparisons=%d fixed comparisons=%d (S=%d R=%d S2=%d)%n",
|
||
cmpDefective[0], cmpFixed[0], S, R, S2);
|
||
System.out.printf("test2: defective result=%b fixed result=%b%n", resDef, resFix);
|
||
|
||
// Fixed must produce same result with fewer comparisons (no S multiplier)
|
||
// Defective: S * R * S2 = 3 * 500 * 3 = 4500 comparisons
|
||
// Fixed: R * S2 (build) = 500 * 3 = 1500 comparisons (no outer S factor)
|
||
boolean sameResult = (resDef == resFix);
|
||
boolean moreEfficient = cmpFixed[0] < cmpDefective[0];
|
||
if (sameResult && moreEfficient) {
|
||
System.out.println("PASS test2: fixed is more efficient and produces same result");
|
||
pass++;
|
||
} else {
|
||
System.out.printf("FAIL test2: sameResult=%b moreEfficient=%b%n", sameResult, moreEfficient);
|
||
fail++;
|
||
}
|
||
|
||
int expectedDefective = S * R * S2;
|
||
int expectedFixed = R * S2; // build phase dominates
|
||
System.out.printf(" Expected defective ~O(S×R×S')=%d, actual=%d%n", expectedDefective, cmpDefective[0]);
|
||
System.out.printf(" Expected fixed ~O(R×S')=%d, actual=%d%n", expectedFixed, cmpFixed[0]);
|
||
}
|
||
|
||
// -- Test 3: no cancellable relocations — both return same result
|
||
{
|
||
List<UID> servers = new ArrayList<>();
|
||
for (int i = 0; i < 3; i++) servers.add(new UID(i));
|
||
RelocateData candidate = new RelocateData(servers, 100, 5);
|
||
Map<UID, Busyness> busymap = buildBusymap(servers);
|
||
|
||
int[] c1 = {0}, c2 = {0};
|
||
boolean r1 = canLaunchSrc_defective(candidate, busymap, new ArrayList<>(), c1);
|
||
boolean r2 = canLaunchSrc_fixed(candidate, busymap, new ArrayList<>(), c2);
|
||
|
||
if (r1 == r2) {
|
||
System.out.printf("PASS test3: empty cancellable — both=%b%n", r1);
|
||
pass++;
|
||
} else {
|
||
System.out.printf("FAIL test3: mismatch on empty cancellable%n");
|
||
fail++;
|
||
}
|
||
}
|
||
|
||
System.out.printf("%nResults: %d passed, %d failed%n", pass, fail);
|
||
if (fail > 0) System.exit(1);
|
||
}
|
||
}
|