230 lines
9.2 KiB
Java
230 lines
9.2 KiB
Java
package unit;
|
||
|
||
import java.util.*;
|
||
|
||
/**
|
||
* grpc-0001: channelz PropertyGrid GetIndex() std::find O(C^2) → O(1) with HashMap
|
||
*
|
||
* Simulates the defective pattern from grpc/src/core/channelz/property_list.cc:
|
||
* GetIndex(std::vector<std::string>& vec, value) does std::find on the vector
|
||
* to find or insert a column/row name. Called for every Set(col, row, val).
|
||
* With C distinct columns and R distinct rows: O(C^2) + O(R^2) total.
|
||
*
|
||
* Fixed: maintain a parallel HashMap<String,Integer> for O(1) index lookup.
|
||
*
|
||
* Run: javac -d . GrpcPropertyGridAlgorithm.java && java unit.GrpcPropertyGridAlgorithm
|
||
*/
|
||
public class GrpcPropertyGridAlgorithm {
|
||
|
||
// ---- Result ----
|
||
static class Result {
|
||
final long slowOps;
|
||
final long fastOps;
|
||
final int columns;
|
||
final int rows;
|
||
Result(long slowOps, long fastOps, int columns, int rows) {
|
||
this.slowOps = slowOps;
|
||
this.fastOps = fastOps;
|
||
this.columns = columns;
|
||
this.rows = rows;
|
||
}
|
||
}
|
||
|
||
// ---- Defective: std::find on vector — O(N) per lookup ----
|
||
static class DefectivePropertyGrid {
|
||
final List<String> columns = new ArrayList<>();
|
||
final List<String> rows = new ArrayList<>();
|
||
final Map<String, String> grid = new HashMap<>();
|
||
long opCount = 0;
|
||
|
||
// Simulates: size_t GetIndex(std::vector<std::string>& vec, value)
|
||
int getIndex(List<String> vec, String value) {
|
||
for (int i = 0; i < vec.size(); i++) {
|
||
opCount++;
|
||
if (vec.get(i).equals(value)) return i;
|
||
}
|
||
// Not found: insert
|
||
opCount++; // one more for the end-of-list check
|
||
vec.add(value);
|
||
return vec.size() - 1;
|
||
}
|
||
|
||
void set(String column, String row, String value) {
|
||
int c = getIndex(columns, column);
|
||
int r = getIndex(rows, row);
|
||
grid.put(c + "," + r, value);
|
||
}
|
||
}
|
||
|
||
// ---- Fixed: HashMap O(1) lookup + ordered vector for iteration ----
|
||
static class FixedPropertyGrid {
|
||
final List<String> columns = new ArrayList<>();
|
||
final List<String> rows = new ArrayList<>();
|
||
final Map<String, Integer> columnsMap = new HashMap<>();
|
||
final Map<String, Integer> rowsMap = new HashMap<>();
|
||
final Map<String, String> grid = new HashMap<>();
|
||
long opCount = 0;
|
||
|
||
// Simulates fixed GetIndex with parallel HashMap
|
||
int getIndex(List<String> vec, Map<String, Integer> map, String value) {
|
||
opCount++; // one hash lookup
|
||
Integer idx = map.get(value);
|
||
if (idx == null) {
|
||
idx = vec.size();
|
||
vec.add(value);
|
||
map.put(value, idx);
|
||
}
|
||
return idx;
|
||
}
|
||
|
||
void set(String column, String row, String value) {
|
||
int c = getIndex(columns, columnsMap, column);
|
||
int r = getIndex(rows, rowsMap, row);
|
||
grid.put(c + "," + r, value);
|
||
}
|
||
}
|
||
|
||
// ---- check helper ----
|
||
static int passed = 0;
|
||
static int total = 0;
|
||
|
||
static void check(String name, boolean condition) {
|
||
total++;
|
||
if (condition) {
|
||
passed++;
|
||
System.out.println(" PASS: " + name);
|
||
} else {
|
||
System.out.println(" FAIL: " + name);
|
||
}
|
||
}
|
||
|
||
public static void main(String[] args) {
|
||
System.out.println("grpc-0001: channelz PropertyGrid GetIndex O(C^2) -> O(1)");
|
||
System.out.println();
|
||
|
||
// ---- Test 1: Small grid (5 columns, 5 rows) ----
|
||
{
|
||
int C = 5, R = 5;
|
||
DefectivePropertyGrid slow = new DefectivePropertyGrid();
|
||
FixedPropertyGrid fast = new FixedPropertyGrid();
|
||
|
||
for (int r = 0; r < R; r++) {
|
||
for (int c = 0; c < C; c++) {
|
||
String col = "col_" + c;
|
||
String row = "row_" + r;
|
||
String val = "v" + c + "_" + r;
|
||
slow.set(col, row, val);
|
||
fast.set(col, row, val);
|
||
}
|
||
}
|
||
|
||
System.out.println("Test 1: " + C + "x" + R + " grid");
|
||
System.out.println(" Slow ops: " + slow.opCount);
|
||
System.out.println(" Fast ops: " + fast.opCount);
|
||
check("slow > fast (vector scan > hash lookup)", slow.opCount > fast.opCount);
|
||
check("grid sizes match", slow.grid.size() == fast.grid.size());
|
||
}
|
||
|
||
// ---- Test 2: Medium grid (50 columns, 50 rows) — O(C^2) becomes clear ----
|
||
{
|
||
int C = 50, R = 50;
|
||
DefectivePropertyGrid slow = new DefectivePropertyGrid();
|
||
FixedPropertyGrid fast = new FixedPropertyGrid();
|
||
|
||
// First pass: register all columns and rows
|
||
for (int c = 0; c < C; c++) {
|
||
for (int r = 0; r < R; r++) {
|
||
slow.set("col_" + c, "row_" + r, "val");
|
||
fast.set("col_" + c, "row_" + r, "val");
|
||
}
|
||
}
|
||
|
||
System.out.println("Test 2: " + C + "x" + R + " grid (O(C^2) regime)");
|
||
System.out.println(" Slow ops: " + slow.opCount);
|
||
System.out.println(" Fast ops: " + fast.opCount);
|
||
double ratio = (double) slow.opCount / fast.opCount;
|
||
System.out.printf(" Ratio slow/fast: %.1fx%n", ratio);
|
||
check("slow > 10x fast ops at C=R=50", ratio > 10.0);
|
||
}
|
||
|
||
// ---- Test 3: High-frequency property updates (same columns, many rows) ----
|
||
// Simulates 1000 RPC calls, each setting 10 metrics across 20 channels
|
||
{
|
||
int calls = 1000;
|
||
int metrics = 10;
|
||
DefectivePropertyGrid slow = new DefectivePropertyGrid();
|
||
FixedPropertyGrid fast = new FixedPropertyGrid();
|
||
|
||
String[] metricNames = new String[metrics];
|
||
for (int i = 0; i < metrics; i++) metricNames[i] = "metric_" + i;
|
||
|
||
for (int call = 0; call < calls; call++) {
|
||
String rowName = "call_" + call;
|
||
for (String metric : metricNames) {
|
||
slow.set(metric, rowName, String.valueOf(call));
|
||
fast.set(metric, rowName, String.valueOf(call));
|
||
}
|
||
}
|
||
|
||
System.out.println("Test 3: " + calls + " RPC calls × " + metrics + " metrics");
|
||
System.out.println(" Slow ops: " + slow.opCount);
|
||
System.out.println(" Fast ops: " + fast.opCount);
|
||
double ratio = (double) slow.opCount / fast.opCount;
|
||
System.out.printf(" Ratio slow/fast: %.1fx%n", ratio);
|
||
// With 1000 rows and 10 cols: rows_ scan grows O(rows_count) per new row
|
||
// column lookups are O(10) but row lookups are O(0..1000) → O(R^2/2) total
|
||
check("slow > 100x fast ops for 1000 rows", ratio > 100.0);
|
||
}
|
||
|
||
// ---- Test 4: Correctness — column/row indices agree ----
|
||
{
|
||
DefectivePropertyGrid slow = new DefectivePropertyGrid();
|
||
FixedPropertyGrid fast = new FixedPropertyGrid();
|
||
|
||
String[] cols = {"alpha", "beta", "gamma", "delta"};
|
||
String[] rowNames = {"r1", "r2", "r3"};
|
||
|
||
for (String r : rowNames) {
|
||
for (String c : cols) {
|
||
slow.set(c, r, c + "_" + r);
|
||
fast.set(c, r, c + "_" + r);
|
||
}
|
||
}
|
||
|
||
// Verify column and row order matches
|
||
boolean colMatch = slow.columns.equals(fast.columns);
|
||
boolean rowMatch = slow.rows.equals(fast.rows);
|
||
System.out.println("Test 4: correctness — column/row order");
|
||
System.out.println(" Slow columns: " + slow.columns);
|
||
System.out.println(" Fast columns: " + fast.columns);
|
||
check("column order preserved in fixed version", colMatch);
|
||
check("row order preserved in fixed version", rowMatch);
|
||
}
|
||
|
||
// ---- Test 5: O(C^2) scaling — measure column scan ops only (R=1 to isolate) ----
|
||
{
|
||
// With R=1, only column scanning grows. Each new column triggers a scan of
|
||
// all prior columns. Total column ops = 0+1+2+...+(C-1) = C*(C-1)/2.
|
||
// Doubling C quadruples these ops: C1^2/2 → C2^2/2 → 4x.
|
||
|
||
int R = 1; // single row to isolate column-only O(C^2)
|
||
|
||
DefectivePropertyGrid slow1 = new DefectivePropertyGrid();
|
||
DefectivePropertyGrid slow2 = new DefectivePropertyGrid();
|
||
|
||
int C1 = 50, C2 = 100;
|
||
for (int c = 0; c < C1; c++) slow1.set("col_" + c, "row_0", "v");
|
||
for (int c = 0; c < C2; c++) slow2.set("col_" + c, "row_0", "v");
|
||
|
||
System.out.println("Test 5: O(C^2) column scaling — C=" + C1 + " vs C=" + C2 + " (R=1)");
|
||
System.out.println(" Ops at C=" + C1 + ": " + slow1.opCount);
|
||
System.out.println(" Ops at C=" + C2 + ": " + slow2.opCount);
|
||
double scalingRatio = (double) slow2.opCount / slow1.opCount;
|
||
System.out.printf(" Scaling: %.2fx (expect ~4x for O(C^2))%n", scalingRatio);
|
||
check("ops grow ~4x when columns doubled (O(C^2) confirmed)", scalingRatio > 3.5);
|
||
}
|
||
|
||
System.out.println();
|
||
System.out.println(passed + "/" + total + " PASS");
|
||
}
|
||
}
|