'surprising c# execution times, IL, vs AOT, vs WASM (and c and JS too)
Writing a WASM app that needs some fast compute power (signal processing). Trying to decide if I should use c, JS or c# (I already have c# library that I wrote). So I decided to do CPU intense workload and compare. Will use Sieve of E
First to get a baseline I decided to just do on desktop, not WASM.
So here is c# code I used.
public static class App {
static char[] prime = new char[100000];
public static int SieveOfEratosthenes(int n) {
Array.Fill<char>(prime, (char)1);
for (int p = 2; p * p <= n; p++) {
// If prime[p] is not changed,
// then it is a prime
if (prime[p] == 1) {
// Update all multiples of p
for (int i = p * p; i <= n; i += p)
prime[i] = (char)0;
}
}
int count = 0;
// Print all prime numbers
for (int i = 2; i <= n; i++) {
if (prime[i] == 1)
count++;
}
return count;
}
public static void Run() {
for (int i = 2; i < 99999; i++) {
SieveOfEratosthenes(i);
}
// Driver Code
}
static void Main() {
var start = DateTime.Now;
Run();
var end = DateTime.Now;
var time = (end - start).TotalMilliseconds;
Console.WriteLine(time);
}
}
- Run release build using regular compiled output = ~14 seconds
- Run crossgen2 x64 release output = ~22 seconds (!)
- run crossgen2 x86 release output = ~12 seconds
Crossgen2 done by doing 'publish' in vs2022 and choose specific platform
I am surprised to see that the x64 is so slow. Any thoughts?
Another baseline I did was the same calculation in c and JS (still on desktop)
- run c equivalent code = ~4 seconds
- JS code in node = ~26 seconds
JS code
prime = new Uint8Array(100000);
function sieveOfEratosthenes(n)
{
prime.fill(1);
for (p = 2; p * p <= n; p++)
{
// If prime[p] is not changed, then it is a
// prime
if (prime[p] == 1)
{
// Update all multiples of p
for (i = p * p; i <= n; i += p)
prime[i] = 0;
}
}
var count = 0;
// Print all prime numbers
for (i = 2; i <= n; i++)
{
if (prime[i] == 1)
count++;
}
return count;
}
globalThis.sieve = () => {
// Driver Code
var n = 30;
console.time("sieve");
for (var j = 3; j < 99999; j++) {
var count = sieveOfEratosthenes(j);
//console.log(count);
}
console.timeEnd("sieve");
}
globalThis.sieve();
C code
#include <memory.h>
#include <stdio.h>
#include <time.h>
int SieveOfEratosthenes(int n)
{
static char prime[100000];
memset(prime, 1, sizeof(prime));
for (int p = 2; p * p <= n; p++)
{
// If prime[p] is not changed,
// then it is a prime
if (prime[p] == 1)
{
for (int i = p * p; i <= n; i += p)
prime[i] = 0;
}
}
int count = 0;
// Print all prime numbers
for (int p = 2; p <= n; p++)
if (prime[p])
count++;
return count;
}
// Driver Code
int sieve()
{
int start = time(NULL);
for (int i = 3; i < 99999;i++)
{
int count = SieveOfEratosthenes(i);
// printf("%d ", i/count);
}
int end = time(NULL);
// printf("time=%d", end - start);
return 0;
}
int main(int argc, char * argv[])
{
sieve();
}
Second surprise is how much slower c# is that C. I didnt expect that much of a hit. Nor did I expect the JS version to give c# such a close race.
FYI, I WASMd the C code and the c# code and ran in Blazor App
- c code ~4 seconds
- JS code ~27 secs (as expected)
- c# code 26 minutes !!
- c# code AOT ~26 seconds
Lessons
- C# blazor not AOT is horrifically slow
- AOT c# ~= JS
- C is best (a surprise that its so much faster)
Anybody think I messed anything up getting these numbers, as I said there are a few surprises in there.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
