.NET8极致性能优化-Random

点击上方蓝字 江湖评谈设为关注

https://img-blog.csdnimg.cn/img_convert/74c06d99e669c3a03bdebba70d6a4697.png

前言

.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://img-blog.csdnimg.cn/img_convert/6c99c430b2eb8a3821d0f4ea694028b6.jpeg

文章来源: https://blog.csdn.net/sD7O95O/article/details/135709339
版权声明: 本文为博主原创文章,遵循CC 4.0 BY-SA 知识共享协议,转载请附上原文出处链接和本声明。