/** * netty-0001: DnsResolveContext.finalResult ArrayList dedup O(R²) * * Demonstrates the defect: O(R²) dedup via ArrayList.contains vs O(R) with LinkedHashSet. * * Compile: javac DnsResolveContextDedupAlgorithm.java * Run: java DnsResolveContextDedupAlgorithm */ import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; public class DnsResolveContextDedupAlgorithm { // ---- DEFECT: O(R²) ArrayList dedup (mirrors DnsResolveContext lines ~906-916) ---- static List dedupArrayList(List responses) { List finalResult = null; int ops = 0; for (String converted : responses) { if (finalResult == null) { finalResult = new ArrayList<>(8); finalResult.add(converted); } else { // O(N) scan — this is the defect ops += finalResult.size(); if (!finalResult.contains(converted)) { finalResult.add(converted); } } } return finalResult; } // ---- FIX: O(R) LinkedHashSet dedup with preserved insertion order ---- static List dedupLinkedHashSet(List responses) { Set finalResult = new LinkedHashSet<>(8); for (String converted : responses) { finalResult.add(converted); // O(1) - returns false on duplicate, no scan needed } return new ArrayList<>(finalResult); } static long benchmarkArrayList(List input, int iterations) { long start = System.nanoTime(); for (int i = 0; i < iterations; i++) { List result = null; for (String s : input) { if (result == null) { result = new ArrayList<>(8); result.add(s); } else if (!result.contains(s)) { result.add(s); } } } return System.nanoTime() - start; } static long benchmarkLinkedHashSet(List input, int iterations) { long start = System.nanoTime(); for (int i = 0; i < iterations; i++) { Set result = new LinkedHashSet<>(8); for (String s : input) { result.add(s); } } return System.nanoTime() - start; } public static void main(String[] args) { System.out.println("=== netty-0001: DnsResolveContext finalResult dedup ==="); System.out.println(); // Simulate DNS resolution: 5 nameservers each returning 10 A records (some duplicates) // This mirrors what happens with multi-server failover or CNAME chain following List dnsResponses = new ArrayList<>(); // 10 unique IPs repeated across 5 server responses = 50 total records for (int server = 0; server < 5; server++) { for (int ip = 0; ip < 10; ip++) { dnsResponses.add("192.168." + server + "." + ip); // unique per server } } // Add duplicates (second server repeats some from first) for (int ip = 0; ip < 10; ip++) { dnsResponses.add("192.168.0." + ip); // duplicates of server-0 entries } System.out.printf("Input: %d DNS records (simulating 5 servers × 10 IPs + 10 duplicates)%n", dnsResponses.size()); System.out.println(); // Verify correctness List resultArrayList = dedupArrayList(dnsResponses); List resultLinkedHashSet = dedupLinkedHashSet(dnsResponses); System.out.printf("ArrayList result size: %d%n", resultArrayList.size()); System.out.printf("LinkedHashSet result size: %d%n", resultLinkedHashSet.size()); boolean correctSize = resultArrayList.size() == resultLinkedHashSet.size(); System.out.printf("Same result size: %s%n", correctSize ? "PASS" : "FAIL"); // Check order is preserved (LinkedHashSet maintains insertion order) boolean orderMatch = resultArrayList.equals(resultLinkedHashSet); System.out.printf("Same result order: %s%n", orderMatch ? "PASS" : "FAIL"); System.out.println(); // Benchmark with larger input to show O(R²) vs O(R) int iterations = 100_000; // Small case: 10 unique records (typical DNS response) List small = new ArrayList<>(); for (int i = 0; i < 10; i++) small.add("10.0.0." + i); // Medium case: 30 records (multi-server with some overlap) List medium = new ArrayList<>(); for (int i = 0; i < 30; i++) medium.add("10.0." + (i / 10) + "." + (i % 10)); // Large case: 50 records (CNAME chains + search domain retries) List large = new ArrayList<>(); for (int i = 0; i < 50; i++) large.add("10." + (i / 20) + "." + (i / 10 % 10) + "." + (i % 10)); System.out.println("--- Benchmarks (ns per iteration, " + iterations + " iterations) ---"); System.out.printf("%-12s %15s %15s %10s%n", "R (records)", "ArrayList O(R²)", "LinkedHashSet O(R)", "Speedup"); System.out.printf("%-12s %15s %15s %10s%n", "-----------", "---------------", "-----------------", "-------"); for (List input : new List[]{small, medium, large}) { // Warmup for (int w = 0; w < 1000; w++) { benchmarkArrayList(input, 1); benchmarkLinkedHashSet(input, 1); } long alTime = benchmarkArrayList(input, iterations); long lhsTime = benchmarkLinkedHashSet(input, iterations); double speedup = (double) alTime / lhsTime; System.out.printf("%-12d %15.0f %15.0f %9.1fx%n", input.size(), (double) alTime / iterations, (double) lhsTime / iterations, speedup); } System.out.println(); System.out.println("--- Algorithmic operation count ---"); System.out.printf("%-12s %15s %15s %10s%n", "R (records)", "ArrayList ops", "LinkedHashSet ops", "Ratio"); System.out.printf("%-12s %15s %15s %10s%n", "-----------", "-------------", "-----------------", "-----"); for (int r : new int[]{10, 20, 30, 50}) { int alOps = 0; for (int i = 1; i < r; i++) alOps += i; // sum of 0..r-1 = r*(r-1)/2 int lhsOps = r; System.out.printf("%-12d %15d %15d %9.1fx%n", r, alOps, lhsOps, (double) alOps / lhsOps); } System.out.println(); if (correctSize && orderMatch) { System.out.println("RESULT: PASS — fix is correct and faster"); } else { System.out.println("RESULT: FAIL"); System.exit(1); } } }