Skip to content

Commit eaf4b48

Browse files
authored
Merge pull request #423 from janosorcsik/csharp_in_process
Add C# in-process runner * Fixes #381
2 parents 6f6aeae + 7020386 commit eaf4b48

File tree

22 files changed

+315
-20
lines changed

22 files changed

+315
-20
lines changed

.gitignore

+3-1
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,6 @@ hello-world/*/run
5959
*/mealg/*.ez
6060
*/mealg/build
6161
erl_crash.dump
62-
manifest.toml
62+
manifest.toml
63+
64+
.DS_Store

clean.sh

+9-5
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,14 @@ function clean_benchmark {
3434
rm lua/code
3535
rm -f swift/code
3636
rm haxe/code.jar
37-
rm -rf csharp/bin
38-
rm -rf csharp/obj
39-
rm -rf csharp/code-aot
40-
rm -rf csharp/code
37+
rm -rf csharp/legacy/bin
38+
rm -rf csharp/legacy/obj
39+
rm -rf csharp/legacy/code-aot
40+
rm -rf csharp/legacy/code
41+
rm -rf csharp/in-process/bin
42+
rm -rf csharp/in-process/obj
43+
rm -rf csharp/in-process/code-aot
44+
rm -rf csharp/in-process/code
4145
rm -rf fsharp/bin
4246
rm -rf fsharp/obj
4347
rm -rf fsharp/code-aot
@@ -83,4 +87,4 @@ fi
8387
for benchmark_dir in "${benchmarks_to_clean[@]}"; do
8488
echo
8589
clean_benchmark "${benchmark_dir}"
86-
done
90+
done

compile-legacy.sh

+2-2
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ compile 'fortran' 'gfortran -O3 fortran/legacy/code.f90 -o fortran/code'
4242
compile 'zig' 'zig build-exe -O ReleaseFast -femit-bin=zig/code zig/code.zig'
4343
compile 'lua' 'luajit -b lua/code.lua lua/code'
4444
compile 'swift' 'swiftc -O -parse-as-library -Xcc -funroll-loops -Xcc -march=native -Xcc -ftree-vectorize -Xcc -ffast-math swift/legacy/code.swift -o swift/code'
45-
compile 'csharp' 'dotnet publish csharp -o csharp/code'
46-
compile 'csharp' 'dotnet publish csharp -o csharp/code-aot /p:PublishAot=true /p:OptimizationPreference=Speed /p:IlcInstructionSet=native'
45+
compile 'csharp' 'dotnet publish csharp/legacy -o csharp/legacy/code'
46+
compile 'csharp' 'dotnet publish csharp/legacy -o csharp/legacy/code-aot /p:PublishAot=true /p:OptimizationPreference=Speed /p:IlcInstructionSet=native'
4747
compile 'fsharp' 'dotnet publish fsharp -o fsharp/code'
4848
compile 'fsharp' 'dotnet publish fsharp -o fsharp/code-aot /p:PublishAot=true /p:OptimizationPreference=Speed /p:IlcInstructionSet=native'
4949
compile 'haskell' 'ghc -O2 -fllvm haskell/code.hs -o haskell/code || { echo "ghc: cannot compile with llvm backend; fallback to use default backend"; ghc -O2 haskell/code.hs -o haskell/code; }'

fibonacci/csharp/in-process/code.cs

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
var runMs = int.Parse(args[0]);
2+
var warmupMs = int.Parse(args[1]);
3+
var n = int.Parse(args[2]);
4+
5+
Benchmark<int>.Run(() => Fibonacci(n), warmupMs);
6+
7+
var result = Benchmark<int>.Run(() => Fibonacci(n), runMs);
8+
9+
Console.WriteLine(result);
10+
11+
return;
12+
13+
static int Fibonacci(int n) =>
14+
n <= 1 ? n : Fibonacci(n - 1) + Fibonacci(n - 2);
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net9.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<Compile Include="..\..\..\lib\csharp\benchmark.cs">
12+
<Link>benchmark.cs</Link>
13+
</Compile>
14+
</ItemGroup>
15+
16+
</Project>
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
static int Fibonacci(int n) =>
1+
static int Fibonacci(int n) =>
22
n <= 1 ? n : Fibonacci(n - 1) + Fibonacci(n - 2);
33

44
var u = int.Parse(args[0]);
@@ -7,4 +7,4 @@
77
{
88
r += Fibonacci(i);
99
}
10-
Console.WriteLine(r);
10+
Console.WriteLine(r);
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
@@ -7,4 +7,4 @@
77
<Nullable>enable</Nullable>
88
</PropertyGroup>
99

10-
</Project>
10+
</Project>
File renamed without changes.
File renamed without changes.

hello-world/csharp/legacy/code.cs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Console.WriteLine("Hello, World!");
File renamed without changes.

languages.sh

+5-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ function compile_languages {
1010
compile 'Clojure Native' 'clojure-native-image' '(cd clojure-native-image ; clojure -M:native-image-run --pgo-instrument -march=native) ; ./clojure-native-image/run -XX:ProfilesDumpFile=clojure-native-image/run.iprof 10000 2000 $(./check-output.sh -i) && (cd clojure-native-image ; clojure -M:native-image-run --pgo=run.iprof -march=native)'
1111
compile 'Crystal' 'crystal' 'crystal build --release --mcpu native crystal/run.cr -o crystal/run'
1212
compile 'C++' 'cpp' 'g++ -march=native -std=c++23 -O3 -Ofast -I../lib/cpp cpp/run.cpp -o cpp/run'
13+
compile 'C#' 'csharp' 'dotnet publish csharp/in-process -o csharp/in-process/code'
14+
compile 'C# AOT' 'csharp' 'dotnet publish csharp/in-process -o csharp/in-process/code-aot /p:PublishAot=true /p:OptimizationPreference=Speed /p:IlcInstructionSet=native'
1315
compile 'Fortran' 'fortran' 'gfortran -O3 -J../lib/fortran ../lib/fortran/benchmark.f90 fortran/*.f90 -o fortran/run'
1416
compile 'Gleam' 'maelg' '(cd maelg && gleam build --target erlang)'
1517
compile 'Java' 'jvm' 'javac -cp ../lib/java jvm/*.java'
@@ -29,6 +31,8 @@ function run_languages {
2931
run 'Clojure Native' './clojure-native-image/run' './clojure-native-image/run'
3032
run "Crystal" "./crystal/run" "./crystal/run"
3133
run 'C++' './cpp/run' './cpp/run'
34+
run 'C#' './csharp/in-process/code/code' './csharp/in-process/code/code'
35+
run 'C# AOT' './csharp/in-process/code-aot/code' './csharp/in-process/code-aot/code'
3236
run 'Fortran' './fortran/run' './fortran/run'
3337
run 'Gleam' './maelg/build/dev/erlang/run/ebin/run.beam' "./maelg/run.sh"
3438
run 'Java' './jvm/run.class' 'java -cp .:../lib/java jvm.run'
@@ -43,4 +47,4 @@ function run_languages {
4347
run 'Rust' './rust/target/release/run' './rust/target/release/run'
4448
run 'Swift' './swift/run' './swift/run'
4549
run 'Zig' './zig/zig-out/bin/run' './zig/zig-out/bin/run'
46-
}
50+
}

levenshtein/csharp/in-process/code.cs

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
using System.Runtime.CompilerServices;
2+
3+
var runMs = int.Parse(args[0]);
4+
var warmupMs = int.Parse(args[1]);
5+
var inputPath = args[2];
6+
7+
var content = File.ReadAllLines(inputPath);
8+
9+
Benchmark<List<int>>.Run(() => Levenshtein(content), warmupMs);
10+
11+
var result = Benchmark<List<int>>.Run(() => Levenshtein(content), runMs);
12+
13+
var summedResult = new BenchmarkResult<int>(
14+
result!.MeanMs,
15+
result.StdDevMs,
16+
result.MinMs,
17+
result.MaxMs,
18+
result.Runs,
19+
result.Result.Sum());
20+
21+
Console.WriteLine(summedResult);
22+
23+
return;
24+
25+
static List<int> Levenshtein(string[] content)
26+
{
27+
var distances = new List<int>();
28+
29+
for (var i = 0; i < content.Length; i++)
30+
{
31+
for (var j = i + 1; j < content.Length; j++)
32+
{
33+
distances.Add(LevenshteinDistance(content[i], content[j]));
34+
}
35+
}
36+
37+
return distances;
38+
}
39+
40+
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
41+
static int LevenshteinDistance(ReadOnlySpan<char> str1, ReadOnlySpan<char> str2)
42+
{
43+
// Early termination checks
44+
if (str1.SequenceEqual(str2))
45+
{
46+
return 0;
47+
}
48+
49+
if (str1.IsEmpty)
50+
{
51+
return str2.Length;
52+
}
53+
54+
if (str2.IsEmpty)
55+
{
56+
return str1.Length;
57+
}
58+
59+
// Ensure str1 is the shorter string
60+
if (str1.Length > str2.Length)
61+
{
62+
var strtemp = str2;
63+
str2 = str1;
64+
str1 = strtemp;
65+
}
66+
67+
// Create two rows, previous and current
68+
Span<int> prev = stackalloc int[str1.Length + 1];
69+
Span<int> curr = stackalloc int[str1.Length + 1];
70+
71+
// initialize the previous row
72+
for (var i = 0; i <= str1.Length; i++)
73+
{
74+
prev[i] = i;
75+
}
76+
77+
// Iterate and compute distance
78+
for (var i = 1; i <= str2.Length; i++)
79+
{
80+
curr[0] = i;
81+
for (var j = 1; j <= str1.Length; j++)
82+
{
83+
var cost = (str1[j - 1] == str2[i - 1]) ? 0 : 1;
84+
curr[j] = Math.Min(
85+
prev[j] + 1, // Deletion
86+
Math.Min(curr[j - 1] + 1, // Insertion
87+
prev[j - 1] + cost) // Substitution
88+
);
89+
}
90+
91+
// Swap spans
92+
var temp = prev;
93+
prev = curr;
94+
curr = temp;
95+
}
96+
97+
// Return final distance, stored in prev[m]
98+
return prev[str1.Length];
99+
}
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net9.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<Compile Include="..\..\..\lib\csharp\benchmark.cs">
12+
<Link>benchmark.cs</Link>
13+
</Compile>
14+
</ItemGroup>
15+
16+
</Project>

levenshtein/csharp/code.cs levenshtein/csharp/legacy/code.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,4 @@ static int levenshtein(ReadOnlySpan<char> str1, ReadOnlySpan<char> str2)
7777

7878
// Return final distance, stored in prev[m]
7979
return prev[str1.Length];
80-
}
80+
}
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
@@ -7,4 +7,4 @@
77
<Nullable>enable</Nullable>
88
</PropertyGroup>
99

10-
</Project>
10+
</Project>

lib/csharp/benchmark.cs

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using System.Diagnostics;
2+
3+
public static class Benchmark<T>
4+
{
5+
public static BenchmarkResult<T>? Run(Func<T> func, int runMs)
6+
{
7+
if (runMs <= 0)
8+
{
9+
return null;
10+
}
11+
12+
var runNs = runMs * 1_000_000L;
13+
14+
var runs = 0;
15+
var sum = 0L;
16+
var min = long.MaxValue;
17+
var max = 0L;
18+
var sumSq = 0L;
19+
20+
T lastResult = default!;
21+
22+
var lastTime = Stopwatch.GetTimestamp();
23+
24+
while (sum < runNs)
25+
{
26+
var startTime = Stopwatch.GetTimestamp();
27+
lastResult = func();
28+
var endTime = Stopwatch.GetTimestamp();
29+
30+
var elapsed = endTime - startTime;
31+
32+
runs++;
33+
34+
sum += elapsed;
35+
sumSq += elapsed * elapsed;
36+
37+
min = Math.Min(min, elapsed);
38+
max = Math.Max(max, elapsed);
39+
40+
if (runMs > 1 && (startTime - lastTime) > Stopwatch.Frequency)
41+
{
42+
lastTime = endTime;
43+
Console.Error.Write('.');
44+
}
45+
}
46+
47+
if (runMs > 1)
48+
{
49+
Console.Error.WriteLine();
50+
}
51+
52+
var mean = (double)sum / runs;
53+
var variance = (double)sumSq / runs - Math.Pow(mean, 2);
54+
var stdDev = Math.Sqrt(variance);
55+
56+
var ticksToMs = 1000D / Stopwatch.Frequency;
57+
58+
return new BenchmarkResult<T>(
59+
mean * ticksToMs,
60+
stdDev * ticksToMs,
61+
min * ticksToMs,
62+
max * ticksToMs,
63+
runs,
64+
lastResult);
65+
}
66+
}
67+
68+
69+
public record BenchmarkResult<T>(
70+
double MeanMs,
71+
double StdDevMs,
72+
double MinMs,
73+
double MaxMs,
74+
int Runs,
75+
T Result)
76+
{
77+
public override string ToString()
78+
{
79+
return $"{MeanMs:F6},{StdDevMs:F6},{MinMs:F6},{MaxMs:F6},{Runs},{Result}";
80+
}
81+
};

loops/csharp/in-process/code.cs

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
var runMs = int.Parse(args[0]);
2+
var warmupMs = int.Parse(args[1]);
3+
var n = int.Parse(args[2]);
4+
5+
Benchmark<int>.Run(() => Loops(n), warmupMs);
6+
7+
var result = Benchmark<int>.Run(() => Loops(n), runMs);
8+
9+
Console.WriteLine(result);
10+
11+
return;
12+
13+
int Loops(int u)
14+
{
15+
var r = Random.Shared.Next(10_000);
16+
17+
//Use span instead of array
18+
Span<int> a = stackalloc int[10_000];
19+
20+
//using a.Length instead of 10_000 constant should give the compiler better optimization hints
21+
for (var i = 0; i < a.Length; i++)
22+
{
23+
for (var j = 0; j < 10_000; j++)
24+
{
25+
a[i] += j % u;
26+
}
27+
28+
a[i] += r;
29+
}
30+
31+
return a[r];
32+
}

loops/csharp/in-process/code.csproj

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net9.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<Compile Include="..\..\..\lib\csharp\benchmark.cs">
12+
<Link>benchmark.cs</Link>
13+
</Compile>
14+
</ItemGroup>
15+
16+
</Project>

0 commit comments

Comments
 (0)