Open stephentoub opened 1 month ago
What OS is this on?
Windows 11
Tagging subscribers to this area: @dotnet/area-system-numerics See info in area-owners.md if you want to be subscribed.
This is likely caused by using SinCos
, will take a look.
My guess is this is faster on Linux and that Windows is hitting one of the open issues like https://github.com/dotnet/runtime/issues/48776 or https://developercommunity.visualstudio.com/t/MSVCs-sincos-implementation-is-incorrec/10582378#T-ND10697989
Will confirm and likely change the SinCos
impl for Windows to just call Sin and Cos independently for now and track getting it back after the above are resolved
Note also that in some cases, .NET 8.0 Math.SinCos
produces slightly different (less accurate) results than .NET 9.0.
For example, for x = 1.57 (different from pi/2 by about 0.0007), the results are:
.NET 8.0:
sin x = 0.9999996829318346
cos x = 0.0007963267107331026
.NET 9.0 (preview 6):
sin x = 0.9999996829318346
cos x = 0.0007963267107332633
Calling Math.Sin
and Math.Cos
separately gives the same result as .NET 9.0.
Note also that in some cases, .NET 8.0 Math.SinCos produces slightly different (less accurate) results than .NET 9.0.
This is https://developercommunity.visualstudio.com/t/MSVCs-sincos-implementation-is-incorrec/10582378#T-ND10697989, which I linked above.
@stephentoub, I'm not able to reproduce this; I also tested on my Intel 11900 and saw similar results
BenchmarkDotNet v0.13.13-nightly.20240311.145, Windows 11 (10.0.26100.1301) AMD Ryzen 9 7950X, 1 CPU, 32 logical and 16 physical cores .NET SDK 9.0.100-preview.6.24328.19 [Host] : .NET 8.0.5 (8.0.524.21615), X64 RyuJIT AVX2 Job-GCZARY : .NET 8.0.5 (8.0.524.21615), X64 RyuJIT AVX2
PowerPlanMode=00000000-0000-0000-0000-000000000000 IterationTime=250ms MaxIterationCount=20 MinIterationCount=15 WarmupCount=1
Method | value | Mean | Error | StdDev | Median | Min | Max | Allocated |
---|---|---|---|---|---|---|---|---|
SinStephen | ? | 10.073 ns | 0.1431 ns | 0.1339 ns | 10.045 ns | 9.920 ns | 10.282 ns | - |
CosStephen | ? | 9.587 ns | 0.0693 ns | 0.0649 ns | 9.552 ns | 9.522 ns | 9.720 ns | - |
Cos | <0; 1> | 6.704 ns | 0.0288 ns | 0.0240 ns | 6.713 ns | 6.656 ns | 6.725 ns | - |
Sin | <0; 1> | 6.862 ns | 0.0347 ns | 0.0325 ns | 6.842 ns | 6.830 ns | 6.924 ns | - |
Cos | <1.23456789; 1.23456789> | 9.964 ns | 0.0569 ns | 0.0532 ns | 9.975 ns | 9.900 ns | 10.040 ns | - |
Sin | <1.23456789; 1.23456789> | 10.281 ns | 0.0434 ns | 0.0406 ns | 10.278 ns | 10.184 ns | 10.349 ns | - |
Cos | <1; 0> | 9.254 ns | 0.0466 ns | 0.0436 ns | 9.261 ns | 9.193 ns | 9.323 ns | - |
Sin | <1; 0> | 9.256 ns | 0.0521 ns | 0.0462 ns | 9.235 ns | 9.210 ns | 9.364 ns | - |
Cos | <1; 1> | 9.949 ns | 0.0587 ns | 0.0550 ns | 9.926 ns | 9.879 ns | 10.043 ns | - |
Sin | <1; 1> | 9.922 ns | 0.0573 ns | 0.0536 ns | 9.893 ns | 9.869 ns | 10.040 ns | - |
Cos | <1; 2> | 10.342 ns | 0.0506 ns | 0.0474 ns | 10.320 ns | 10.284 ns | 10.441 ns | - |
Sin | <1; 2> | 9.872 ns | 0.0485 ns | 0.0453 ns | 9.871 ns | 9.804 ns | 9.947 ns | - |
BenchmarkDotNet v0.13.13-nightly.20240311.145, Windows 11 (10.0.26100.1301) AMD Ryzen 9 7950X, 1 CPU, 32 logical and 16 physical cores .NET SDK 9.0.100-preview.6.24328.19 [Host] : .NET 8.0.5 (8.0.524.21615), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI Job-JGPDSN : .NET 8.0.5 (8.0.524.21615), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
PowerPlanMode=00000000-0000-0000-0000-000000000000 IterationTime=250ms MaxIterationCount=20 MinIterationCount=15 WarmupCount=1
Method | value | Mean | Error | StdDev | Median | Min | Max | Allocated |
---|---|---|---|---|---|---|---|---|
SinStephen | ? | 9.980 ns | 0.1542 ns | 0.1443 ns | 9.957 ns | 9.810 ns | 10.177 ns | - |
CosStephen | ? | 9.777 ns | 0.0737 ns | 0.0689 ns | 9.758 ns | 9.684 ns | 9.909 ns | - |
Cos | <0; 1> | 6.687 ns | 0.0433 ns | 0.0405 ns | 6.691 ns | 6.632 ns | 6.743 ns | - |
Sin | <0; 1> | 6.627 ns | 0.0450 ns | 0.0421 ns | 6.600 ns | 6.580 ns | 6.695 ns | - |
Cos | <1.23456789; 1.23456789> | 10.395 ns | 0.0501 ns | 0.0469 ns | 10.414 ns | 10.310 ns | 10.436 ns | - |
Sin | <1.23456789; 1.23456789> | 9.851 ns | 0.0628 ns | 0.0587 ns | 9.856 ns | 9.767 ns | 9.951 ns | - |
Cos | <1; 0> | 9.139 ns | 0.0486 ns | 0.0455 ns | 9.143 ns | 9.074 ns | 9.203 ns | - |
Sin | <1; 0> | 9.650 ns | 0.0544 ns | 0.0509 ns | 9.648 ns | 9.580 ns | 9.743 ns | - |
Cos | <1; 1> | 10.348 ns | 0.0433 ns | 0.0405 ns | 10.352 ns | 10.247 ns | 10.405 ns | - |
Sin | <1; 1> | 10.031 ns | 0.0619 ns | 0.0579 ns | 10.036 ns | 9.964 ns | 10.128 ns | - |
Cos | <1; 2> | 10.340 ns | 0.0674 ns | 0.0630 ns | 10.359 ns | 10.250 ns | 10.415 ns | - |
Sin | <1; 2> | 10.152 ns | 0.0239 ns | 0.0187 ns | 10.143 ns | 10.133 ns | 10.186 ns | - |
BenchmarkDotNet v0.13.13-nightly.20240311.145, Windows 11 (10.0.26100.1301) AMD Ryzen 9 7950X, 1 CPU, 32 logical and 16 physical cores .NET SDK 9.0.100-preview.6.24328.19 [Host] : .NET 9.0.0 (9.0.24.32707), X64 RyuJIT AVX2 Job-IXTZQT : .NET 9.0.0 (9.0.24.32707), X64 RyuJIT AVX2
PowerPlanMode=00000000-0000-0000-0000-000000000000 IterationTime=250ms MaxIterationCount=20 MinIterationCount=15 WarmupCount=1
Method | value | Mean | Error | StdDev | Median | Min | Max | Allocated |
---|---|---|---|---|---|---|---|---|
SinStephen | ? | 9.911 ns | 0.1471 ns | 0.1304 ns | 9.866 ns | 9.796 ns | 10.186 ns | - |
CosStephen | ? | 10.133 ns | 0.0711 ns | 0.0665 ns | 10.125 ns | 10.023 ns | 10.242 ns | - |
Cos | <0; 1> | 6.869 ns | 0.0546 ns | 0.0511 ns | 6.868 ns | 6.786 ns | 6.938 ns | - |
Sin | <0; 1> | 6.868 ns | 0.0501 ns | 0.0444 ns | 6.870 ns | 6.811 ns | 6.937 ns | - |
Cos | <1.23456789; 1.23456789> | 10.323 ns | 0.0657 ns | 0.0615 ns | 10.326 ns | 10.227 ns | 10.414 ns | - |
Sin | <1.23456789; 1.23456789> | 10.146 ns | 0.0598 ns | 0.0559 ns | 10.149 ns | 10.063 ns | 10.240 ns | - |
Cos | <1; 0> | 9.293 ns | 0.1028 ns | 0.0911 ns | 9.277 ns | 9.180 ns | 9.465 ns | - |
Sin | <1; 0> | 9.634 ns | 0.1120 ns | 0.1048 ns | 9.623 ns | 9.525 ns | 9.854 ns | - |
Cos | <1; 1> | 10.285 ns | 0.0676 ns | 0.0632 ns | 10.248 ns | 10.216 ns | 10.416 ns | - |
Sin | <1; 1> | 9.844 ns | 0.0584 ns | 0.0546 ns | 9.860 ns | 9.767 ns | 9.933 ns | - |
Cos | <1; 2> | 10.103 ns | 0.0757 ns | 0.0708 ns | 10.086 ns | 10.023 ns | 10.229 ns | - |
Sin | <1; 2> | 10.200 ns | 0.0905 ns | 0.0847 ns | 10.201 ns | 10.089 ns | 10.306 ns | - |
BenchmarkDotNet v0.13.13-nightly.20240311.145, Windows 11 (10.0.26100.1301) AMD Ryzen 9 7950X, 1 CPU, 32 logical and 16 physical cores .NET SDK 9.0.100-preview.6.24328.19 [Host] : .NET 9.0.0 (9.0.24.32707), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI Job-EAJRXI : .NET 9.0.0 (9.0.24.32707), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
PowerPlanMode=00000000-0000-0000-0000-000000000000 IterationTime=250ms MaxIterationCount=20 MinIterationCount=15 WarmupCount=1
Method | value | Mean | Error | StdDev | Median | Min | Max | Allocated |
---|---|---|---|---|---|---|---|---|
SinStephen | ? | 10.247 ns | 0.0718 ns | 0.0561 ns | 10.248 ns | 10.171 ns | 10.333 ns | - |
CosStephen | ? | 10.254 ns | 0.0503 ns | 0.0471 ns | 10.248 ns | 10.172 ns | 10.335 ns | - |
Cos | <0; 1> | 6.876 ns | 0.0306 ns | 0.0287 ns | 6.870 ns | 6.823 ns | 6.924 ns | - |
Sin | <0; 1> | 6.701 ns | 0.0380 ns | 0.0356 ns | 6.704 ns | 6.634 ns | 6.747 ns | - |
Cos | <1.23456789; 1.23456789> | 10.075 ns | 0.0737 ns | 0.0689 ns | 10.088 ns | 9.972 ns | 10.158 ns | - |
Sin | <1.23456789; 1.23456789> | 10.189 ns | 0.0825 ns | 0.0772 ns | 10.186 ns | 10.099 ns | 10.376 ns | - |
Cos | <1; 0> | 9.596 ns | 0.0541 ns | 0.0452 ns | 9.596 ns | 9.521 ns | 9.677 ns | - |
Sin | <1; 0> | 9.475 ns | 0.0490 ns | 0.0459 ns | 9.484 ns | 9.403 ns | 9.541 ns | - |
Cos | <1; 1> | 10.464 ns | 0.2140 ns | 0.2002 ns | 10.523 ns | 10.065 ns | 10.776 ns | - |
Sin | <1; 1> | 10.195 ns | 0.0693 ns | 0.0649 ns | 10.204 ns | 10.079 ns | 10.286 ns | - |
Cos | <1; 2> | 10.296 ns | 0.0640 ns | 0.0568 ns | 10.281 ns | 10.238 ns | 10.406 ns | - |
Sin | <1; 2> | 10.161 ns | 0.0638 ns | 0.0597 ns | 10.158 ns | 10.083 ns | 10.249 ns | - |
This was using:
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Attributes;
using MicroBenchmarks;
namespace System.Numerics.Tests
{
[BenchmarkCategory(Categories.Libraries)]
public class Perf_Complex
{
public IEnumerable<object> Values()
{
yield return new Complex(1.0, 0.0);
yield return new Complex(0.0, 1.0);
yield return new Complex(1.0, 1.0);
yield return new Complex(1.23456789, 1.23456789);
yield return new Complex(1, 2);
}
[Benchmark]
[ArgumentsSource(nameof(Values))]
public Complex Cos(Complex value) => Complex.Cos(value);
[Benchmark]
[ArgumentsSource(nameof(Values))]
public Complex Sin(Complex value) => Complex.Sin(value);
private Complex _value = new Complex(1, 2);
[Benchmark]
public Complex SinStephen() => Complex.Sin(_value);
[Benchmark]
public Complex CosStephen() => Complex.Cos(_value);
}
}
which I was working on adding to the dotnet/performance repo (wasn't planning on adding SinStephen/CosStephen, just added them locally when I couldn't repro initially)
This was using:
I get this:
Method | Runtime | value | Mean | Ratio |
---|---|---|---|---|
SinStephen | .NET 8.0 | ? | 22.17 ns | 1.00 |
SinStephen | .NET 9.0 | ? | 38.29 ns | 1.75 |
CosStephen | .NET 8.0 | ? | 22.21 ns | 1.00 |
CosStephen | .NET 9.0 | ? | 38.56 ns | 1.74 |
Cos | .NET 8.0 | <0; 1> | 14.54 ns | 1.00 |
Cos | .NET 9.0 | <0; 1> | 30.64 ns | 2.11 |
Sin | .NET 8.0 | <0; 1> | 15.04 ns | 1.00 |
Sin | .NET 9.0 | <0; 1> | 29.67 ns | 1.97 |
Cos | .NET 8.0 | <1.23(...)6789> [24] | 22.05 ns | 1.00 |
Cos | .NET 9.0 | <1.23(...)6789> [24] | 38.88 ns | 1.76 |
Sin | .NET 8.0 | <1.23(...)6789> [24] | 21.94 ns | 1.00 |
Sin | .NET 9.0 | <1.23(...)6789> [24] | 38.44 ns | 1.75 |
Cos | .NET 8.0 | <1; 0> | 17.79 ns | 1.00 |
Cos | .NET 9.0 | <1; 0> | 20.97 ns | 1.18 |
Sin | .NET 8.0 | <1; 0> | 17.61 ns | 1.00 |
Sin | .NET 9.0 | <1; 0> | 20.71 ns | 1.18 |
Cos | .NET 8.0 | <1; 1> | 22.11 ns | 1.00 |
Cos | .NET 9.0 | <1; 1> | 38.64 ns | 1.75 |
Sin | .NET 8.0 | <1; 1> | 22.11 ns | 1.00 |
Sin | .NET 9.0 | <1; 1> | 37.71 ns | 1.71 |
Cos | .NET 8.0 | <1; 2> | 22.15 ns | 1.00 |
Cos | .NET 9.0 | <1; 2> | 38.40 ns | 1.73 |
Sin | .NET 8.0 | <1; 2> | 22.12 ns | 1.00 |
Sin | .NET 9.0 | <1; 2> | 38.09 ns | 1.72 |
on this configuration:
BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.3958/23H2/2023Update/SunValley3)
Intel Core i7-8700 CPU 3.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET SDK 9.0.100-preview.7.24373.4
[Host] : .NET 8.0.7 (8.0.724.31311), X64 RyuJIT AVX2
Job-WRZLJY : .NET 8.0.7 (8.0.724.31311), X64 RyuJIT AVX2
Job-GMITOL : .NET 9.0.0 (9.0.24.36618), X64 RyuJIT AVX2
I tried again with a runtime built locally from latest in main, and I get basically the same thing:
Method | Toolchain | value | Mean | Ratio |
---|---|---|---|---|
SinStephen | net8.0 | ? | 21.62 ns | 1.00 |
SinStephen | CoreRun | ? | 37.38 ns | 1.73 |
CosStephen | net8.0 | ? | 21.90 ns | 1.00 |
CosStephen | CoreRun | ? | 37.69 ns | 1.72 |
Cos | net8.0 | <0; 1> | 14.64 ns | 1.00 |
Cos | CoreRun | <0; 1> | 30.49 ns | 2.08 |
Sin | net8.0 | <0; 1> | 15.44 ns | 1.00 |
Sin | CoreRun | <0; 1> | 29.71 ns | 1.92 |
Cos | net8.0 | <1.23(...)6789> [24] | 21.77 ns | 1.00 |
Cos | CoreRun | <1.23(...)6789> [24] | 37.52 ns | 1.72 |
Sin | net8.0 | <1.23(...)6789> [24] | 21.78 ns | 1.00 |
Sin | CoreRun | <1.23(...)6789> [24] | 37.36 ns | 1.71 |
Cos | net8.0 | <1; 0> | 17.74 ns | 1.00 |
Cos | CoreRun | <1; 0> | 19.97 ns | 1.13 |
Sin | net8.0 | <1; 0> | 17.73 ns | 1.00 |
Sin | CoreRun | <1; 0> | 20.33 ns | 1.15 |
Cos | net8.0 | <1; 1> | 22.13 ns | 1.00 |
Cos | CoreRun | <1; 1> | 38.48 ns | 1.75 |
Sin | net8.0 | <1; 1> | 21.97 ns | 1.00 |
Sin | CoreRun | <1; 1> | 37.89 ns | 1.72 |
Cos | net8.0 | <1; 2> | 21.94 ns | 1.00 |
Cos | CoreRun | <1; 2> | 38.09 ns | 1.73 |
Sin | net8.0 | <1; 2> | 22.29 ns | 1.00 |
Sin | CoreRun | <1; 2> | 38.17 ns | 1.71 |
Could you get the disassembly for the runs here? On my box, the codegen is nearly identical, the only difference is that .NET 8 has a vzeroupper
emitted.
So, either you're getting pessimized due to lack of vzeroupper
or you're likely being pessimized by the JCC erratum: https://www.intel.com/content/www/us/en/developer/articles/technical/software-security-guidance/best-practices/mitigation-strategies-jcc-microcode.html
My guess is you're being impacted by the latter given that you're on Coffee Lake
and vzeroupper
has been highly optimized since Skylake
(plus we're already zeroing the upper bits of all the used registers and Cos+Sin
should be executing the internal AVX aware path themselves, so vzeroupper "should" be unnecessary).
Via [DisassemblyDiagnoser]
; Tests.Cos(System.Numerics.Complex)
push rbx
sub rsp,70
vzeroupper
vmovaps [rsp+60],xmm6
vmovaps [rsp+50],xmm7
vmovaps [rsp+40],xmm8
vmovaps [rsp+30],xmm9
mov rbx,rdx
vmovsd xmm0,qword ptr [r8]
vmovsd qword ptr [rsp+28],xmm0
vmovsd xmm1,qword ptr [r8+8]
vmovaps xmm0,xmm1
call System.Math.Exp(Double)
vmovaps xmm6,xmm0
vmovsd xmm0,qword ptr [7FFF954A86A0]
vdivsd xmm7,xmm0,xmm6
vsubsd xmm0,xmm6,xmm7
vmovsd xmm8,qword ptr [7FFF954A86A8]
vmulsd xmm9,xmm0,xmm8
vmovsd xmm0,qword ptr [rsp+28]
call System.Math.Cos(Double)
vaddsd xmm1,xmm6,xmm7
vmulsd xmm1,xmm1,xmm8
vmulsd xmm6,xmm0,xmm1
vmovsd xmm0,qword ptr [rsp+28]
call System.Math.Sin(Double)
vxorps xmm0,xmm0,[7FFF954A86B0]
vmulsd xmm0,xmm0,xmm9
vmovsd qword ptr [rbx],xmm6
vmovsd qword ptr [rbx+8],xmm0
mov rax,rbx
vmovaps xmm6,[rsp+60]
vmovaps xmm7,[rsp+50]
vmovaps xmm8,[rsp+40]
vmovaps xmm9,[rsp+30]
add rsp,70
pop rbx
ret
; Total bytes of code 184
Extern method System.Math.Exp(Double) System.Math.Cos(Double) System.Math.Sin(Double)
; Tests.Cos(System.Numerics.Complex)
push rbx
sub rsp,60
vmovaps [rsp+50],xmm6
mov rbx,rdx
vmovsd xmm0,qword ptr [r8]
vmovsd xmm1,qword ptr [r8+8]
vmovsd qword ptr [rsp+30],xmm1
lea r8,[rsp+38]
lea rdx,[rsp+40]
call System.Math.SinCos(Double, Double*, Double*)
vmovsd xmm0,qword ptr [rsp+40]
vmovsd xmm1,qword ptr [rsp+38]
vmovsd qword ptr [rsp+28],xmm0
vmovsd qword ptr [rsp+20],xmm1
vmovsd xmm0,qword ptr [rsp+30]
call System.Math.Cosh(Double)
vmulsd xmm6,xmm0,qword ptr [rsp+20]
vmovsd xmm1,qword ptr [rsp+28]
vxorps xmm1,xmm1,[7FFF8ACA9540]
vmovsd qword ptr [rsp+48],xmm1
vmovsd xmm0,qword ptr [rsp+30]
call System.Math.Sinh(Double)
vmulsd xmm0,xmm0,qword ptr [rsp+48]
vmovsd qword ptr [rbx],xmm6
vmovsd qword ptr [rbx+8],xmm0
mov rax,rbx
vmovaps xmm6,[rsp+50]
add rsp,60
pop rbx
ret
; Total bytes of code 148
Extern method System.Math.SinCos(Double, Double, Double) System.Math.Cosh(Double) System.Math.Sinh(Double)
Ah, I see. I was running the benchmark with the wrong 9.0 path and it was picking up an outdated library version. Doing a clean build fixed that and I get:
Method | value | Mean | Error | StdDev | Median | Min | Max | Code Size | Allocated |
---|---|---|---|---|---|---|---|---|---|
SinStephen | ? | 20.26 ns | 0.269 ns | 0.252 ns | 20.21 ns | 19.74 ns | 20.65 ns | 127 B | - |
CosStephen | ? | 20.29 ns | 0.256 ns | 0.239 ns | 20.30 ns | 19.94 ns | 20.59 ns | 147 B | - |
Cos | <0; 1> | 14.83 ns | 0.103 ns | 0.091 ns | 14.84 ns | 14.63 ns | 15.01 ns | 148 B | - |
Sin | <0; 1> | 14.72 ns | 0.130 ns | 0.121 ns | 14.72 ns | 14.56 ns | 14.93 ns | 128 B | - |
Cos | <1.23456789; 1.23456789> | 20.13 ns | 0.169 ns | 0.159 ns | 20.13 ns | 19.86 ns | 20.37 ns | 148 B | - |
Sin | <1.23456789; 1.23456789> | 19.64 ns | 0.041 ns | 0.034 ns | 19.63 ns | 19.60 ns | 19.71 ns | 128 B | - |
Cos | <1; 0> | 15.14 ns | 0.149 ns | 0.132 ns | 15.09 ns | 14.99 ns | 15.46 ns | 148 B | - |
Sin | <1; 0> | 29.21 ns | 0.678 ns | 0.781 ns | 29.37 ns | 27.11 ns | 30.27 ns | 128 B | - |
Cos | <1; 1> | 19.99 ns | 0.161 ns | 0.150 ns | 20.02 ns | 19.79 ns | 20.25 ns | 148 B | - |
Sin | <1; 1> | 20.03 ns | 0.130 ns | 0.115 ns | 20.01 ns | 19.83 ns | 20.26 ns | 128 B | - |
Cos | <1; 2> | 19.86 ns | 0.049 ns | 0.041 ns | 19.85 ns | 19.81 ns | 19.95 ns | 148 B | - |
Sin | <1; 2> | 19.81 ns | 0.148 ns | 0.138 ns | 19.86 ns | 19.62 ns | 19.98 ns | 128 B | - |
The reason for the regresion here is that .NET 9 changed from calling:
double p = Math.Exp(value.m_imaginary);
double q = 1.0 / p;
double sinh = (p - q) * 0.5;
double cosh = (p + q) * 0.5;
To instead doing (effectively):
double sinh = Math.Sinh(value.m_imaginary);
double cosh = Math.Cosh(value.m_imaginary);
This can be reproduced in isolation via:
public IEnumerable<object> Values()
{
yield return new Complex(1.0, 0.0);
yield return new Complex(0.0, 1.0);
yield return new Complex(1.0, 1.0);
yield return new Complex(1.23456789, 1.23456789);
yield return new Complex(1, 2);
}
[Benchmark]
[ArgumentsSource(nameof(Values))]
public Complex SinNet8(Complex value)
{
double p = Math.Exp(value.Imaginary);
double q = 1.0 / p;
double sinh = (p - q) * 0.5;
double cosh = (p + q) * 0.5;
return new Complex(Math.Sin(value.Real) * cosh, Math.Cos(value.Real) * sinh);
}
[Benchmark]
[ArgumentsSource(nameof(Values))]
public Complex SinNet9(Complex value)
{
(var sin, var cos) = Math.SinCos(value.Real);
return new Complex(sin * Math.Cosh(value.Imaginary), cos * Math.Sinh(value.Imaginary));
}
[Benchmark]
[ArgumentsSource(nameof(Values))]
public Complex SinNet9Individual(Complex value)
{
return new Complex(Math.Sin(value.Real) * Math.Cosh(value.Imaginary), Math.Cos(Value.Real) * Math.Sinh(value.Imaginary));
}
The change was made to ensure more correct results, but is more expensive since we're computing the proper sinh
and cosh
independently. We could introduce some SinhCosh
pair to reduce this cost and win that back but that's likely out of scope for .NET 9. I don't think we want to back out the change since its explicitly there to ensure we get more accurate results and correct edge case handling.
This is what I currently see:
cc: @tannergooding