-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Description
Main Description
In DEBUG version, there seems to be a regression when it comes to
Math.Sqrt(x*x+y*y)
method.
I have a nunit test test case to reproduce it. It fits nicely into a single project with "only" some 16 main classes and a single test method.
RELEASE version
I first try to compile the code in RELEASE mode, and run the code in .net 8.0, .net 9.0 and .net 10.0, in NUnit test adapter, and .Net 10.0 comes out fastest always. This is expected.
I run with BDN with the following script:
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
namespace SqrtExplore;
[SimpleJob(RuntimeMoniker.Net80, baseline: true)]
[SimpleJob(RuntimeMoniker.Net90)]
[SimpleJob(RuntimeMoniker.Net10_0)]
[MemoryDiagnoser]
[Config(typeof(BenchmarkConfig))]
public class PolygonBenchmarks
{
private class BenchmarkConfig : ManualConfig
{
public BenchmarkConfig()
{
AddJob(Job.Default);
WithOptions(ConfigOptions.DisableOptimizationsValidator);
}
}
private PolygonD _poly1 = null!;
private PolygonD _poly2 = null!;
[GlobalSetup]
public void Setup()
{
_poly1 = PolygonBuilder.BuildPolygon1();
_poly2 = PolygonBuilder.BuildPolygon2();
}
[Benchmark]
public void Sqrt()
{
for (int j = 0; j < _poly1.PCount - 1; j++)
{
var dx = _poly1.PointList[j].X - _poly1.PointList[j + 1].X;
var dy = _poly1.PointList[j].Y - _poly1.PointList[j + 1].Y;
_ = Math.Sqrt(dx * dx + dy * dy);
}
}
}
and here's the result:
| Method | Job | Runtime | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|-------- |---------- |---------- |-------------:|------------:|------------:|------:|--------:|-------:|----------:|------------:|
| | | | | | | | | | | |
| Overlap | .NET 10.0 | .NET 10.0 | 83,041.0 ns | 848.84 ns | 752.48 ns | 0.74 | 0.01 | 0.1221 | 2680 B | 0.99 |
| Overlap | .NET 8.0 | .NET 8.0 | 112,175.8 ns | 1,869.82 ns | 1,749.03 ns | 1.00 | 0.02 | 0.1221 | 2720 B | 1.00 |
| Overlap | .NET 9.0 | .NET 9.0 | 114,751.4 ns | 1,902.91 ns | 1,779.99 ns | 1.02 | 0.02 | 0.1221 | 2680 B | 0.99 |
.Net 10.0 is the fastest by a wide margin
DEBUG version
However, compile the code in DEBUG mode, and run the EXACTLY same code in .net 8.0, .net 9.0 and .net 10.0, in NUnit test adapter will yield different results
.Net 8.0
Overlap_BenchmarkIterations
Source: BenchmarkTest.cs line 958
Duration: 1.2 sec
Standard Output:
Runtime: .NET 8.0.25
1000 iterations: 1210ms
Per call: 1210.47µs
.Net 9.0
Overlap_BenchmarkIterations
Source: BenchmarkTest.cs line 958
Duration: 3.9 sec
Standard Output:
Runtime: .NET 9.0.14
1000 iterations: 3811ms
Per call: 3811.10µs
.Net 10.0
Overlap_BenchmarkIterations
Source: BenchmarkTest.cs line 958
Duration: 3.4 sec
Standard Output:
Runtime: .NET 10.0.5
1000 iterations: 3371ms
Per call: 3371.88µs
See, the time taken for .net 9.0 is about 3 times as much as .net 8.0! even though that they run on exactly the same code.
The main regression happens from .net 8.0 to .net 9.0 version. .Net 10 does register some improvements from .Net 9.0 (on unrelated matters), but that's not nearly enough to cover the lost ground.
Additional observation
In the method XMath.Distance2D
public static double Distance2D(PointD pt1, PointD pt2)
{
double deltax = pt2.X - pt1.X,
deltay = pt2.Y - pt1.Y;
#if NET8_0_OR_GREATER
return Math.Sqrt(deltax * deltax + deltay * deltay);
//return double.Hypot(deltax, deltay); // this will give some improvement
#else
return Math.Sqrt(deltax * deltax + deltay * deltay);
#endif
}
You can use double.Hypot(deltax, deltay) in .net 9.0 and .net 10.0, and you will find that hypot is actually faster than sqrt in the same .net version ( though of course still slower than sqrt in .net 8.0 version), which really shouldn't be the case, as hypot is more robust and contains more checking.
And additional source of confirmation comes from using VS 2026 performance profiler to profile the code, and for the above method, I can confirm that in .net 10.0, the dotnet runtimes spends far more time ( roughly 10x) in Math.Sqrt than in the above two deltax and deltay statements, but in .net 8.0, they spend about equal time in three statements each.
Computer Information
Processor 12th Gen Intel(R) Core(TM) i7-12650H (2.70 GHz)
NumberOfCores 10
NumberOfLogicalProcessors 16
Installed RAM 32.0 GB (31.7 GB usable)
System type 64-bit operating system, x64-based processor
Pen and touch No pen or touch input is available for this display
Edition Windows 11 Home Single Language
Version 25H2
Installed on 1/24/2026
OS build 26200.7623
Experience Windows Feature Experience Pack 1000.26100.275.0
Urgency
This is critical as we are developing computationally heavy software, and upgrading to .net 10 seems like a must, and it is causing deep performance regression on our software. Our internal unit tests show that in general the overall application performance can degrade by as much as 30%.
Workaround
NONE discovered so far. Trying to trick the compiler into doing proper optimization by using Math.Pow(x,2) and so on, failed. So any workaround will be welcomed.
Conclusion and Question
Why DEBUG mode is so much slower in .Net 10?