-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Performance regression on .NET 9, compared to .NET 8 #108096
Comments
I can repro this ... BenchmarkDotNet v0.14.0, Windows 11 (10.0.22631.4169/23H2/2023Update/SunValley3) EvaluateOverhead=False OutlierMode=DontRemove InvocationCount=16777216
Looks like the issue is in the benchmark method.
|
I cannot resist being off-topic... you can always remove the idx1 = Random.Next(chromosomeLength);
idx2 = (idx1 + Random.Next(chromosomeLength - 1) + 1) % chromosomeLength; |
Thanks @filipnavara! I will definitely use that in my code. It seems like the problem is connected with seeded random. Using Random.Shared shows no regression. Benchmark codeusing BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
BenchmarkRunner.Run<Benchmarks>();
[MemoryDiagnoser, DisassemblyDiagnoser, SimpleJob(RuntimeMoniker.Net80), SimpleJob(RuntimeMoniker.Net90)]
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByJob)]
public class Benchmarks
{
private const int Seed = 100;
private static readonly int[] Chromosome = new int[59];
private static readonly Random Random = new(Seed);
public Benchmarks()
{
var genes = Enumerable.Range(1, 59).ToArray();
Random.Shuffle(genes);
genes.CopyTo(Chromosome.AsSpan());
}
[Benchmark]
public int[] InversionMutationSharedRandom()
{
var chromosomeLength = Chromosome.Length;
var idx1 = Random.Shared.Next(chromosomeLength);
var idx2 = (idx1 + Random.Shared.Next(chromosomeLength - 1) + 1) % chromosomeLength;
if (idx1 > idx2)
{
(idx1, idx2) = (idx2, idx1);
}
Array.Reverse(Chromosome, idx1, idx2 - idx1 + 1);
return Chromosome;
}
[Benchmark]
public int[] InversionMutationSeededRandom()
{
var chromosomeLength = Chromosome.Length;
var idx1 = Random.Next(chromosomeLength);
var idx2 = (idx1 + Random.Next(chromosomeLength - 1) + 1) % chromosomeLength;
if (idx1 > idx2)
{
(idx1, idx2) = (idx2, idx1);
}
Array.Reverse(Chromosome, idx1, idx2 - idx1 + 1);
return Chromosome;
}
} Results:
|
cc @stephentoub I seem to remember that the implementation of seeded random has changed recently |
Not the APIs used here. |
Codegen diff ;; net 8
vxorps xmm0, xmm0, xmm0
vcvtsi2sd xmm0, xmm0, eax
vmulsd xmm0, xmm0, qword ptr [reloc @RWD00]
vmulsd xmm0, xmm0, qword ptr [reloc @RWD08]
vcvttsd2si r14d, xmm0
;; net 9
vxorps xmm0, xmm0, xmm0
vcvtsi2sd xmm0, xmm0, eax
vmulsd xmm0, xmm0, qword ptr [reloc @RWD00]
vmulsd xmm0, xmm0, qword ptr [reloc @RWD08]
vmovddup xmm1, xmm0
vmovddup xmm2, xmm0
vmovddup xmm0, xmm0
vcmppd xmm1, xmm2, xmm1, 0
vandpd xmm0, xmm1, xmm0
vcmppd xmm1, xmm0, xmmword ptr [reloc @RWD16], 13
vcvttsd2si ecx, xmm0
vmovd xmm0, ecx
vpbroadcastd xmm0, xmm0
vpblendvb xmm0, xmm0, xmmword ptr [reloc @RWD32], xmm1
vmovd r14d, xmm0 |
Looks like this is a consequence of #97529, which standardized the behavior of some floating to integer conversions. We expected some regressions in performance. The legacy RNG involves floating to integer conversion (even for runtime/src/libraries/System.Private.CoreLib/src/System/Random.Net5CompatImpl.cs Lines 311 to 313 in a3d1e1c
The We might be able to use the faster "legacy" conversion here since the values from |
FYI @tannergooding |
This would be less affected by while (idx1 == idx2)
{
idx1 = Random.Next(chromosomeLength * chromosomeLength);
idx2 = idx1 % chromosomeLength;
idx1 /= chromosomeLength;
} |
Hi @skyoxZ! While I agree that reducing number of calls to random.Next would reduce the regression (which is obvious), I cannot use that example as it would introduce strong correlation between mutation points (for different random seeds, idx2 will always be exactly related to idx1), which is not acceptable in terms of genetic algorithm. |
|
@skyoxZ as I mentioned above, the example introduces a fixed relationship: Since No matter how many times (or with what random seed) you generate the number 34, |
@KeterSCP please think again. My random number contains two independent digits. |
@skyoxZ While it may seem like the two digits are independent because they come from different parts of the random number, they are not independent in a probabilistic sense: The two digits are derived from a single random number, not two separate random events. Both |
@KeterSCP I believe my algorithm is ok. It's just like: there are 2 dice, one red and one blue. You throw them one by one while I throw them both at the same time. And you may be interested in the source code of Random.Shared.NextBytes. |
@skyoxZ thanks for the willingness to help, but I cannot accept that solution due to the aforementioned correlation between these numbers. Let's not continue the off-top |
The consideration isn't in that sample is I'll get a fix up for .NET 10. However, barring some real world samples showing the impact to a hot path, I don't think it meets the bar for backport. This is because the regression is 4ns and even in extreme cases of generating 250 million random numbers (which would add up to a 1s regression), it is likely going to be lost to the general noise of the rest of the system (the overhead of the |
Description
After migrating a genetic algorithm project to .NET 9, I noticed that there was a performance regression, and I was able to track it down to a single method that performs inverse mutation of the given chromosome (in my case it is just an array of ints). I tried many different seeds, and the result is the same: about 10-15% regression. I also set
<CETCompat>false</CETCompat>
in the .csproj, but it did not help.The problem is that this method is on a hot path, and eventually this adds up to milliseconds of difference between .NET 8 and .NET 9.
Benchmark code:
Regression?
Yes
Data
The text was updated successfully, but these errors were encountered: