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();