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:
- Prime Number Calculation (CPU-intensive)
- String Manipulation (Memory allocation)
- Mathematical Operations (Floating-point performance)
- 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 10 | Go 1.26 | Winner | Difference |
|---|---|---|---|---|
| Prime Calculation | 124ms | 109ms | Go | +13.9% |
| String Manipulation | 536ms | 757ms | .NET | +41.1% |
| Math Operations | 398ms | 291ms | Go | +36.8% |
| Collection Processing | 52ms | 107ms | .NET | +104.8% |
| Total Time | 1.124s | 1.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.
