2
0
mirror of https://github.com/esiur/esiur-dotnet.git synced 2026-04-29 06:48:41 +00:00

Expanding test

This commit is contained in:
2026-04-08 17:30:05 +03:00
parent cc66520fee
commit 05eae10a73
4 changed files with 269 additions and 114 deletions
@@ -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,
}
}
+120 -85
View File
@@ -8,107 +8,142 @@ namespace Esiur.Tests.Gvwie;
public static class IntArrayGenerator public static class IntArrayGenerator
{ {
private static readonly Random rng = new Random(24241564); private static readonly Random rng = new Random(24241564);
/// <summary>
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)
/// </summary>
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<long>();
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]; var data = new long[length];
int idx = 0;
long prevEnd = long.MinValue;
int i = 0; while (idx < length)
var inSmallRange = true;
var inShortRange = false;
var inLargeRange = false;
var inLongRange = false;
long range = 30;
while (i < length)
{ {
// stay same range // choose run size
if (rng.NextDouble() < 0.9) int runSize = rng.Next(minRunSize, maxRunSize + 1);
{ if (idx + runSize > length)
if (inSmallRange) runSize = length - idx;
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);
// 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; return data;
} }
// Generate random int array of given length and distribution // 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) int range = int.MaxValue)
{ {
var data = new int[length]; var data = new int[length];
switch (pattern.ToLower()) switch (pattern)
{ {
case "uniform": case GeneratorPattern.Uniform:
// Random values in [-range, range] // Random values in [-range, range]
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
data[i] = rng.Next(-range, range); data[i] = rng.Next(-range, range);
break; break;
case "positive": case GeneratorPattern.Positive:
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
data[i] = rng.Next(0, range); data[i] = rng.Next(0, range);
break; break;
case "negative": case GeneratorPattern.Negative:
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
data[i] = -rng.Next(0, range); data[i] = -rng.Next(0, range);
break; break;
case "alternating": case GeneratorPattern.Alternating:
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
{ {
int val = rng.Next(0, range); int val = rng.Next(0, range);
@@ -116,14 +151,14 @@ public static class IntArrayGenerator
} }
break; break;
case "small": case GeneratorPattern.Small:
// Focused on small magnitudes to test ZigZag fast path // Focused on small magnitudes to test ZigZag fast path
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
data[i] = rng.Next(-64, 65); data[i] = rng.Next(-64, 65);
break; break;
case "ascending": case GeneratorPattern.Ascending:
{ {
int start = rng.Next(-range, range); int start = rng.Next(-range, range);
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
@@ -247,30 +282,30 @@ public static class IntArrayGenerator
} }
// Generate random int array of given length and distribution // 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) long range = long.MaxValue)
{ {
var data = new long[length]; var data = new long[length];
switch (pattern.ToLower()) switch (pattern)
{ {
case "uniform": case GeneratorPattern.Uniform:
// Random values in [-range, range] // Random values in [-range, range]
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
data[i] = rng.NextInt64(-range, range); data[i] = rng.NextInt64(-range, range);
break; break;
case "positive": case GeneratorPattern.Positive:
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
data[i] = rng.NextInt64(0, range); data[i] = rng.NextInt64(0, range);
break; break;
case "negative": case GeneratorPattern.Negative:
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
data[i] = -rng.NextInt64(0, range); data[i] = -rng.NextInt64(0, range);
break; break;
case "alternating": case GeneratorPattern.Alternating:
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
{ {
var val = rng.NextInt64(0, range); var val = rng.NextInt64(0, range);
@@ -278,14 +313,14 @@ public static class IntArrayGenerator
} }
break; break;
case "small": case GeneratorPattern.Small:
// Focused on small magnitudes to test ZigZag fast path // Focused on small magnitudes to test ZigZag fast path
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
data[i] = rng.NextInt64(-64, 65); data[i] = rng.NextInt64(-64, 65);
break; break;
case "ascending": case GeneratorPattern.Ascending:
{ {
var start = rng.NextInt64(-range, range); var start = rng.NextInt64(-range, range);
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
@@ -300,29 +335,29 @@ public static class IntArrayGenerator
return data; 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) short range = short.MaxValue)
{ {
var data = new short[length]; var data = new short[length];
switch (pattern.ToLower()) switch (pattern)
{ {
case "uniform": case GeneratorPattern.Uniform:
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
data[i] = (short)rng.Next(-range, range + 1); data[i] = (short)rng.Next(-range, range + 1);
break; break;
case "positive": case GeneratorPattern.Positive:
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
data[i] = (short)rng.Next(0, range + 1); data[i] = (short)rng.Next(0, range + 1);
break; break;
case "negative": case GeneratorPattern.Negative:
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
data[i] = (short)(-rng.Next(0, range + 1)); data[i] = (short)(-rng.Next(0, range + 1));
break; break;
case "alternating": case GeneratorPattern.Alternating:
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
{ {
short val = (short)rng.Next(0, range + 1); short val = (short)rng.Next(0, range + 1);
@@ -330,13 +365,13 @@ public static class IntArrayGenerator
} }
break; break;
case "small": case GeneratorPattern.Small:
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
data[i] = (short)rng.Next(-64, 65); data[i] = (short)rng.Next(-64, 65);
break; break;
case "ascending": case GeneratorPattern.Ascending:
{ {
short start = (short)rng.Next(-range, range); short start = (short)rng.Next(-range, range);
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
+132 -29
View File
@@ -21,71 +21,151 @@ namespace Esiur.Tests.Gvwie
public virtual IList<T>? Values { get; set; } public virtual IList<T>? Values { get; set; }
} }
internal class IntArrayRunner internal class IntArrayRunner
{ {
public void Run() public void Run()
{ {
const int TEST_ITERATIONS = 100;
const int SAMPLE_SIZE = 100;
Console.WriteLine(";Esiur;FlatBuffer;ProtoBuffer;MessagePack;BSON;CBOR;Avro,Optimal"); Console.WriteLine(";Esiur;FlatBuffer;ProtoBuffer;MessagePack;BSON;CBOR;Avro,Optimal");
//Console.Write("Cluster (Int32);"); Console.Write("Cluster (Int32);");
////CompareInt(int32cluster);
//Average(() => CompareInt(IntArrayGenerator.GenerateInt32Run(1000)), 1000);
PrintAverage(
Average(() => CompareInt(IntArrayGenerator.GenerateRuns(SAMPLE_SIZE)), TEST_ITERATIONS)
);
Console.Write("Positive (Int32);"); 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);"); 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);"); Console.Write("Small (Int32);");
Average(() => CompareInt(IntArrayGenerator.GenerateInt32(1000, "small")), 1000); PrintAverage(
Average(() => CompareInt(IntArrayGenerator.GenerateInt32(SAMPLE_SIZE, GeneratorPattern.Small)), TEST_ITERATIONS)
);
// CompareInt(int32small); // CompareInt(int32small);
Console.Write("Alternating (Int32);"); Console.Write("Alternating (Int32);");
//CompareInt(int32alter); //CompareInt(int32alter);
Average(() => CompareInt(IntArrayGenerator.GenerateInt32(1000, "alternating")), 1000); PrintAverage(
Average(() => CompareInt(IntArrayGenerator.GenerateInt32(SAMPLE_SIZE, GeneratorPattern.Alternating)), TEST_ITERATIONS)
);
Console.Write("Ascending (Int32);"); 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;"); Console.Write("Int64;");
Average(() => CompareInt(IntArrayGenerator.GenerateInt64(1000, "uniform")), 1000);
//CompareInt(int64Uni); PrintAverage(
Average(() => CompareInt(IntArrayGenerator.GenerateInt64(SAMPLE_SIZE)), TEST_ITERATIONS)
);
Console.Write("Int32;"); Console.Write("Int32;");
//CompareInt(int32Uni);
Average(() => CompareInt(IntArrayGenerator.GenerateInt32(1000, "uniform")), 1000); PrintAverage(
Average(() => CompareInt(IntArrayGenerator.GenerateInt32(SAMPLE_SIZE)), TEST_ITERATIONS)
);
Console.Write("Int16;"); Console.Write("Int16;");
//CompareInt(int16Uni);
Average(() => CompareInt(IntArrayGenerator.GenerateInt16(1000, "uniform")), 1000);
PrintAverage(
Average(() => CompareInt(IntArrayGenerator.GenerateInt16(SAMPLE_SIZE)), TEST_ITERATIONS)
);
Console.Write("UInt64;"); Console.Write("UInt64;");
//CompareInt(uint64Uni);
Average(() => CompareInt(IntArrayGenerator.GenerateUInt64(1000, "uniform")), 1000);
PrintAverage(
Average(() => CompareInt(IntArrayGenerator.GenerateUInt64(SAMPLE_SIZE)), TEST_ITERATIONS)
);
Console.Write("UInt32;"); Console.Write("UInt32;");
//CompareInt(uint32Uni);
Average(() => CompareInt(IntArrayGenerator.GenerateUInt32(1000, "uniform")), 1000); PrintAverage(
Average(() => CompareInt(IntArrayGenerator.GenerateUInt32(SAMPLE_SIZE)), TEST_ITERATIONS)
);
Console.Write("UInt16;"); 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<int, int, double[]> 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) public static (int, int, int, int, int, int, int, int) CompareInt(long[] sample)
{ {
var intRoot = new ArrayRoot<long>() { Values = sample }; var intRoot = new ArrayRoot<long>() { 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)>(); var sum = new List<(int, int, int, int, int, int, int, int)>();
@@ -308,7 +388,7 @@ namespace Esiur.Tests.Gvwie
sum.Add(call()); 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.Item2),
sum.Average(x => x.Item3), sum.Average(x => x.Item3),
sum.Average(x => x.Item4), sum.Average(x => x.Item4),
@@ -316,13 +396,36 @@ namespace Esiur.Tests.Gvwie
sum.Average(x => x.Item6), sum.Average(x => x.Item6),
sum.Average(x => x.Item7), sum.Average(x => x.Item7),
sum.Average(x => x.Item8) 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; 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<T>(ArrayRoot<T> array) public static byte[] SerializeFlatBuffers<T>(ArrayRoot<T> array)
{ {
+1
View File
@@ -11,4 +11,5 @@ MessagePack.MessagePackSerializer.DefaultOptions = MessagePackSerializerOptions.
var ints = new IntArrayRunner(); var ints = new IntArrayRunner();
ints.Run(); ints.Run();
ints.RunChart();