171 lines
7.1 KiB
Java
171 lines
7.1 KiB
Java
package unit;
|
||
|
||
import java.util.*;
|
||
|
||
/**
|
||
* victoria-metrics-0001 — streamaggr: slices.Contains O(L×K) inside Push() hot loop
|
||
*
|
||
* Models lib/streamaggr/streamaggr.go:
|
||
* func (a *aggregator) Push(tss []prompb.TimeSeries, ...) {
|
||
* for _, ts := range tss {
|
||
* labels.Labels = dropSeriesLabels(labels.Labels, ts.Labels, dropLabels)
|
||
* getInputOutputLabels(..., a.by, a.without)
|
||
* }
|
||
* }
|
||
*
|
||
* dropSeriesLabels — for each label, calls slices.Contains(labelNames, label.Name) → O(K)
|
||
* getInputOutputLabels — for each label, calls slices.Contains(without/by, label.Name) → O(K)
|
||
*
|
||
* Defective: O(N × L × K) total comparisons
|
||
* Fixed: O(N × L) using pre-built map[string]struct{} (built once, O(K))
|
||
*
|
||
* Ratio >= 10× at N=1000, L=20, K=20
|
||
*
|
||
* No JUnit. Uses assert. Prints N/N PASS.
|
||
*
|
||
* Compile: javac -d . VictoriaMetricsStreamAggrAlgorithm.java
|
||
* Run: java -ea -cp . unit.VictoriaMetricsStreamAggrAlgorithm
|
||
*/
|
||
public class VictoriaMetricsStreamAggrAlgorithm {
|
||
|
||
static int passed = 0;
|
||
static int total = 0;
|
||
|
||
static void check(String desc, boolean cond) {
|
||
total++;
|
||
if (cond) {
|
||
passed++;
|
||
System.out.println("PASS: " + desc);
|
||
} else {
|
||
System.out.println("FAIL: " + desc);
|
||
throw new AssertionError("FAIL: " + desc);
|
||
}
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// Slow: models dropSeriesLabels + getInputOutputLabels with slices.Contains
|
||
// For each of N series, for each of L labels, scans K-element filter list
|
||
// Returns total comparison operations performed.
|
||
// -----------------------------------------------------------------------
|
||
static long pushSlow(int N, int L, List<String> filterList) {
|
||
long ops = 0;
|
||
int K = filterList.size();
|
||
// Simulate N time series, each with L labels named "label_0".."label_{L-1}"
|
||
// filterList contains the "by" or "without" set (some subset of label names)
|
||
for (int s = 0; s < N; s++) {
|
||
for (int l = 0; l < L; l++) {
|
||
String labelName = "label_" + l;
|
||
// slices.Contains: O(K) linear scan
|
||
for (int k = 0; k < K; k++) {
|
||
ops++;
|
||
if (filterList.get(k).equals(labelName)) {
|
||
break; // found — early exit (worst case: not found, full K ops)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// Fast: pre-build map[string]struct{} once, then O(1) per label per series
|
||
// Returns total comparison operations performed.
|
||
// -----------------------------------------------------------------------
|
||
static long pushFast(int N, int L, List<String> filterList) {
|
||
long ops = 0;
|
||
// Build set once — O(K)
|
||
Set<String> filterSet = new HashSet<>(filterList);
|
||
ops += filterList.size(); // cost of building the set
|
||
|
||
// Now O(1) per label lookup
|
||
for (int s = 0; s < N; s++) {
|
||
for (int l = 0; l < L; l++) {
|
||
String labelName = "label_" + l;
|
||
ops++; // O(1) hash lookup
|
||
filterSet.contains(labelName);
|
||
}
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// Correctness: both methods produce the same classification of labels
|
||
// -----------------------------------------------------------------------
|
||
static List<String> classifyLabelsSlice(List<String> labels, List<String> byList) {
|
||
List<String> output = new ArrayList<>();
|
||
for (String label : labels) {
|
||
boolean inBy = false;
|
||
for (String b : byList) {
|
||
if (b.equals(label)) { inBy = true; break; }
|
||
}
|
||
if (inBy) output.add(label); // output = "by" labels
|
||
}
|
||
return output;
|
||
}
|
||
|
||
static List<String> classifyLabelsMap(List<String> labels, List<String> byList) {
|
||
Set<String> bySet = new HashSet<>(byList);
|
||
List<String> output = new ArrayList<>();
|
||
for (String label : labels) {
|
||
if (bySet.contains(label)) output.add(label);
|
||
}
|
||
return output;
|
||
}
|
||
|
||
public static void main(String[] args) {
|
||
System.out.println("victoria-metrics-0001: streamaggr label filter O(N×L×K) → O(N×L)");
|
||
System.out.println();
|
||
|
||
// --- Correctness ---
|
||
List<String> labels = Arrays.asList("label_0","label_1","label_2","label_3","label_4");
|
||
List<String> byList = Arrays.asList("label_1","label_3");
|
||
|
||
List<String> slowResult = classifyLabelsSlice(labels, byList);
|
||
List<String> fastResult = classifyLabelsMap(labels, byList);
|
||
|
||
check("correctness: same labels classified", slowResult.equals(fastResult));
|
||
check("correctness: 2 labels in 'by' set", slowResult.size() == 2);
|
||
check("correctness: label_1 in output", slowResult.contains("label_1"));
|
||
check("correctness: label_3 in output", slowResult.contains("label_3"));
|
||
check("correctness: label_0 not in output", !slowResult.contains("label_0"));
|
||
|
||
// --- Performance ---
|
||
// N=1000 series, L=20 labels each, K=20 filter entries (worst case: none match → full scan)
|
||
int N = 1000, L = 20, K = 20;
|
||
|
||
// Build a filter list of 20 entries NOT matching any of our L labels
|
||
// (worst case — label not found means full K comparisons every time)
|
||
List<String> filterList = new ArrayList<>();
|
||
for (int k = 0; k < K; k++) filterList.add("filter_" + k);
|
||
|
||
long slowOps = pushSlow(N, L, filterList);
|
||
long fastOps = pushFast(N, L, filterList);
|
||
|
||
double ratio = (double) slowOps / fastOps;
|
||
System.out.println(" N=" + N + " series, L=" + L + " labels, K=" + K + " filters");
|
||
System.out.println(" Slow ops: " + slowOps);
|
||
System.out.println(" Fast ops: " + fastOps);
|
||
System.out.printf (" Ratio: %.1fx%n", ratio);
|
||
System.out.println();
|
||
|
||
check("ops: slow is O(N*L*K) = " + (N*L*K), slowOps == (long) N * L * K);
|
||
check("ops: fast is ~O(N*L+K) < 2*N*L", fastOps <= (long) 2 * N * L + K);
|
||
check("speedup >= 10x at N=1000/L=20/K=20", ratio >= 10.0);
|
||
|
||
// Scale: larger cluster
|
||
N = 10000; L = 30; K = 20;
|
||
long slowOps2 = pushSlow(N, L, filterList); // reuse same filterList (K=20 entries)
|
||
List<String> filterList2 = new ArrayList<>();
|
||
for (int k = 0; k < K; k++) filterList2.add("filter_" + k);
|
||
long fastOps2 = pushFast(N, L, filterList2);
|
||
double ratio2 = (double) slowOps2 / fastOps2;
|
||
System.out.println(" Scale test: N=" + N + " series, L=" + L + " labels, K=" + K);
|
||
System.out.println(" Slow ops: " + slowOps2);
|
||
System.out.println(" Fast ops: " + fastOps2);
|
||
System.out.printf (" Ratio: %.1fx%n", ratio2);
|
||
System.out.println();
|
||
check("scale: speedup >= 10x at N=10000/L=30/K=20", ratio2 >= 10.0);
|
||
|
||
System.out.println(passed + "/" + total + " PASS");
|
||
}
|
||
}
|