点击上方蓝字 江湖评谈设为关注
前言
.NET8的性能优化是方方面面的,随机数值的优化同样需要进步。本篇来看下Random的优化。
详细
在Ramdom的优化里面,issuse:dotnet/runtime#79790提供了一个中性范围函数URF(unbiased range functions)。当调用像Ramdom.Next(int min, int max)函数的时候,需要提供min到max范围内的值。为了提供一个中性的答案,.NET7实现生成一个32位的值。简单点来说,就是取最大值的log2(设最大值为x,取x的2的幂数),并进行移位丢弃。得到的结果检查下,是否小于最大值。如果是,作为返回答案。如果不是,拒绝该值(这个过程称之为[拒绝采样]),并且重复循环这个过程,直到得到结果为止。使用新的方法,它实现了模数(之前使用模数得到随机结果)减少(例如 Next() % max)。除了用更简单的乘法和移位替代了更昂贵的模数,它中间穿插了一个拒绝采样略微下降性能。但它纠正的偏差发生频率更低,因此耗时的可能性发生的频率也更低。这样Random方法在性能和吞吐量上获得了更好的提升。
下面看下测试结果,这里需要注意Random从PGO中可以获得提升,因为Random使用的内部抽象可以被去虚拟化,所以下面展示了启动和未启用PGO的影响。
// dotnet run -c Release -f net7.0 --filter "*" using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Configs; using BenchmarkDotNet.Environments; using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Running; var config = DefaultConfig.Instance .AddJob(Job.Default.WithId(".NET 7").WithRuntime(CoreRuntime.Core70).AsBaseline()) .AddJob(Job.Default.WithId(".NET 8 w/o PGO").WithRuntime(CoreRuntime.Core80).WithEnvironmentVariable("DOTNET_TieredPGO", "0")) .AddJob(Job.Default.WithId(".NET 8").WithRuntime(CoreRuntime.Core80)); BenchmarkSwitcher.FromAssembly(typeof(Tests).Assembly).Run(args, config); [HideColumns("Error", "StdDev", "Median", "RatioSD", "EnvironmentVariables")] public class Tests { private static readonly Random s_rand = new(); [Benchmark] public int NextMax() => s_rand.Next(12345); }
MethodruntimeMeanRatioNextMax.NET 7.05.793 ns1.00NextMax.NET 8.0 w/o PGO1.840 ns0.32NextMax.NET 8.01.598 ns0.28
dotnet/runtime#87219 由 @MichalPetryka 提出,然后进一步对此进行了优化,以适用于长值。算法的核心部分涉及将随机值乘以最大值,然后取乘积的低位部分:
UInt128 randomProduct = (UInt128)maxValue * xoshiro.NextUInt64(); ulong lowPart = (ulong)randomProduct;
这可以通过不使用 UInt128 的乘法实现,而是使用 Math.BigMul 来提高效率
ulong randomProduct = Math.BigMul(maxValue, xoshiro.NextUInt64(), out ulong lowPart);
它是通过使用 Bmi2.X64.MultiplyNoFlags 或 Armbase.Arm64.MultiplyHigh 内部函数来实现的,当其中一个可用时。
// dotnet run -c Release -f net7.0 --filter "*" --runtimes net7.0 net8.0 using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; BenchmarkSwitcher.FromAssembly(typeof(Tests).Assembly).Run(args); [HideColumns("Error", "StdDev", "Median", "RatioSD", "EnvironmentVariables")] public class Tests { private static readonly Random s_rand = new(); [Benchmark] public long NextMinMax() => s_rand.NextInt64(123456789101112, 1314151617181920); }
MethodRuntimeMeanRatioNextMinMax.NET 7.09.839 ns1.00NextMinMax.NET 8.01.927 ns0.20
欢迎加入C#12.NET8技术交流群
往期精彩回顾
.NET8 JIT核心:分层编译的原理
新版.Net性能有没有达到C++90%?
面试官问.Net对象赋值为null,就会被GC回收吗?
文章来源: https://blog.csdn.net/sD7O95O/article/details/135709339
版权声明: 本文为博主原创文章,遵循CC 4.0 BY-SA 知识共享协议,转载请附上原文出处链接和本声明。