From 05eae10a73c72b697e014c923313942d94c52823 Mon Sep 17 00:00:00 2001 From: ahmed Date: Wed, 8 Apr 2026 17:30:05 +0300 Subject: [PATCH] Expanding test --- Tests/Serialization/Gvwie/GeneratorPattern.cs | 16 ++ .../Serialization/Gvwie/IntArrayGenerator.cs | 205 ++++++++++-------- Tests/Serialization/Gvwie/IntArrayRunner.cs | 161 +++++++++++--- Tests/Serialization/Gvwie/Program.cs | 1 + 4 files changed, 269 insertions(+), 114 deletions(-) create mode 100644 Tests/Serialization/Gvwie/GeneratorPattern.cs diff --git a/Tests/Serialization/Gvwie/GeneratorPattern.cs b/Tests/Serialization/Gvwie/GeneratorPattern.cs new file mode 100644 index 0000000..d365e6d --- /dev/null +++ b/Tests/Serialization/Gvwie/GeneratorPattern.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Esiur.Tests.Gvwie +{ + public enum GeneratorPattern + { + Uniform, + Positive, + Negative, + Alternating, + Small, + Ascending, + } +} diff --git a/Tests/Serialization/Gvwie/IntArrayGenerator.cs b/Tests/Serialization/Gvwie/IntArrayGenerator.cs index d9b6768..6f1899b 100644 --- a/Tests/Serialization/Gvwie/IntArrayGenerator.cs +++ b/Tests/Serialization/Gvwie/IntArrayGenerator.cs @@ -8,107 +8,142 @@ namespace Esiur.Tests.Gvwie; public static class IntArrayGenerator { + + + private static readonly Random rng = new Random(24241564); - - public static long[] GenerateInt32Run(int length) + /// + /// Generate an array composed of ascending runs (consecutive integers). + /// Example output: [1,2,3,4,5, 5001,5002,5003, 10000001,10000002,...] + /// Parameters: + /// - length: total array length + /// - minRunSize / maxRunSize: inclusive bounds for run lengths + /// - minValue / maxValue: allowed value range for run starts + /// - allowNegative: if false, generated values will be non-negative + /// - minGap / maxGap: approximate gap between runs (large gaps produce the jump examples) + /// + public static long[] GenerateRuns(int length, + int minRunSize = 3, + int maxRunSize = 8, + long minValue = -10_000_000L, + long maxValue = 10_000_000L, + bool allowNegative = true, + long minGap = 1_000L, + long maxGap = 10_000_000L) { + if (length <= 0) + return Array.Empty(); + + if (minRunSize < 1) minRunSize = 1; + if (maxRunSize < minRunSize) maxRunSize = minRunSize; + + // If negative runs not allowed, clamp minValue to 0 + if (!allowNegative && minValue < 0) minValue = 0; + var data = new long[length]; + int idx = 0; + long prevEnd = long.MinValue; - int i = 0; - var inSmallRange = true; - var inShortRange = false; - var inLargeRange = false; - var inLongRange = false; - - long range = 30; - - while (i < length) + while (idx < length) { - // stay same range - if (rng.NextDouble() < 0.9) - { - if (inSmallRange) - data[i++] = rng.Next(-64, 65); - else if (inShortRange) - data[i++] = rng.NextInt64(range - 100, range + 100); - else if (inLargeRange) - data[i++] = rng.NextInt64(range - 1000, range + 1000); - else if (inLongRange) - data[i++] = rng.NextInt64(range - 10000, range + 10000); - } - else - { - // switch range - var rand = rng.NextDouble(); - if (rand < 0.25) - { - inSmallRange = true; - inShortRange = false; - inLargeRange = false; - inLongRange = false; - data[i++] = rng.Next(-64, 65); - } - else if (rand < 0.50) - { - inSmallRange = false; - inShortRange = true; - inLargeRange = false; - inLongRange = false; - range = rng.NextInt64(1000, short.MaxValue); - data[i++] = rng.NextInt64(range - 100, range + 100); - } - else if (rand < 0.75) - { - inSmallRange = false; - inShortRange = false; - inLargeRange = true; - inLongRange = false; - range = rng.NextInt64(1000, int.MaxValue); - data[i++] = rng.NextInt64(range - 1000, range + 1000); - } - else - { - inSmallRange = false; - inShortRange = false; - inLargeRange = false; - inLongRange = true; - range = rng.NextInt64(10000, long.MaxValue); - data[i++] = rng.NextInt64(range - 10000, range + 10000); + // choose run size + int runSize = rng.Next(minRunSize, maxRunSize + 1); + if (idx + runSize > length) + runSize = length - idx; + // pick a start. Aim for gaps between runs by either taking a random value + // or basing on previous end + gap. Try a few times to avoid accidental small gaps. + long start = 0; + long attemptUpper = maxValue - runSize; // inclusive exclusive handled by NextInt64 + if (attemptUpper < minValue) attemptUpper = minValue; + + bool picked = false; + for (int attempt = 0; attempt < 10 && !picked; attempt++) + { + // decide whether to use a jump based on minGap/maxGap or pick random + if (prevEnd != long.MinValue && rng.NextDouble() < 0.7) + { + // generate a gap and place start after prevEnd + gap + long gap = rng.NextInt64(minGap, Math.Max(minGap + 1, maxGap)); + long candidate = prevEnd + gap; + // if candidate within allowed bounds adjust to fit + if (candidate >= minValue && candidate <= attemptUpper) + { + start = candidate; + picked = true; + break; + } + } + + // fallback: pick random start in allowed bounds + start = rng.NextInt64(minValue, attemptUpper + 1); + // avoid being too close to previous run end if present + if (prevEnd == long.MinValue || Math.Abs(start - prevEnd) >= minGap) + { + picked = true; + break; } } + if (!picked) + { + // final fallback: clamp to bounds + start = Math.Max(minValue, Math.Min(attemptUpper, prevEnd + minGap)); + } + + // fill the run with consecutive values, careful with overflow + for (int j = 0; j < runSize; j++) + { + long val; + try + { + checked + { + val = start + j; + } + } + catch (OverflowException) + { + // clamp if overflow occurs + val = (start >= 0) ? long.MaxValue - (runSize - j - 1) : long.MinValue + (runSize - j - 1); + } + + data[idx++] = val; + } + + prevEnd = data[idx - 1]; } return data; } + // Generate random int array of given length and distribution - public static int[] GenerateInt32(int length, string pattern = "uniform", + public static int[] GenerateInt32(int length, GeneratorPattern pattern = GeneratorPattern.Uniform, int range = int.MaxValue) { var data = new int[length]; - switch (pattern.ToLower()) + switch (pattern) { - case "uniform": + case GeneratorPattern.Uniform: // Random values in [-range, range] for (int i = 0; i < length; i++) data[i] = rng.Next(-range, range); break; - case "positive": + case GeneratorPattern.Positive: for (int i = 0; i < length; i++) data[i] = rng.Next(0, range); break; - case "negative": + case GeneratorPattern.Negative: for (int i = 0; i < length; i++) data[i] = -rng.Next(0, range); break; - case "alternating": + case GeneratorPattern.Alternating: for (int i = 0; i < length; i++) { int val = rng.Next(0, range); @@ -116,14 +151,14 @@ public static class IntArrayGenerator } break; - case "small": + case GeneratorPattern.Small: // Focused on small magnitudes to test ZigZag fast path for (int i = 0; i < length; i++) data[i] = rng.Next(-64, 65); break; - case "ascending": + case GeneratorPattern.Ascending: { int start = rng.Next(-range, range); for (int i = 0; i < length; i++) @@ -247,30 +282,30 @@ public static class IntArrayGenerator } // Generate random int array of given length and distribution - public static long[] GenerateInt64(int length, string pattern = "uniform", + public static long[] GenerateInt64(int length, GeneratorPattern pattern = GeneratorPattern.Uniform, long range = long.MaxValue) { var data = new long[length]; - switch (pattern.ToLower()) + switch (pattern) { - case "uniform": + case GeneratorPattern.Uniform: // Random values in [-range, range] for (int i = 0; i < length; i++) data[i] = rng.NextInt64(-range, range); break; - case "positive": + case GeneratorPattern.Positive: for (int i = 0; i < length; i++) data[i] = rng.NextInt64(0, range); break; - case "negative": + case GeneratorPattern.Negative: for (int i = 0; i < length; i++) data[i] = -rng.NextInt64(0, range); break; - case "alternating": + case GeneratorPattern.Alternating: for (int i = 0; i < length; i++) { var val = rng.NextInt64(0, range); @@ -278,14 +313,14 @@ public static class IntArrayGenerator } break; - case "small": + case GeneratorPattern.Small: // Focused on small magnitudes to test ZigZag fast path for (int i = 0; i < length; i++) data[i] = rng.NextInt64(-64, 65); break; - case "ascending": + case GeneratorPattern.Ascending: { var start = rng.NextInt64(-range, range); for (int i = 0; i < length; i++) @@ -300,29 +335,29 @@ public static class IntArrayGenerator return data; } - public static short[] GenerateInt16(int length, string pattern = "uniform", + public static short[] GenerateInt16(int length, GeneratorPattern pattern = GeneratorPattern.Uniform, short range = short.MaxValue) { var data = new short[length]; - switch (pattern.ToLower()) + switch (pattern) { - case "uniform": + case GeneratorPattern.Uniform: for (int i = 0; i < length; i++) data[i] = (short)rng.Next(-range, range + 1); break; - case "positive": + case GeneratorPattern.Positive: for (int i = 0; i < length; i++) data[i] = (short)rng.Next(0, range + 1); break; - case "negative": + case GeneratorPattern.Negative: for (int i = 0; i < length; i++) data[i] = (short)(-rng.Next(0, range + 1)); break; - case "alternating": + case GeneratorPattern.Alternating: for (int i = 0; i < length; i++) { short val = (short)rng.Next(0, range + 1); @@ -330,13 +365,13 @@ public static class IntArrayGenerator } break; - case "small": + case GeneratorPattern.Small: for (int i = 0; i < length; i++) data[i] = (short)rng.Next(-64, 65); break; - case "ascending": + case GeneratorPattern.Ascending: { short start = (short)rng.Next(-range, range); for (int i = 0; i < length; i++) diff --git a/Tests/Serialization/Gvwie/IntArrayRunner.cs b/Tests/Serialization/Gvwie/IntArrayRunner.cs index 0671ba7..0ba565e 100644 --- a/Tests/Serialization/Gvwie/IntArrayRunner.cs +++ b/Tests/Serialization/Gvwie/IntArrayRunner.cs @@ -21,71 +21,151 @@ namespace Esiur.Tests.Gvwie public virtual IList? Values { get; set; } } + internal class IntArrayRunner { public void Run() { + + const int TEST_ITERATIONS = 100; + const int SAMPLE_SIZE = 100; + Console.WriteLine(";Esiur;FlatBuffer;ProtoBuffer;MessagePack;BSON;CBOR;Avro,Optimal"); - //Console.Write("Cluster (Int32);"); - ////CompareInt(int32cluster); - //Average(() => CompareInt(IntArrayGenerator.GenerateInt32Run(1000)), 1000); + Console.Write("Cluster (Int32);"); + PrintAverage( + Average(() => CompareInt(IntArrayGenerator.GenerateRuns(SAMPLE_SIZE)), TEST_ITERATIONS) + ); Console.Write("Positive (Int32);"); - Average(() => CompareInt(IntArrayGenerator.GenerateInt32(1000, "positive")), 1000); + + PrintAverage( + Average(() => CompareInt(IntArrayGenerator.GenerateInt32(SAMPLE_SIZE, GeneratorPattern.Uniform)), TEST_ITERATIONS) + ); Console.Write("Negative (Int32);"); - Average(() => CompareInt(IntArrayGenerator.GenerateInt32(1000, "negative")), 1000); - + PrintAverage( + Average(() => CompareInt(IntArrayGenerator.GenerateInt32(SAMPLE_SIZE, GeneratorPattern.Negative)), TEST_ITERATIONS) + ); Console.Write("Small (Int32);"); - Average(() => CompareInt(IntArrayGenerator.GenerateInt32(1000, "small")), 1000); - + PrintAverage( + Average(() => CompareInt(IntArrayGenerator.GenerateInt32(SAMPLE_SIZE, GeneratorPattern.Small)), TEST_ITERATIONS) + ); // CompareInt(int32small); Console.Write("Alternating (Int32);"); //CompareInt(int32alter); - Average(() => CompareInt(IntArrayGenerator.GenerateInt32(1000, "alternating")), 1000); - + PrintAverage( + Average(() => CompareInt(IntArrayGenerator.GenerateInt32(SAMPLE_SIZE, GeneratorPattern.Alternating)), TEST_ITERATIONS) + ); Console.Write("Ascending (Int32);"); - //CompareInt(int32asc); - Average(() => CompareInt(IntArrayGenerator.GenerateInt32(1000, "ascending")), 1000); + PrintAverage( + Average(() => CompareInt(IntArrayGenerator.GenerateInt32(SAMPLE_SIZE, GeneratorPattern.Ascending)), TEST_ITERATIONS) + ); Console.Write("Int64;"); - Average(() => CompareInt(IntArrayGenerator.GenerateInt64(1000, "uniform")), 1000); - //CompareInt(int64Uni); + + PrintAverage( + Average(() => CompareInt(IntArrayGenerator.GenerateInt64(SAMPLE_SIZE)), TEST_ITERATIONS) + ); Console.Write("Int32;"); - //CompareInt(int32Uni); - Average(() => CompareInt(IntArrayGenerator.GenerateInt32(1000, "uniform")), 1000); + + PrintAverage( + Average(() => CompareInt(IntArrayGenerator.GenerateInt32(SAMPLE_SIZE)), TEST_ITERATIONS) + ); Console.Write("Int16;"); - //CompareInt(int16Uni); - - Average(() => CompareInt(IntArrayGenerator.GenerateInt16(1000, "uniform")), 1000); + PrintAverage( + Average(() => CompareInt(IntArrayGenerator.GenerateInt16(SAMPLE_SIZE)), TEST_ITERATIONS) + ); Console.Write("UInt64;"); - //CompareInt(uint64Uni); - Average(() => CompareInt(IntArrayGenerator.GenerateUInt64(1000, "uniform")), 1000); + PrintAverage( + Average(() => CompareInt(IntArrayGenerator.GenerateUInt64(SAMPLE_SIZE)), TEST_ITERATIONS) + ); Console.Write("UInt32;"); - //CompareInt(uint32Uni); - Average(() => CompareInt(IntArrayGenerator.GenerateUInt32(1000, "uniform")), 1000); + + PrintAverage( + Average(() => CompareInt(IntArrayGenerator.GenerateUInt32(SAMPLE_SIZE)), TEST_ITERATIONS) + ); Console.Write("UInt16;"); - //CompareInt(uint16Uni); - Average(() => CompareInt(IntArrayGenerator.GenerateUInt16(1000, "uniform")), 1000); + PrintAverage( + Average(() => CompareInt(IntArrayGenerator.GenerateUInt16(SAMPLE_SIZE)), TEST_ITERATIONS) + ); } + // Generate CSV suitable for Office Word chart where the sample size varies. + // Produces a CSV with header: SampleSize;Esiur;FlatBuffer;ProtoBuffer;MessagePack;BSON;CBOR;Avro;Optimal + public void RunChart() + { + var sizes = new int[] { 10, 100, 1000, 10000, 100000 }; + + // Define generators to evaluate. Each entry maps a name to a function that + // given a sample size returns the averages (double[]) by calling Average(...). + var generators = new List<(string name, Func fn)>() + { + ("GenerateRuns", (size, iterations) => Average(() => CompareInt(IntArrayGenerator.GenerateRuns(size)), iterations)), + ("Int32_Uniform", (size, iterations) => Average(() => CompareInt(IntArrayGenerator.GenerateInt32(size, GeneratorPattern.Uniform)), iterations)), + ("Int32_Negative", (size, iterations) => Average(() => CompareInt(IntArrayGenerator.GenerateInt32(size, GeneratorPattern.Negative)), iterations)), + ("Int32_Small", (size, iterations) => Average(() => CompareInt(IntArrayGenerator.GenerateInt32(size, GeneratorPattern.Small)), iterations)), + ("Int32_Alternating", (size, iterations) => Average(() => CompareInt(IntArrayGenerator.GenerateInt32(size, GeneratorPattern.Alternating)), iterations)), + ("Int32_Ascending", (size, iterations) => Average(() => CompareInt(IntArrayGenerator.GenerateInt32(size, GeneratorPattern.Ascending)), iterations)), + ("Int64", (size, iterations) => Average(() => CompareInt(IntArrayGenerator.GenerateInt64(size)), iterations)), + ("Int32_Default", (size, iterations) => Average(() => CompareInt(IntArrayGenerator.GenerateInt32(size)), iterations)), + ("Int16", (size, iterations) => Average(() => CompareInt(IntArrayGenerator.GenerateInt16(size)), iterations)), + ("UInt64", (size, iterations) => Average(() => CompareInt(IntArrayGenerator.GenerateUInt64(size)), iterations)), + ("UInt32", (size, iterations) => Average(() => CompareInt(IntArrayGenerator.GenerateUInt32(size)), iterations)), + ("UInt16", (size, iterations) => Average(() => CompareInt(IntArrayGenerator.GenerateUInt16(size)), iterations)), + }; + + foreach (var gen in generators) + { + var sb = new System.Text.StringBuilder(); + sb.AppendLine("SampleSize;Esiur;FlatBuffer;ProtoBuffer;MessagePack;BSON;CBOR;Avro;Optimal"); + + foreach (var size in sizes) + { + // Choose iterations depending on size to keep total runtime reasonable + int iterations; + if (size <= 100) iterations = 1000; + else if (size <= 1000) iterations = 200; + else if (size <= 10000) iterations = 50; + else iterations = 10; + + Console.WriteLine($"Running {gen.name} sample size={size}, iterations={iterations}..."); + + var averages = gen.fn(size, iterations); + + sb.Append(size); + for (int i = 0; i < averages.Length; i++) + { + sb.Append(';'); + sb.Append(Math.Round(averages[i])); + } + + sb.AppendLine(); + } + + var file = $"run_chart_{gen.name}.csv"; + System.IO.File.WriteAllText(file, sb.ToString()); + Console.WriteLine($"Chart CSV written to: {file}"); + } + } + + public static (int, int, int, int, int, int, int, int) CompareInt(long[] sample) { var intRoot = new ArrayRoot() { Values = sample }; @@ -300,7 +380,7 @@ namespace Esiur.Tests.Gvwie } - static (double, double, double, double, double, double, double, double) Average(Func<(int, int, int, int, int, int, int, int)> call, int count) + static double[] Average(Func<(int, int, int, int, int, int, int, int)> call, int count) { var sum = new List<(int, int, int, int, int, int, int, int)>(); @@ -308,7 +388,7 @@ namespace Esiur.Tests.Gvwie sum.Add(call()); - var rt = (sum.Average(x => x.Item1), + var rt = new double[]{ sum.Average(x => x.Item1), sum.Average(x => x.Item2), sum.Average(x => x.Item3), sum.Average(x => x.Item4), @@ -316,13 +396,36 @@ namespace Esiur.Tests.Gvwie sum.Average(x => x.Item6), sum.Average(x => x.Item7), sum.Average(x => x.Item8) - ); + }; + + Console.WriteLine($"{rt[0]};{rt[1]};{rt[2]};{rt[3]};{rt[4]};{rt[5]};{rt[6]};{rt[7]}"); - Console.WriteLine($"{rt.Item1};{rt.Item2};{rt.Item3};{rt.Item4};{rt.Item5};{rt.Item6};{rt.Item7};{rt.Item8}"); return rt; } + static string PrintAverage(double[] values) + { + // Determine winner (lowest average size) + var names = new string[] { "Esiur", "FlatBuffer", "ProtoBuffer", "MessagePack", "BSON", "CBOR", "Avro", "Optimal" }; + var min = values.SkipLast(1).Min(); + var idx = Array.IndexOf(values, min); + if (idx >= 0 && idx < names.Length) + { + if (idx == 0) + + Console.ForegroundColor = ConsoleColor.Green; + else + Console.ForegroundColor = ConsoleColor.Red; + + Console.WriteLine($"Winner: {names[idx]} ({min:F0})"); + Console.ForegroundColor = ConsoleColor.White; + + return names[idx]; + } + + return "Unknown"; + } public static byte[] SerializeFlatBuffers(ArrayRoot array) { diff --git a/Tests/Serialization/Gvwie/Program.cs b/Tests/Serialization/Gvwie/Program.cs index 7f87a61..d342b36 100644 --- a/Tests/Serialization/Gvwie/Program.cs +++ b/Tests/Serialization/Gvwie/Program.cs @@ -11,4 +11,5 @@ MessagePack.MessagePackSerializer.DefaultOptions = MessagePackSerializerOptions. var ints = new IntArrayRunner(); ints.Run(); +ints.RunChart();