java-topology/defects/grpc/unit/GrpcPropertyGridAlgorithm.java

230 lines
9.2 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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");
}
}