From bfd8458b7a634bc71ef868a8bd4f85487906a820 Mon Sep 17 00:00:00 2001 From: Emmanouil Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Sun, 4 Aug 2024 23:53:11 +0300 Subject: [PATCH] Port GDScript benchmarks to C# (#84) --- benchmarks/csharp/Alloc.cs | 80 +++++++ benchmarks/csharp/BinaryTrees.cs | 77 +++++++ benchmarks/csharp/HelloWorld.cs | 9 + benchmarks/csharp/LambdaPerformance.cs | 15 ++ benchmarks/csharp/MandelbrotSet.cs | 70 ++++++ benchmarks/csharp/MerkleTrees.cs | 112 ++++++++++ benchmarks/csharp/Nbody.cs | 163 ++++++++++++++ benchmarks/csharp/SpectralNorm.cs | 87 ++++++++ benchmarks/csharp/StringChecksum.cs | 93 ++++++++ benchmarks/csharp/StringFormat.cs | 113 ++++++++++ benchmarks/csharp/StringManipulation.cs | 272 ++++++++++++++++++++++++ 11 files changed, 1091 insertions(+) create mode 100644 benchmarks/csharp/Alloc.cs create mode 100644 benchmarks/csharp/BinaryTrees.cs create mode 100644 benchmarks/csharp/HelloWorld.cs create mode 100644 benchmarks/csharp/LambdaPerformance.cs create mode 100644 benchmarks/csharp/MandelbrotSet.cs create mode 100644 benchmarks/csharp/MerkleTrees.cs create mode 100644 benchmarks/csharp/Nbody.cs create mode 100644 benchmarks/csharp/SpectralNorm.cs create mode 100644 benchmarks/csharp/StringChecksum.cs create mode 100644 benchmarks/csharp/StringFormat.cs create mode 100644 benchmarks/csharp/StringManipulation.cs diff --git a/benchmarks/csharp/Alloc.cs b/benchmarks/csharp/Alloc.cs new file mode 100644 index 0000000..4fe2643 --- /dev/null +++ b/benchmarks/csharp/Alloc.cs @@ -0,0 +1,80 @@ +using Godot; + +public partial class Alloc : Benchmark +{ + const int ITERATIONS = 100_000; + + void BenchmarkDeepTree() + { + Node rt = new Node(); + for (int i = 0; i < ITERATIONS; i++) + { + Node n = new Node(); + n.AddChild(rt); + rt = n; + } + + // Avoid triggering a stack overflow with rt.Free() + while (rt.GetChildCount() != 0) + { + Node n = rt.GetChild(0); + rt.RemoveChild(n); + rt.Free(); + rt = n; + } + rt.Free(); + } + + void BenchmarkWideTree() + { + Node rt = new Node(); + for (int i = 0; i < ITERATIONS; i++) + { + rt.AddChild(new Node()); + } + rt.Free(); + } + + void BenchmarkFragmentation() + { + Node top = new Node(); + for (int i = 0; i < 5; i++) + { + top.AddChild(new Node()); + } + + for (int k = 0; k < 10; k++) { + for (int i = 0; i < ITERATIONS; i++) + { + // Attempt to scatter children in memory by assigning newly created nodes to a random parent + int idx = (int)GD.Randi() % top.GetChildCount(); + top.GetChild(idx).AddChild(new Node()); + } + + Node tmp = top.GetChild(0); + top.RemoveChild(tmp); + // Since nodes in the tree are scattered in memory, + // freeing subtrees this way should maximize fragmentation. + tmp.Free(); + top.AddChild(new Node()); + //GD.Print("Iteration %d: %.3f MB" % [k, Performance.get_monitor(Performance.MEMORY_STATIC) / 1e6]) + } + + top.Free(); + } + + void BenchmarkDuplicate() + { + Node rt = new Node(); + for (int i = 0; i < 16; i++) + { + Node n = new Node(); + n.AddChild(rt.Duplicate()); + n.AddChild(rt.Duplicate()); + rt.Free(); + rt = n; + //GD.Print("Iteration %d: %.3f MB" % [i, Performance.get_monitor(Performance.MEMORY_STATIC) / 1e6]) + } + rt.Free(); + } +} diff --git a/benchmarks/csharp/BinaryTrees.cs b/benchmarks/csharp/BinaryTrees.cs new file mode 100644 index 0000000..d2e6f8f --- /dev/null +++ b/benchmarks/csharp/BinaryTrees.cs @@ -0,0 +1,77 @@ +using Godot; + +public partial class BinaryTrees : Benchmark +{ + // Based on https://github.com/hanabi1224/Programming-Language-Benchmarks/blob/main/bench/algorithm/binarytrees/1.cs + class TreeNode + { + public TreeNode left; + public TreeNode right; + + TreeNode(TreeNode left = null, TreeNode right = null) + { + this.left = left; + this.right = right; + } + + internal static TreeNode Create(int d) + { + return d == 0 ? new TreeNode() + : new TreeNode(Create(d - 1), Create(d - 1)); + } + + internal int Check() + { + int c = 1; + if (right != null) + { + c += right.Check(); + } + if (left != null) + { + c += left.Check(); + } + return c; + } + } + + const int MinDepth = 4; + public void CalculateBinaryTrees(int input) + { + int maxDepth = Mathf.Max(MinDepth + 2, input); + + int stretchDepth = maxDepth + 1; + GD.Print($"stretch tree of depth {stretchDepth}\t check: {TreeNode.Create(stretchDepth).Check()}"); + + TreeNode longLivedTree = TreeNode.Create(maxDepth); + int maxPlusMinDepth = maxDepth + MinDepth; + for (int depth = MinDepth; depth < maxDepth; depth += 2) + { + int iterations = 1 << (maxPlusMinDepth - depth); + int check = 0; + for (int i = 0; i < iterations; i++) + { + check += TreeNode.Create(depth).Check(); + } + + GD.Print($"{iterations}\t trees of depth {depth}\t check: {check}"); + } + + GD.Print($"long lived tree of depth {maxDepth}\t check: {longLivedTree.Check()}"); + } + + public void BenchmarkBinaryTrees13() + { + CalculateBinaryTrees(13); + } + + public void BenchmarkBinaryTrees15() + { + CalculateBinaryTrees(15); + } + + public void BenchmarkBinaryTrees18() + { + CalculateBinaryTrees(18); + } +} diff --git a/benchmarks/csharp/HelloWorld.cs b/benchmarks/csharp/HelloWorld.cs new file mode 100644 index 0000000..c0989d3 --- /dev/null +++ b/benchmarks/csharp/HelloWorld.cs @@ -0,0 +1,9 @@ +using Godot; + +public partial class HelloWorld : Benchmark +{ + public void BenchmarkHelloWorld() + { + GD.Print("Hello world!"); + } +} diff --git a/benchmarks/csharp/LambdaPerformance.cs b/benchmarks/csharp/LambdaPerformance.cs new file mode 100644 index 0000000..2e0eb49 --- /dev/null +++ b/benchmarks/csharp/LambdaPerformance.cs @@ -0,0 +1,15 @@ +using System; + +public partial class LambdaPerformance : Benchmark +{ + const int ITERATIONS = 1_000_000; + Action lambda = () => { }; + + public void BenchmarkLambdaCall() + { + for(int i = 0; i < ITERATIONS; i++) + { + lambda(); + } + } +} diff --git a/benchmarks/csharp/MandelbrotSet.cs b/benchmarks/csharp/MandelbrotSet.cs new file mode 100644 index 0000000..b0891d1 --- /dev/null +++ b/benchmarks/csharp/MandelbrotSet.cs @@ -0,0 +1,70 @@ +using Godot; + +public partial class MandelbrotSet : Benchmark +{ + const int WIDTH = 600; + const int HEIGHT = 400; + const int MAX_ITERATION = 1000; + + private Color HSV(float hue, float sat, float value) + { + hue = Mathf.PosMod(hue, 360.0f); + int h = Mathf.FloorToInt(hue) / 60; + float f = hue / 60.0f - h; + float p = value * (1.0f - sat); + float q = value * (1.0f - sat * f); + float t = value * (1.0f - sat * (1.0f - f)); + if (h == 0 || h == 6) + return new Color(value, t, p); + if (h == 1) + return new Color(q, value, p); + if (h == 2) + return new Color(p, value, t); + if (h == 3) + return new Color(p, q, value); + if (h == 4) + return new Color(t, p, value); + return new Color(value, p, q); + } + + // Algorithm from + // https://en.wikipedia.org/wiki/Plotting_algorithms_for_the_Mandelbrot_set#Optimized_escape_time_algorithms + private void mandelbrot_set(int width, int height, int maxIteration) + { + Image image = Image.CreateEmpty(width, height, false, Image.Format.Rgb8); + float ratio = (float)width / (float)height; + float xRange = 3.6f; + float yRange = xRange / ratio; + float minX = -xRange / 2.0f; + float maxY = yRange / 2.0f; + for (int x = 0; x < image.GetWidth(); x++) + { + for (int y = 0; y < image.GetHeight(); y++) + { + int iteration = 0; + float x0 = minX + xRange * x / width; + float y0 = maxY - yRange * y / height; + float xx = 0.0f; + float yy = 0.0f; + float x2 = 0.0f; + float y2 = 0.0f; + while (x2 + y2 <= 4 && iteration < maxIteration) + { + yy = 2 * xx * yy + y0; + xx = x2 - y2 + x0; + x2 = xx * xx; + y2 = yy * yy; + iteration += 1; + } + float m = (float)iteration / (float)maxIteration; + Color color = HSV(360.0f * m, 1.0f, Mathf.Ceil(1.0f - 1.1f * m)); + image.SetPixel(x, y, color); + } + } + } + + public void BenchmarkMandelbrotSet() + { + mandelbrot_set(WIDTH, HEIGHT, MAX_ITERATION); + } +} diff --git a/benchmarks/csharp/MerkleTrees.cs b/benchmarks/csharp/MerkleTrees.cs new file mode 100644 index 0000000..4346051 --- /dev/null +++ b/benchmarks/csharp/MerkleTrees.cs @@ -0,0 +1,112 @@ +using Godot; + +public partial class MerkleTrees : Benchmark +{ + // Based on https://github.com/hanabi1224/Programming-Language-Benchmarks/blob/main/bench/algorithm/merkletrees/1.cs + class TreeNode + { + public long? value; + public long? hash; + public TreeNode left; + public TreeNode right; + + public TreeNode(long? value, TreeNode left = null, TreeNode right = null) + { + this.value = value; + this.left = left; + this.right = right; + } + + public static TreeNode Create(int d) + { + return d == 0 ? new TreeNode(1L, null, null) + : new TreeNode(null, Create(d - 1), Create(d - 1)); + } + + public bool Check() + { + if (hash != null) + { + if (value != null) + { + return true; + } + else if (left != null && right != null) + { + return left.Check() && right.Check(); + } + } + return false; + } + + public long GetHash() + { + if (hash.Value is long v) + { + return v; + } + return default; + } + + public void CalHash() + { + if (hash == null) + { + if (value.HasValue) + { + hash = value; + } + else if (left != null && right != null) + { + left.CalHash(); + right.CalHash(); + hash = left.GetHash() + right.GetHash(); + } + } + } + } + + const int MinDepth = 4; + public static void CalculateMerkleTrees(int input) + { + int maxDepth = Mathf.Max(MinDepth + 2, input); + + int stretchDepth = maxDepth + 1; + TreeNode stretchTree = TreeNode.Create(stretchDepth); + stretchTree.CalHash(); + GD.Print($"stretch tree of depth {stretchDepth}\t root hash: {stretchTree.GetHash()} check: {stretchTree.Check().ToString().ToLowerInvariant()}"); + + TreeNode longLivedTree = TreeNode.Create(maxDepth); + int maxPlusMinDepth = maxDepth + MinDepth; + for (int depth = MinDepth; depth < maxDepth; depth += 2) + { + int iterations = 1 << (maxPlusMinDepth - depth); + long sum = 0; + for (int i = 0; i < iterations; i++) + { + TreeNode tree = TreeNode.Create(depth); + tree.CalHash(); + sum += tree.GetHash(); + } + GD.Print($"{iterations}\t trees of depth {depth}\t root hash sum: {sum}"); + } + + longLivedTree.CalHash(); + GD.Print($"long lived tree of depth {maxDepth}\t root hash: {longLivedTree.GetHash()} check: {longLivedTree.Check().ToString().ToLowerInvariant()}"); + } + + public void BenchmarkMerkleTrees13() + { + CalculateMerkleTrees(13); + } + + public void BenchmarkMerkleTrees15() + { + CalculateMerkleTrees(15); + } + + public void BenchmarkMerkleTrees18() + { + CalculateMerkleTrees(18); + } +} diff --git a/benchmarks/csharp/Nbody.cs b/benchmarks/csharp/Nbody.cs new file mode 100644 index 0000000..fc466ba --- /dev/null +++ b/benchmarks/csharp/Nbody.cs @@ -0,0 +1,163 @@ +using Godot; + +public partial class Nbody : Benchmark +{ + // Based on https://github.com/hanabi1224/Programming-Language-Benchmarks/blob/main/bench/algorithm/nbody/8.cs + public class Body { public double x, y, z, vx, vy, vz, mass; } + + public class NBodySystem + { + private Body[] _bodies; + const byte bodyCount = 5; + const double Pi = 3.141592653589793; + const double Solarmass = 4 * Pi * Pi; + const double DaysPeryear = 365.24; + + public NBodySystem() + { + _bodies = new[] + { + new Body() + { + // Sun + mass = Solarmass, + }, + new Body() + { + // Jupiter + x = 4.84143144246472090e+00, + y = -1.16032004402742839e+00, + z = -1.03622044471123109e-01, + vx = 1.66007664274403694e-03*DaysPeryear, + vy = 7.69901118419740425e-03*DaysPeryear, + vz = -6.90460016972063023e-05*DaysPeryear, + mass = 9.54791938424326609e-04*Solarmass, + }, + new Body() + { + // Saturn + x = 8.34336671824457987e+00, + y = 4.12479856412430479e+00, + z = -4.03523417114321381e-01, + vx = -2.76742510726862411e-03*DaysPeryear, + vy = 4.99852801234917238e-03*DaysPeryear, + vz = 2.30417297573763929e-05*DaysPeryear, + mass = 2.85885980666130812e-04*Solarmass, + }, + new Body() + { + // Uranus + x = 1.28943695621391310e+01, + y = -1.51111514016986312e+01, + z = -2.23307578892655734e-01, + vx = 2.96460137564761618e-03*DaysPeryear, + vy = 2.37847173959480950e-03*DaysPeryear, + vz = -2.96589568540237556e-05*DaysPeryear, + mass = 4.36624404335156298e-05*Solarmass, + }, + new Body() + { + // Neptune + x = 1.53796971148509165e+01, + y = -2.59193146099879641e+01, + z = 1.79258772950371181e-01, + vx = 2.68067772490389322e-03*DaysPeryear, + vy = 1.62824170038242295e-03*DaysPeryear, + vz = -9.51592254519715870e-05*DaysPeryear, + mass = 5.15138902046611451e-05*Solarmass, + }, + }; + } + + public void OffsetMomentum() + { + double px = 0, py = 0, pz = 0; + foreach (var b in _bodies) + { + px -= b.vx * b.mass; + py -= b.vy * b.mass; + pz -= b.vz * b.mass; + } + var sol = _bodies[0]; + sol.vx = px / Solarmass; + sol.vy = py / Solarmass; + sol.vz = pz / Solarmass; + } + + public void Advance(double dt) + { + for (var i = 0; i < bodyCount; i++) + { + var bi = _bodies[i]; + var x = bi.x; + var y = bi.y; + var z = bi.z; + var vx = bi.vx; + var vy = bi.vy; + var vz = bi.vz; + var mi = bi.mass; + for (var j = i + 1; j < bodyCount; j++) + { + var bj = _bodies[j]; + var dx = x - bj.x; + var dy = y - bj.y; + var dz = z - bj.z; + var d2 = dx * dx + dy * dy + dz * dz; + var mag = dt / (d2 * Mathf.Sqrt(d2)); + var bj_m_mag = bj.mass * mag; + vx -= dx * bj_m_mag; + vy -= dy * bj_m_mag; + vz -= dz * bj_m_mag; + + var bi_m_mag = mi * mag; + bj.vx += dx * bi_m_mag; + bj.vy += dy * bi_m_mag; + bj.vz += dz * bi_m_mag; + } + bi.vx = vx; + bi.vy = vy; + bi.vz = vz; + + bi.x += vx * dt; + bi.y += vy * dt; + bi.z += vz * dt; + } + } + + public double Energy() + { + double e = 0.0; + for (int i = 0; i < bodyCount; i++) + { + var bi = _bodies[i]; + e += 0.5 * bi.mass * (bi.vx * bi.vx + bi.vy * bi.vy + bi.vz * bi.vz); + for (int j = i + 1; j < bodyCount; j++) + { + var bj = _bodies[j]; + double dx = bi.x - bj.x, dy = bi.y - bj.y, dz = bi.z - bj.z; + e -= (bi.mass * bj.mass) / Mathf.Sqrt(dx * dx + dy * dy + dz * dz); + } + } + return e; + } + } + + public static void CalculateNbody(int n) + { + NBodySystem sys = new NBodySystem(); + sys.OffsetMomentum(); + GD.Print("{0:f9}", sys.Energy()); + for (int i = 0; i < n; i++) sys.Advance(0.01); + GD.Print("{0:f9}", sys.Energy()); + } + + public void BenchmarkNbody500_000() + { + CalculateNbody(500_000); + } + + public void BenchmarkNbody1_000_000() + { + CalculateNbody(1_000_000); + } +} diff --git a/benchmarks/csharp/SpectralNorm.cs b/benchmarks/csharp/SpectralNorm.cs new file mode 100644 index 0000000..a1527cd --- /dev/null +++ b/benchmarks/csharp/SpectralNorm.cs @@ -0,0 +1,87 @@ +using Godot; + +public partial class SpectralNorm : Benchmark +{ + // Based on https://github.com/hanabi1224/Programming-Language-Benchmarks/blob/main/bench/algorithm/spectral-norm/3.cs + // Return element i,j of infinite matrix A. + private double eval_A(int i, int j) + { + return 1.0 / ((i + j) * (i + j + 1) / 2 + i + 1); + } + + // multiply vector v by matrix A, each thread evaluate its range only. + private void MultiplyAv(double[] v, double[] Av, int n) + { + for (int i = 0; i < n; i++) + { + double sum = 0; + for (int j = 0; j < v.Length; j++) + sum += eval_A(i, j) * v[j]; + + Av[i] = sum; + } + } + + // multiply vector v by matrix A transposed. + private void MultiplyAtv(double[] v, double[] Atv, int n) + { + for (int i = 0; i < n; i++) + { + double sum = 0; + for (int j = 0; j < v.Length; j++) + sum += eval_A(j, i) * v[j]; + + Atv[i] = sum; + } + } + + // Multiply vector v by matrix A and then by matrix A transposed. + private void MultiplyAtAv(double[] v, double[] tmp, double[] AtAv, int n) + { + MultiplyAv(v, tmp, n); + MultiplyAtv(tmp, AtAv, n); + } + + private void CalculateSpectralNorm(int n) + { + double[] u = new double[n]; + double[] v = new double[n]; + double[] tmp = new double[n]; + + // Create unit vector. + for (int i = 0; i < n; i++) + u[i] = 1.0; + + for (int i = 0; i < 10; i++) + { + MultiplyAtAv(u, v, tmp, n); + MultiplyAtAv(v, u, tmp, n); + } + + double vBv = 0, vv = 0; + for (int i = 0; i < n; i++) + { + vBv += u[i] * v[i]; + vv += v[i] * v[i]; + } + + double square_root = Mathf.Sqrt(vBv / vv); + GD.Print("{0:f9}", square_root); + } + + public void BenchmarkSpectralNorm100() + { + CalculateSpectralNorm(100); + } + + public void BenchmarkSpectralNorm500() + { + CalculateSpectralNorm(500); + } + + public void BenchmarkSpectralNorm1000() + { + CalculateSpectralNorm(1000); + } + +} diff --git a/benchmarks/csharp/StringChecksum.cs b/benchmarks/csharp/StringChecksum.cs new file mode 100644 index 0000000..4e6c76e --- /dev/null +++ b/benchmarks/csharp/StringChecksum.cs @@ -0,0 +1,93 @@ +using Godot; + +public partial class StringChecksum : Benchmark +{ + + const int ITERATIONS = 1_000_000; + const string LOREM_IPSUM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."; + + // Benchmark computation of checksums on a string. + + private void BenchmarkMd5BufferEmpty() + { + for (int i = 0; i < ITERATIONS; i++) + "".Md5Buffer(); + } + + + private void BenchmarkMd5BufferNonEmpty() + { + for (int i = 0; i < ITERATIONS; i++) + LOREM_IPSUM.Md5Buffer(); + } + + + private void BenchmarkSha1BufferEmpty() + { + for (int i = 0; i < ITERATIONS; i++) + "".Sha1Buffer(); + } + + + private void BenchmarkSha1BufferNonEmpty() + { + for (int i = 0; i < ITERATIONS; i++) + LOREM_IPSUM.Sha1Buffer(); + } + + + private void BenchmarkSha256BufferEmpty() + { + for (int i = 0; i < ITERATIONS; i++) + "".Sha256Buffer(); + } + + + private void BenchmarkSha256BufferNonEmpty() + { + for (int i = 0; i < ITERATIONS; i++) + LOREM_IPSUM.Sha256Buffer(); + } + + + private void BenchmarkMd5TextEmpty() + { + for (int i = 0; i < ITERATIONS; i++) + "".Md5Text(); + } + + + private void BenchmarkMd5TextNonEmpty() + { + for (int i = 0; i < ITERATIONS; i++) + LOREM_IPSUM.Md5Text(); + } + + + private void BenchmarkSha1TextEmpty() + { + for (int i = 0; i < ITERATIONS; i++) + "".Sha1Text(); + } + + + private void BenchmarkSha1TextNonEmpty() + { + for (int i = 0; i < ITERATIONS; i++) + LOREM_IPSUM.Sha1Text(); + } + + + private void BenchmarkSha256TextEmpty() + { + for (int i = 0; i < ITERATIONS; i++) + "".Sha256Text(); + } + + + private void BenchmarkSha256TextNonEmpty() + { + for (int i = 0; i < ITERATIONS; i++) + LOREM_IPSUM.Sha256Text(); + } +} diff --git a/benchmarks/csharp/StringFormat.cs b/benchmarks/csharp/StringFormat.cs new file mode 100644 index 0000000..2fbd9c6 --- /dev/null +++ b/benchmarks/csharp/StringFormat.cs @@ -0,0 +1,113 @@ +using Godot; +using System; + +public partial class StringFormat : Benchmark +{ + + const int ITERATIONS = 1_000_000; + const string ENGINE_NAME = "Godot"; + Godot.Collections.Dictionary FORMAT_DICT = new Godot.Collections.Dictionary(){{"engine", ENGINE_NAME}}; + + string engineName = "Godot"; + int someInteger = 123456; + float someFloat = 1.2F; + Vector2I someVector2i = new Vector2I(12, 34); + + // Benchmark various ways to format strings. + + private void BenchmarkNoOpConstantMethod() + { + for (int i = 0; i < ITERATIONS; i++) + { + String.Format("Hello nothing!", new Godot.Collections.Dictionary(){}); + } + } + + private void BenchmarkSimpleConstantConcatenate() + { + for (int i = 0; i < ITERATIONS; i++) + { + string temp = "Hello " + ENGINE_NAME + "!"; + } + } + + private void BenchmarkSimpleConstantPercent() + { + for (int i = 0; i < ITERATIONS; i++) + { + string temp = $"Hello {ENGINE_NAME}!"; + } + } + + private void BenchmarkSimpleConstantMethod() + { + for (int i = 0; i < ITERATIONS; i++) + { + String.Format("Hello {0}!", new Godot.Collections.Dictionary(){{"engine", ENGINE_NAME}}["engine"]); + } + } + + private void BenchmarkSimpleConstantMethodConstantDict() + { + for (int i = 0; i < ITERATIONS; i++) + { + String.Format("Hello {0}!", FORMAT_DICT["engine"]); + } + } + + private void BenchmarkSimpleVariableConcatenate() + { + for (int i = 0; i < ITERATIONS; i++) + { + string temp = "Hello " + engineName + "!"; + } + } + + private void BenchmarkSimpleVariablePercent() + { + for (int i = 0; i < ITERATIONS; i++) + { + string temp = $"Hello {engineName}!"; + } + } + + private void BenchmarkSimpleVariableMethod() + { + for (int i = 0; i < ITERATIONS; i++) + { + String.Format("Hello {0}!", new Godot.Collections.Dictionary(){{"engine", engineName}}["engine"]); + } + } + + private void BenchmarkComplexVariableConcatenate() + { + for (int i = 0; i < ITERATIONS; i++) + { + string temp = "Hello " + engineName + "!\nA few examples of formatting: " + someInteger.ToString() + ", " + someFloat.ToString().PadDecimals(2) + ", " + someVector2i.ToString(); + } + } + + private void BenchmarkComplexVariablePercent() + { + for (int i = 0; i < ITERATIONS; i++) + { + string temp = $"Hello {engineName}!\nA few examples of formatting: {someInteger}, {someFloat:F2}, {someVector2i}"; + } + } + + private void BenchmarkComplexVariableMethod() + { + for (int i = 0; i < ITERATIONS; i++) + { + Godot.Collections.Dictionary tempDict = new Godot.Collections.Dictionary(){ + {"engine", engineName}, + {"an_integer", someInteger}, + {"a_float", someFloat.ToString().PadDecimals(2)}, + {"a_vector2i", someVector2i}, + }; + String.Format( + "Hello {0}!\nA few examples of formatting: {1}, {2}, {3}", tempDict["engine"], tempDict["an_integer"], tempDict["a_float"], tempDict["a_vector2i"] + ); + } + } +} diff --git a/benchmarks/csharp/StringManipulation.cs b/benchmarks/csharp/StringManipulation.cs new file mode 100644 index 0000000..a7f9083 --- /dev/null +++ b/benchmarks/csharp/StringManipulation.cs @@ -0,0 +1,272 @@ +using Godot; +using System; + +public partial class StringManipulation : Benchmark +{ + + const int ITERATIONS = 1_000_000; + + // Benchmark various ways to modify strings. + + private void BenchmarkBeginsWith() + { + for (int i = 0; i < ITERATIONS; i++) + "Godot Engine".StartsWith("Godot"); // true + } + + + private void BenchmarkEndsWith() + { + for (int i = 0; i < ITERATIONS; i++) + "Godot Engine".EndsWith("Engine"); // true + } + + + private void BenchmarkCount() + { + for (int i = 0; i < ITERATIONS; i++) + StringExtensions.Count("Godot Engine", "o"); // 2 + } + + + private void BenchmarkCountn() + { + for (int i = 0; i < ITERATIONS; i++) + StringExtensions.CountN("Godot Engine", "o"); // 2 + } + + + private void BenchmarkContains() + { + for (int i = 0; i < ITERATIONS; i++) + "Godot Engine".Contains("o"); // true + } + + private void BenchmarkFind() + { + for (int i = 0; i < ITERATIONS; i++) + StringExtensions.Find("Godot Engine", "o"); // 1 + } + + + private void BenchmarkFindn() + { + for (int i = 0; i < ITERATIONS; i++) + StringExtensions.FindN("Godot Engine", "o"); // 1 + } + + + private void BenchmarkRfind() + { + for (int i = 0; i < ITERATIONS; i++) + StringExtensions.RFind("Godot Engine", "o"); // 3 + } + + + private void BenchmarkRfindn() + { + for (int i = 0; i < ITERATIONS; i++) + StringExtensions.RFindN("Godot Engine", "o"); // 3 + } + + + private void BenchmarkSubstr() + { + for (int i = 0; i < ITERATIONS; i++) + "Hello Godot!".Substr(6, 5); // "Godot" + } + + + private void BenchmarkInsert() + { + for (int i = 0; i < ITERATIONS; i++) + "Hello !".Insert(6, "Godot"); // "Hello Godot!" + } + + + private void BenchmarkBigrams() + { + for (int i = 0; i < ITERATIONS; i++) + "Godot Engine".Bigrams(); // ["Go", "od", "do", "ot", "t ", " E", "En", "ng", "gi", "in", "ne"] + } + + + private void BenchmarkSplit() + { + for (int i = 0; i < ITERATIONS; i++) + "1234,5678,90.12".Split(","); // ["1234", "5678", "90.12"] + } + + + private void BenchmarkSplitFloats() + { + for (int i = 0; i < ITERATIONS; i++) + "1234,5678,90.12".SplitFloats(","); // [1234.0, 5678.0, 90.12] + } + + + private void BenchmarkPadZerosPreConstructed() + { + for (int i = 0; i < ITERATIONS; i++) + "12345".PadZeros(7); // "0012345" + } + + + private void BenchmarkPadZeros() + { + for (int i = 0; i < ITERATIONS; i++) + 12345.ToString().PadZeros(7); // "0012345" + } + + + private void BenchmarkPadDecimalsPreConstructed() + { + for (int i = 0; i < ITERATIONS; i++) + "1234.5678".PadDecimals(2); // "1234.56" + } + + + private void BenchmarkPadDecimals() + { + for (int i = 0; i < ITERATIONS; i++) + 1234.5678.ToString().PadDecimals(2); // "1234.56" + } + + + private void BenchmarkLpad() + { + for (int i = 0; i < ITERATIONS; i++) + "Godot".PadLeft(7, '+'); // "++Godot" + } + + + private void BenchmarkRpad() + { + for (int i = 0; i < ITERATIONS; i++) + "Godot".PadRight(7, '+'); // "Godot++" + } + + + private void BenchmarkSimilarity() + { + for (int i = 0; i < ITERATIONS; i++) + "Godot".Similarity("Engine"); + } + + + private void BenchmarkSimplifyPath() + { + for (int i = 0; i < ITERATIONS; i++) + "./path/to///../file".SimplifyPath(); // "path/file" + } + + + private void BenchmarkCapitalize() + { + for (int i = 0; i < ITERATIONS; i++) + "godot_engine_demo".Capitalize(); // "Godot Engine Demo" + } + + + private void BenchmarkToSnakeCase() + { + for (int i = 0; i < ITERATIONS; i++) + "GodotEngineDemo".ToSnakeCase(); // "godot_engine_demo" + } + + + private void BenchmarkToCamelCase() + { + for (int i = 0; i < ITERATIONS; i++) + "godot_engine_demo".ToSnakeCase(); // "godotEngineDemo" + } + + + private void BenchmarkToPascalCase() + { + for (int i = 0; i < ITERATIONS; i++) + "godot_engine_demo".ToPascalCase(); // "GodotEngineDemo" + } + + + private void BenchmarkToLower() + { + for (int i = 0; i < ITERATIONS; i++) + "Godot Engine Demo".ToLower(); // "godot engine demo" + } + + + private void BenchmarkUriDecode() + { + for (int i = 0; i < ITERATIONS; i++) + StringExtensions.URIDecode("Godot%20Engine%3Adocs"); // "Godot Engine:docs" + } + + + private void BenchmarkUriEncode() + { + for (int i = 0; i < ITERATIONS; i++) + StringExtensions.URIEncode("Godot Engine:docs"); // "Godot%20Engine%3Adocs" + } + + + private void BenchmarkXmlEscape() + { + for (int i = 0; i < ITERATIONS; i++) + StringExtensions.XMLEscape("Godot Engine <&>"); // "Godot Engine <&>" + } + + + private void BenchmarkXmlUnescape() + { + for (int i = 0; i < ITERATIONS; i++) + StringExtensions.XMLUnescape("Godot Engine <&>"); // "Godot Engine <&>" + } + + private void BenchmarkIsValidFilename() + { + for (int i = 0; i < ITERATIONS; i++) + StringExtensions.IsValidFileName("Godot Engine: Demo.exe"); // false + } + + private void BenchmarkValidateNodeName() + { + for (int i = 0; i < ITERATIONS; i++) + "TestNode:123456".ValidateNodeName(); // "TestNode123456" + } + + + private void BenchmarkCasecmpTo() + { + for (int i = 0; i < ITERATIONS; i++) + "2 Example".CasecmpTo("10 Example"); // 1 + } + + + private void BenchmarkNocasecmpTo() + { + for (int i = 0; i < ITERATIONS; i++) + "2 Example".NocasecmpTo("10 Example"); // 1 + } + + + private void BenchmarkToUtf8Buffer() + { + for (int i = 0; i < ITERATIONS; i++) + "Godot Engine".ToUtf8Buffer(); + } + + + private void BenchmarkToUtf16Buffer() + { + for (int i = 0; i < ITERATIONS; i++) + "Godot Engine".ToUtf16Buffer(); + } + + + private void BenchmarkToUtf32Buffer() + { + for (int i = 0; i < ITERATIONS; i++) + "Godot Engine".ToUtf32Buffer(); + } +}