首页 > 其他分享 >【译】使用 Visual Studio Profiler 进行基准测试

【译】使用 Visual Studio Profiler 进行基准测试

时间:2025-01-17 10:02:43浏览次数:1  
标签:分配 基准 Profiler Visual Studio 内存 测试 new 我们

  在 Visual Studio 17.13 预览版中,我们发布了更新的 BenchmarkDotNet 诊断器,允许您使用性能分析器中的更多工具来分析基准测试。有了这个变化,可以非常快速地挖掘 CPU 使用情况和内存分配,从而使测量/修改/测量周期快速而高效。

对实际项目进行基准测试

  因此,为了展示我们如何使用这些工具使事情变得更好,让我们来测试一个真实的项目。在撰写本文时,CsvHelper 是 Nuget.org 上排名67的最受欢迎的包,当前版本的下载量超过900万次。如果我们可以对其进行基准测试并使其变得更好,我们就可以帮助许多用户。

  您可以在 https://github.com/karpinsn/CsvHelper 上拉取我的分支。值得注意的变化是,我添加了一个新的控制台项目(CsvHelper.Benchmarks),我们可以使用它来存储基准测试,添加了 BenchmarkDotNet 包来执行实际的基准测试运行,以及一个简单的 EnumerateRecords 基准测试,它将 CSV 流解析为记录,如下所示。

public class BenchmarkEnumerateRecords
{
    private const int entryCount = 10000;
    private readonly MemoryStream stream = new();
    public class Simple
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
    
    [GlobalSetup]
    public void GlobalSetup()
    {
        using var streamWriter = new StreamWriter(this.stream, null, -1, true);
        using var writer = new CsvWriter(streamWriter, CultureInfo.InvariantCulture, true);
        var random = new Random(42); // Pick a known seed to keep things consistent
        var chars = new char[10];
        string getRandomString()
        {
            for (int i = 0; i < 10; ++i)
                chars[i] = (char)random.Next('a', 'z' + 1);
            return new string(chars);
        }
        writer.WriteHeader(typeof(Simple));
        writer.NextRecord();
        for (int i = 0; i < BenchmarkEnumerateRecords.entryCount; ++i)
        {
            writer.WriteRecord(new Simple()
            {
                Id = random.Next(),
                Name = getRandomString()
            });
            writer.NextRecord();
        }
    }
    
    [GlobalCleanup]
    public void GlobalCleanup()
    {
        this.stream.Dispose();
    }
    
    [Benchmark]
    public void EnumerateRecords()
    {
        this.stream.Position = 0;
        using var streamReader = new StreamReader(this.stream, null, true, -1, true);
        using var csv = new CsvReader(streamReader, CultureInfo.InvariantCulture, true);
        foreach (var record in csv.GetRecords<Simple>())
        {
            _ = record;
        }
    }
}

  这里有几点需要注意。我们有一个全局设置函数,它创建一个简单的 CSV 流,并将其保存在内存流中。我们在基准测试运行的[GlobalSetup]中这样做,这样它就不会影响基准测试的结果,我们只想对 CSV 文件的实际解析进行基准测试,而不是创建测试数据。

  接下来,我们有一个全局清理函数,它可以正确地释放我们的内存流,在添加更多基准测试的情况下,这是一个很好的实践,这样我们就不会持续泄漏内存。

  最后,我们的基准测试只是从流中创建一个 CsvReader,然后从中读取每条记录。这将演习 CsvHelper 的解析功能,而这正是我们将要尝试和优化的。

深入了解基准

  从这里,您可以向基准类添加一个 BenchmarkDotNet(BDN)Diagnoser,以便在运行时捕获有关基准的信息。BenchmarkDotNet 附带了一个[MemoryDiagnosers],它可以捕获内存分配和总体内存使用信息。如果我们添加这个特性并运行基准测试,您应该得到类似的结果:

  从这里可以看到 BDN 提供的正常平均值、误差和标准偏差,以及我们的诊断程序的输出,其中显示我们在基准测试期间分配了1.69 MB 内存,并将其分解为不同的 GC 堆。如果我们想进一步挖掘,然后我们可以包括来自 Nuget.org 的  Microsoft.VisualStudio.DiagnosticsHub.BenchmarkDotNetDiagnosers 包,它将 BenchmarkDotNet 挂到 VisualStudio Profiler 中,这样我们就可以看到在运行期间发生了什么。在包含这个包并将[DotNetAllocDiagnoser]和[DotNetObjectAllocJobConfiguration]添加到基准测试并重新运行后,我们得到:

  最值得注意的是底部的一行,它显示了收集到的诊断文件的路径,这是 Visual Studio Profiler 文件格式的。随着新的更新,它会自动在 VS 中打开,现在我们有了所有需要挖掘的东西,可能会减少一些内存分配。

狩猎内存分配

  现在我们有了一个详细描述运行中所有内存分配的诊断,让我们看看是否可以减少我们正在进行的内存分配并减少垃圾收集器的负载。

  由于我们的基准测试被设计为从内存流中反序列化10,000条记录,因此我们希望查找10,000的倍数,因为这表明它正在为每条记录进行分配。我们立即看到 String, Type[],int32 和 Simple。String 和 Int32 是我们的 Simple 记录类型上的属性这是合理的分配,Simple 是我们反序列化的记录类型这也是合理的分配。Type[] 有点可疑,进一步挖掘事情只会看起来更糟:

  在这种情况下,看起来我们正在为我们反序列化的每条记录分配一个空的 Type[],每条记录为24字节,在这个基准测试运行中总共分配了7.6MB。在基准测试中,这些无法保存任何数据的垃圾分陪占总内存分配的14%。这太疯狂了,我们应该能解决好的。双击该类型显示了回溯,这表明它来自某个匿名函数:

  转到源代码(右键单击上下文菜单->Go to Source File),我们看到以下内容:

  对我来说,这个分配的来源并不明显,所以最简单的方法就是在 CreateInstance 调用中添加一个断点,然后在调试器中查看。现在 BDN 在一个单独的进程中运行我们的基准测试,以更好地控制基准测试,所以要调试,我们只需实例化我们的基准测试并自己调用基准测试方法。我们可以这样更新 main:

static async Task Main(string[] args)
{
    //_ = BenchmarkRunner.Run<BenchmarkEnumerateRecords>();

    var benchmarks = new BenchmarkEnumerateRecords();
    benchmarks.GlobalSetup();
    await benchmarks.EnumerateRecords();
    benchmarks.GlobalCleanup();
}

  在调试器中运行,我们到达分配发生的地方:

  再一次,它不是特别明显的显示分配发生在哪里,所以让我们进入调用,以防这是来自内联框架:

  该方法有一个 Type 参数,但没有 Type[]。它是一个相对较短的函数,也许它和它的调用者是内联的,所以让我们再次进入。

  不幸的是,还是没有 Type[],但是这个方法被标记为 AggressiveInlining,这解释了为什么我们没有在分配堆栈中看到它。最后一步,我们得到了 Type[] 分配!

  这就是“啊哈时刻”!我们调用 GetArgTypes,它根据传入的 object[] 返回 Type[]。我们首先分配一个与 object[] 大小相同的数组,但如果 object[] 的长度为0,那么我们分配一个新的长度为0的数组。在这种情况下,我们可以很容易地通过检查参数大小和在没有参数可以从中获取类型的情况下尽早返回来解决这个问题。

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Type[] GetArgTypes(object?[] args)
{
    if (args.Length == 0)
    {
        return Array.Empty<Type>();
    }
    var argTypes = new Type[args.Length];
    for (var i = 0; i < args.Length; i++)
    {
        argTypes[i] = args[i]?.GetType() ?? typeof(object);
    }
    return argTypes;
}

  在做了这个更改之后,我们可以重新运行我们的基准测试,这是一个测量/修改/测量的过程,我们得到以下结果:

  我们有效地减少了约14%的分配内存!虽然这看起来不是一个巨大的胜利,但这与记录的数量有关。对于具有大量记录的 CSV 文件来说,这是一个巨大的胜利,特别是在一个已经非常快速和大量优化的库中。

让我们知道您的想法

  总而言之,我们能够在一篇博客文章中对一个真实的项目,添加一个基准测试,使用 Visual Studio 分析器,并做出有意义的贡献。通过创建基准测试套件,可以很容易地隔离您希望通过测量/修改/测量来改进的特定代码,并查看性能优化的影响。我们很想听听您的想法!

 

原文连接:https://devblogs.microsoft.com/visualstudio/benchmarking-with-visual-studio-profiler/#benchmarking-a-real-project

 

标签:分配,基准,Profiler,Visual,Studio,内存,测试,new,我们
From: https://www.cnblogs.com/MeteorSeed/p/18662695

相关文章

  • e2studio开发RA0E1(17)----ADC单通道采集电压
    e2studio开发RA0E1.17--ADC单通道采集电压概述视频教学样品申请完整代码下载硬件准备参考程序新建工程工程模板保存工程路径芯片配置工程模板选择时钟设置UART配置UART属性配置设置e2studio堆栈e2studio的重定向printf设置R_UARTA_Open()函数原型回调函数user_uart_callb......
  • Opencv 的下载安装和VisualStudio配置
    Opencv的下载安装和VisualStudio配置1opencv-windows的下载1.1github直接下载链接(需要外网链接)最新4.10.0版本的下载链接为:https://github.com/opencv/opencv/releases/download/4.10.0/opencv-4.10.0-windows.exe1.2官网下载其他版本官网链接:https://opencv.org/rele......
  • Visual NAND Reconstructor 9.0, 新增 pSLC 闪存块数据恢复方案
    一、什么是pSLCpSLC(Pseudo-SingleLevelCell)即伪SLC,是将多层单元(MLC)或三层单元(TLC)闪存的一部分,通过固件管理,模拟成单层单元(SLC)闪存来使用的区域。它并非闪存芯片本身的物理差异,而是通过软件控制实现的。简单来说,就是把一部分MLC或TLC闪存暂时当作SLC闪存来用,从而提......
  • 用DevEco Studio性能分析工具 高效解决鸿蒙原生应用内存问题
    在鸿蒙原生应用开发过程中,可能由于种种原因导致应用内存未被正常地使用或者归还至操作系统,从而引发内存异常占用、内存泄漏等问题,最终导致应用卡顿甚至崩溃,严重影响用户体验。为了帮助鸿蒙应用开发者高效定位并解决内存问题、提升应用稳定性与体验,华为在DevEcoStudio上提供了专属......
  • Mounriver Studio编译器在当前工程中添加文件夹后编译报错问题的解决方法
    在开发一些例程时,往往需要将自己现有的封装好的函数接口文件夹移植进来,但工程编译后往往会出现报未包含的错误,可按以下步骤处理解决:一、这边做示例,随便打开一个工程,假设在该工程目录下添加了一个MOUSE文件夹, 此时这个MOUSE文件夹并没有包含在这个工程的编译路径中,如果在mai......
  • Syncfusion Essential Studio Flutter 2024 Crack
    SyncfusionEssentialStudioFlutter2024CrackSyncfusionEssentialStudioFlutter2024Volume4addstrackballforindividualseries,enablingprecisedatatrackingandchartinteractions.SyncfusionEssentialStudioFlutter(availableaspart......
  • Label Studio:基于CS架构的一站式多格式数据标注平台,解锁AI训练数据新体验
    LabelStudio是一款强大的开源数据标注工具,支持文本、图像、音频、视频、时间序列等多种格式的标注。它非常适合用来为机器学习模型准备高质量的训练数据,尤其是NLP、计算机视觉和语音任务等领域。LabelStudio的主要功能:多格式支持:文本分类、命名实体识别(NER)图像分......
  • SQL Server性能优化(3)使用SQL Server Profiler查询性能瓶颈
    关于SQLServerProfiler的使用,网上已经有很多教程,比如这一篇文章:SQLServerProfiler:使用方法和指标说明。微软官方文档:https://msdn.microsoft.com/zh-cn/library/ms179428(v=sql.105).aspx有更详细的介绍。经过使用Profiler进行监视,得到监视结果。=========================......
  • 手把手教你Nucleistudio+Vivado协同仿真教程
    创建Vivado工程1.创建工程:在Vivado中创建工程,命名随意,路径随意;2.配置工程:这里可以选择是否添加源文件等,我们先不添加;3.选择FPGA核心:选择MCU200T对应的FPGA核心xc7a200tfbg484-2  等待创建中~4.添加源文件:创建工程完成后,单击"+",添加从https://github.com......
  • 自动化测试工具Ranorex Studio(八十二)-WEB测试
    测试移动网站如果你想要在你的iOS设备或者simulator上自动化web测试,你可以使用已经调制过的RXBrowserapp。按照下面描述的步骤即可:下载并且解压RXBrowserXCode项目(RXBRowser_401.zip)到你的Mac使用XCode打开该项目clean然后build项目,并且将其部署到你的iOS设备或者simul......