In fact both C# and F# beat Java now on most of the benchmarks on the site (though not by wide margins), which I think is a reversal from a couple of years ago on Mono.
Yes, it runs on .NET Core now and thats why its much faster.
I'm still surprised, because I thought the JVM was legendarily optimized over many years, and I expected it would have to be more advanced and optimized than Microsoft's VM, especially on Linux, but that doesn't seem to be the case.
In some of the cases where .NET Core wins over JVM the C# code uses e.g. raw pointer arithmetic without bound checks (especially in two of the cases where .NET Core wins the most: k-nucleotide and mandelbrot). Also the tests are done on pretty old hardware (Core 2 Quad Q6600), so if new JVM is heavily tuned for new architectures then it will lose a bit on older ones.
Another thing to consider is the type of optimizations we are measuring. Benchmarks game only checks low level optimization strategies, i.e. careful selection of CPU instructions. However, high level object oriented code contains a lot of things that can be optimized away entirely, eg: methods can be devirtualized so v-table checks are eliminated, escape analysis allows to allocate things on stack instead of heap removing GC pressure, synchronization can be elided if it is determined that some chunk of code always run within one thread and so on.
For example consider following Java code:
Code:
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
interface Function<T> {
long perform(T parameter, long x);
}
class MyArray<T> {
T[] array;
long x;
public MyArray(int size, Class<T> type, long x) {
array = (T[]) Array.newInstance(type, size);
this.x = x;
}
public void forEach(Function<T> function) {
for (T element : array) {
x = function.perform(element, x);
}
}
}
class Compute {
int factor;
final long constant;
public Compute(int factor, long constant) {
this.factor = factor;
this.constant = constant;
}
public long compute(long parameter, long x) {
return x * factor + parameter + constant;
}
}
public class Main {
public static void main(String[] args) {
List<Long> numbers = new ArrayList<Long>(50000000);
for (int i = 0; i < 50000000; i++) {
numbers.add(i * i + 5L);
}
long x = 234553523525L;
long time = System.currentTimeMillis();
for (int i = 0; i < numbers.size(); i++) {
x += x * 7 + numbers.get(i) + 3;
}
System.out.println(System.currentTimeMillis() - time);
System.out.println(x);
x = 0;
time = System.currentTimeMillis();
for (long i : numbers) {
x += x * 7 + i + 3;
}
System.out.println(System.currentTimeMillis() - time);
System.out.println(x);
x = 0;
numbers = null;
MyArray<Long> myArray = new MyArray<Long>(50000000, Long.class, 234553523525L);
for (int i = 0; i < 50000000; i++) {
myArray.array[i] = i * i + 3L;
}
time = System.currentTimeMillis();
myArray.forEach(new Function<Long>() {
public long perform(Long parameter, long x) {
return x * 8 + parameter + 5L;
}
});
System.out.println(System.currentTimeMillis() - time);
System.out.println(myArray.x);
myArray = null;
myArray = new MyArray<Long>(50000000, Long.class, 234553523525L);
for (int i = 0; i < 50000000; i++) {
myArray.array[i] = i * i + 3L;
}
time = System.currentTimeMillis();
myArray.forEach(new Function<Long>() {
public long perform(Long parameter, long x) {
return new Compute(8, 5).compute(parameter, x);
}
});
System.out.println(System.currentTimeMillis() - time);
System.out.println(myArray.x);
}
}
It does some basic arithmetic operations on each element of a list and sums the results together. It does that in a few different ways, starting from the most basic ones (ordinary for loops) going to closures and extra classes for arithmetic transformations. In all cases the times were comparable. My results on Java 6 update 24 done some years ago:
276
-699150247503735895
274
-699150247503735895
248
-699150247503735895
266
-699150247503735895
That is impressive. Java 6u24 was released 15th Feb 2011 which is almost 8 years ago. Such optimizations are AFAIR still unavaliable to C# programmers.