Introduction

After my previous benchmark comparing .NET 9 and Go 1.22 was well received, I knew I had to revisit this battle with the release of .NET 10. This time, I also updated Go to the latest version (1.26.4) to keep things current. Both runtimes were compiled in Release/Optimized mode on the same hardware for a fair comparison.

Executive Summary

๐Ÿ† .NET 10 wins overall by 12.5% in total execution time:

  • Go dominates in mathematical operations (+36.8%) and prime calculation (+13.9%)
  • .NET 10 dominates in collection processing (+104.8%) and string manipulation (+41.1%)
  • The overall winner depends heavily on workload mix โ€” this is a tale of two runtimes with very different strengths

The Setup: No Bias, Pure Performance

Same methodology as the original: identical algorithms, data structures, and operations in both languages. Built and run in Release mode with all optimizations enabled.

Versions tested:

  • .NET SDK 10.0.301 (Runtime 10.0.9)
  • Go 1.26.4

Benchmark Categories:

  1. Prime Number Calculation (CPU-intensive)
  2. String Manipulation (Memory allocation)
  3. Mathematical Operations (Floating-point performance)
  4. Collection Processing (Memory management)

The Code: Identical Implementations

.NET 10 Implementation

using System.Diagnostics;

Console.WriteLine("Starting benchmarks...");
var stopwatch = Stopwatch.StartNew();

PrimeBenchmark();
StringManipulationBenchmark();
MathematicalBenchmark();
CollectionBenchmark();

stopwatch.Stop();
Console.WriteLine($"\nAll benchmarks completed in {stopwatch.ElapsedMilliseconds}ms");

static void PrimeBenchmark()
{
    Console.WriteLine("\n=== Prime Number Benchmark ===");
    var stopwatch = Stopwatch.StartNew();

    var count = 0;
    for (var i = 2; i <= 1000000; i++)
    {
        if (IsPrime(i)) count++;
    }

    stopwatch.Stop();
    Console.WriteLine($"Found {count} primes in {stopwatch.ElapsedMilliseconds}ms");
}

static bool IsPrime(int number)
{
    if (number <= 1) return false;
    if (number == 2) return true;
    if (number % 2 == 0) return false;

    var boundary = (int)Math.Floor(Math.Sqrt(number));
    for (var i = 3; i <= boundary; i += 2)
    {
        if (number % i == 0) return false;
    }
    return true;
}

static void StringManipulationBenchmark()
{
    Console.WriteLine("\n=== String Manipulation Benchmark ===");
    var stopwatch = Stopwatch.StartNew();

    var result = "";
    for (var i = 0; i < 100000; i++)
    {
        result += i.ToString();
        if (result.Length > 10000)
        {
            result = result[..Math.Min(result.Length, 5000)];
        }
    }

    stopwatch.Stop();
    Console.WriteLine($"String manipulation completed in {stopwatch.ElapsedMilliseconds}ms");
}

static void MathematicalBenchmark()
{
    Console.WriteLine("\n=== Mathematical Operations Benchmark ===");
    var stopwatch = Stopwatch.StartNew();

    double sum = 0;
    for (var i = 0; i < 10000000; i++)
    {
        sum += Math.Sqrt(i) * Math.Sin(i) + Math.Cos(i) / Math.Sqrt(i + 1);
    }

    stopwatch.Stop();
    Console.WriteLine($"Mathematical operations completed in {stopwatch.ElapsedMilliseconds}ms");
    Console.WriteLine($"Sum: {sum}");
}

static void CollectionBenchmark()
{
    Console.WriteLine("\n=== Collection Operations Benchmark ===");
    var stopwatch = Stopwatch.StartNew();

    var list = new List<int>();
    for (var i = 0; i < 1000000; i++) list.Add(i);

    var evenNumbers = new List<int>();
    foreach (var x in list)
    {
        if (x % 2 == 0) evenNumbers.Add(x);
    }

    var squaredNumbers = new List<long>();
    foreach (var x in evenNumbers)
    {
        squaredNumbers.Add((long)x * x);
    }

    long total = 0;
    foreach (var x in squaredNumbers) total += x;

    stopwatch.Stop();
    Console.WriteLine($"Collection operations completed in {stopwatch.ElapsedMilliseconds}ms");
    Console.WriteLine($"Total: {total}");
}

Go 1.26 Implementation

package main

import (
	"fmt"
	"math"
	"time"
)

func main() {
	fmt.Println("Starting benchmarks...")
	start := time.Now()

	primeBenchmark()
	stringManipulationBenchmark()
	mathematicalBenchmark()
	collectionBenchmark()

	elapsed := time.Since(start)
	fmt.Printf("\nAll benchmarks completed in %v\n", elapsed)
}

func primeBenchmark() {
	fmt.Println("\n=== Prime Number Benchmark ===")
	start := time.Now()
	count := 0
	for i := 2; i <= 1000000; i++ {
		if isPrime(i) { count++ }
	}
	elapsed := time.Since(start)
	fmt.Printf("Found %d primes in %v\n", count, elapsed)
}

func isPrime(number int) bool {
	if number <= 1 { return false }
	if number == 2 { return true }
	if number%2 == 0 { return false }
	boundary := int(math.Floor(math.Sqrt(float64(number))))
	for i := 3; i <= boundary; i += 2 {
		if number%i == 0 { return false }
	}
	return true
}

func stringManipulationBenchmark() {
	fmt.Println("\n=== String Manipulation Benchmark ===")
	start := time.Now()
	result := ""
	for i := 0; i < 100000; i++ {
		result += fmt.Sprintf("%d", i)
		if len(result) > 10000 && len(result) > 5000 {
			result = result[:5000]
		}
	}
	elapsed := time.Since(start)
	fmt.Printf("String manipulation completed in %v\n", elapsed)
}

func mathematicalBenchmark() {
	fmt.Println("\n=== Mathematical Operations Benchmark ===")
	start := time.Now()
	sum := 0.0
	for i := 0; i < 10000000; i++ {
		sum += math.Sqrt(float64(i))*math.Sin(float64(i)) + math.Cos(float64(i))/math.Sqrt(float64(i)+1)
	}
	elapsed := time.Since(start)
	fmt.Printf("Mathematical operations completed in %v\n", elapsed)
	fmt.Printf("Sum: %f\n", sum)
}

func collectionBenchmark() {
	fmt.Println("\n=== Collection Operations Benchmark ===")
	start := time.Now()
	list := make([]int, 0)
	for i := 0; i < 1000000; i++ { list = append(list, i) }

	evenNumbers := make([]int, 0)
	for _, x := range list {
		if x%2 == 0 { evenNumbers = append(evenNumbers, x) }
	}

	squaredNumbers := make([]int64, 0)
	for _, x := range evenNumbers {
		squaredNumbers = append(squaredNumbers, int64(x)*int64(x))
	}

	var total int64 = 0
	for _, x := range squaredNumbers { total += x }

	elapsed := time.Since(start)
	fmt.Printf("Collection operations completed in %v\n", elapsed)
	fmt.Printf("Total: %d\n", total)
}

Results (10-run average)

Benchmark.NET 10Go 1.26WinnerDifference
Prime Calculation124ms109msGo+13.9%
String Manipulation536ms757ms.NET+41.1%
Math Operations398ms291msGo+36.8%
Collection Processing52ms107ms.NET+104.8%
Total Time1.124s1.264s.NET+12.5%

Key Insights

Where Go Truly Shines

  • Mathematical Operations: 36.8% faster โ€” Go’s math library continues to deliver exceptional floating-point throughput
  • CPU-Bound Tasks: Better raw computation for algorithms like prime calculation, though the gap narrowed from this setup’s perspective

Where .NET 10 Dominates

  • Collection Processing: Massive 104.8% advantage โ€” .NET 10’s JIT and GC improvements over .NET 9 make this an absolute crushing
  • String Manipulation: 41.1% faster โ€” significant improvements in string handling, likely from .NET 10’s runtime optimizations
  • Memory Management: Better GC performance for large data sets, especially visible in the collection benchmark

๐Ÿ” Surprising Findings

  • .NET 10 flipped the overall result from the previous benchmark, going from a 13.2% loss to a 12.5% win
  • Collection operations show the biggest gap in favor of .NET by a massive margin

Final Thoughts

The runtime improvements in .NET 10 are real โ€” especially in collection and string processing โ€” but Go remains formidable in math and CPU-bound workloads.

Developer productivity, ecosystem fit, and team expertise should remain the primary decision factors. Run your own benchmarks before making a choice.

What’s Next

๐Ÿ”ฎ .NET 11 Preview: .NET 11 preview builds are already available. I’ll be testing them against Go 1.27 when it hits stable โ€” follow for the next chapter in this ongoing rivalry.