Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is this code 5 times slower in C# compared to Java?

First of all we create a random binary file with 100.000.000 bytes. I used Python for this:

import random
import os

def Main():
    length = 100000000
    randomArray = random.randbytes(length)
    desktopPath = os.path.normpath(os.path.expanduser("~/Desktop"))
    fullPath=os.path.join(desktopPath,"RandomFile.dat")
    if os.path.isfile(fullPath):
        print("File already exists.")
        return
    with open(fullPath, "wb") as file:
        file.write(randomArray)
        print("The file has been created.")

Main()

After this we find how many of these bytes were greater than 100 using C# and Java.

C#:

using System.Diagnostics;

namespace TestYouTube
{
    public class Program
    {
        //finished
        public static void Main(string[] args)
        {
            string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
            string fullPath = Path.Combine(desktopPath, "RandomFile.dat");
            if (!File.Exists(fullPath))
            {
                Console.WriteLine("The file does not exist.");
                return;
            }
            byte[] byteArray = File.ReadAllBytes(fullPath);
            int[] intArray = ByteArrayToIntArray(byteArray);
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            int count = TestSpeed(intArray);
            stopwatch.Stop();
            Console.WriteLine($"Count = {count}");
            Console.WriteLine($"Elapsed: {stopwatch.Elapsed.TotalSeconds} seconds");
            Console.ReadLine();
        }

        //finished
        private static int[] ByteArrayToIntArray(byte[] byteArray)
        {
            if (byteArray == null) return null;
            if (byteArray.Length == 0) return Array.Empty<int>();
            int[] intArray = new int[byteArray.Length];
            for (int i = 0; i < intArray.Length; i++)
            {
                intArray[i] = byteArray[i];
            }
            return intArray;
        }

        //finished
        private static int TestSpeed(int[] array)
        {
            if (array == null) throw new ArgumentNullException();
            if (array.Length == 0) throw new ArgumentException();
            int count = 0;
            foreach (int element in array)
            {
                if (element > 100)
                {
                    count++;
                }
            }
            return count;
        }
    }
}

Java:

import javax.swing.filechooser.*;
import java.io.*;
import java.nio.file.*;

public class Main
{
    public static void main(String[] args) throws IOException
    {
        FileSystemView view = FileSystemView.getFileSystemView();
        File file = view.getHomeDirectory();
        String desktopPath = file.getPath();
        Path fullPath = Paths.get(desktopPath,"RandomFile.dat");
        File randomFile=new File(fullPath.toString());
        if (!randomFile.exists() || randomFile.isDirectory())
        {
            System.out.println("The file does not exist.");
            return;
        }
        byte[] array = Files.readAllBytes(randomFile.toPath());
        int[] intArray=ByteArrayToUnsignedByteArray(array);
        long start = System.currentTimeMillis();
        int count=TestSpeed(intArray);
        long end = System.currentTimeMillis();
        long elapsed=end-start;
        double elapsedSeconds=((double)elapsed)/1000;
        System.out.printf("Count = %d\n",count);
        System.out.printf("Elapsed: %f seconds\n",elapsedSeconds);
    }

    private static int ByteToUnsignedByte(byte b)
    {
        return b & 0b11111111;
    }

    private static int[] ByteArrayToUnsignedByteArray(byte[] byteArray)
    {
        if (byteArray==null) return null;
        if (byteArray.length==0) return new int[0];
        int[] newArray=new int[byteArray.length];
        for (int i=0;i<byteArray.length;i++)
        {
            int element=ByteToUnsignedByte(byteArray[i]);
            newArray[i]=element;
        }
        return newArray;
    }

    private static int TestSpeed(int[] byteArray)
    {
        if (byteArray==null) throw new IllegalArgumentException("The array must not be null.");
        if (byteArray.length==0) throw new IllegalArgumentException("The array length must be non zero.");
        int count=0;
        for (int element : byteArray)
        {
            if (element>100)
            {
                count++;
            }
        }
        return count;
    }
}

Please note that we only measure the time of the function TestSpeed in both languages.

Results: C#:
Count = 60542647
Elapsed: 0,4286155 seconds

Java:
Count = 60542647
Elapsed: 0,077000 seconds

I ran the code multiple times and the result is about the same. C# is about 5 times slower than Java. Please note that I ran the code from "Release" and also I used "Start Without Debbuging".

Does anybody know why this happens? What am I doing wrong?


According to comments it's better to benchmark the C# code with a benchmark library so I used BenchmarkDotNet for this. The results:
TestSpeed Mean: 411.2 ms
Which is not really different from Stopwatch.

  • .NET version: .NET 9.0,
  • Java openjdk version "24.0.1" 2025-04-15,
  • OS: Microsoft Windows [Version 10.0.19045.5965],
  • CPU name: AMD Athlon Gold 3150U with Radeon Graphics (dual core Zen 1)
like image 419
Vasilis Kontopoulos Avatar asked Sep 09 '25 16:09

Vasilis Kontopoulos


1 Answers

Matthew Watson's and Charlieface's answers actually explain the reason for this question, because compared to CLR, JVM has implemented more loop optimizations, including vectorization and loop unrolling, which allows the JVM to use fewer jump and inc instructions and save more time when the loop length is very long.

There is an issue on GitHub that tracks the progress of .NET loop optimizations. unfortunately the above optimizations is still ongoing.

like image 144
shingo Avatar answered Sep 12 '25 04:09

shingo