Arm64上的.NET性能

目录

环境设置

对.NET应用程序进行基准测试

自定义基准测试

矩阵乘法

字符串操作

把东西放在一起

Arm64上的.NET性能提升

结论


Arm64(通常称为AArch64)提供功耗优化体系结构,该体系结构是许多片上系统(SoC)的基础。SoC集成了CPU、内存、GPUI/O设备,可在各种行业、应用和设备中执行高能效计算操作。由于其便携性和低功耗,Arm64体系结构非常适合移动设备。但是,笔记本电脑和台式机也开始使用Arm64

Microsoft Windows 11通过支持Arm64并提供多项简化应用移植的功能,帮助加快了这种采用速度。具体而言,Windows 11提供Windows on Arm WoA)以通过本机Arm64方法很好地运行 Python应用程序,而Arm64EC(仿真兼容)有助于逐步将x64应用移植到Arm64。此外,包括Qt在内的许多框架现在使用 Windows on ArmWoA)来本机运行基于UI的应用程序。为了帮助开发人员,Microsoft 推出了 Windows Dev Kit 2023,它提供了一个方便的Arm64驱动的设备。

Arm64在使用C++Python时具有原生优势,但它也为其他框架提供了许多优势。例如,.NET是适用于WindowsLinuxMacOS的跨平台开发框架。将框架与Arm64结合使用,可以在多个平台上实现高效的应用。

本文演示如何使用Arm64运行.NET应用程序,从而获得本机体系结构的优势,例如电源效率和速度提升。可以为.NET设置开发环境,并了解通过在Arm64上本机运行代码可以预期的性能提升。下载配套代码以继续操作。

环境设置

若要设置开发环境,需要满足以下条件:

首先,在Arm64设备上为两种体系结构(x64  Arm64)中的每一个安装适用于Windows.NET 8.0 SDK。该SDK目前提供预览版(在此处下载)。要确认安装成功,请打开命令提示符窗口并输入:

dotnet --info

这将产生以下输出。

默认情况下,该dotnet命令在Arm64设备上运行时使用Arm64体系结构。但是,它认识到x64体系结构在找到的其他体系结构列表中也可用。

安装.NET SDK后,将 Visual Studio Code for Windows 安装为IDE。选择Arm64 的用户安装程序,然后启动安装程序。使用默认设置。安装后,选择您的颜色主题。最后,使用64位独立安装程序安装 Git for Windows

.NET应用程序进行基准测试

.NET团队提供了一组基准测试,可用于评估不同体系结构上各种.NET版本的性能。这些基准测试依赖于BenchmarkDotNet 库,该库提供了一个框架来简化代码执行时间的度量。

可以使用C#属性将这些度量添加到代码中。该库评估执行时间并报告平均计算时间和标准偏差。此外,该库可以生成绘图来帮助您评估代码性能。所有这些功能在.NET性能基准测试中也可用。

要使用这些基准测试,请从克隆dotnet performance存储库开始:

git clone https://github.com/dotnet/performance.git

然后,导航到performance\src\benchmarks\micro,如下所示。

performance\src\benchmarks\micro中,输入以下命令:

dotnet run -c Release -f net8.0

应用程序将生成并启动,并显示可用性能基准的列表。

现在,键入475并按Enter键以启动C#字符串数据类型的性能测试。此操作的结果如下所示(向上滚动以查看此表)。

默认情况下,该表汇总了性能测试结果。您可以看到每个性能测试的执行时间和统计信息(平均值、中位数、最小值、最大值和标准差)。这为您提供了有关代码性能的全面信息。

与控制台应用一样,该dotnet run命令默认使用Arm64体系结构。若要使用x64启动性能测试,请使用以下-a开关:

dotnet run -c Release -f net8.0 -a x64

但是,目前,BenchmarkDotNet库与基于Arm64的计算机上适用于x64.NET SDK不兼容。因此,BenchmarkDotNet会报告错误和不正确的执行时间。

若要比较x64Arm64上的.NET性能,请使用控制台应用模板并实现自定义基准。

自定义基准测试

若要实现自定义基准,请使用 System.Diagnostics.Stopwatch 类。首先创建控制台应用程序Arm64.Performancedotnet new console -o Arm64.Performance)。然后,在Visual Studio中打开它。接下来,通过单击新建文件并键入PerformanceHelper.cs作为文件名来创建一个新文件。

然后,打开PerformanceHelper.cs,并定义PerformanceHelper类:

using System.Diagnostics;
 
namespace Arm64.Performance
{
    public static class PerformanceHelper
    {
        private static readonly Stopwatch stopwatch = new();
 
        public static void MeasurePerformance(Action method, int executionCount, string label)
        {
            stopwatch.Restart();
 
            for(int i = 0; i < executionCount; i++)
            {
                method();
            }
 
            stopwatch.Stop();
 
            Console.WriteLine($"[{label}]: {stopwatch.ElapsedMilliseconds.ToString("f2")} ms");
        }
    }
}

PerformanceHelper类是静态的。它有一个方法,即MeasurePerformance,该方法通过使用 Action委托调用提供的函数来工作。此委托作为MeasurePerformance方法的第一个参数传递。调用该方法的第二个参数executionCount,所指定的次数与调用次数相同。之后,该MeasurePerformance方法打印执行特定代码所需的时间。此外,MeasurePerformance接受第三个参数label,您可以使用该参数传递描述性能测试的字符串。

您可以通过创建一个新文件来定义性能测试,PerformanceTests.cs,您可以在其中声明PerformanceTests类:

namespace Arm64.Performance
{
    public static class PerformanceTests 
    { 
    }
}

此类为空。您将在下一节中对其进行扩展。

列表排序

列表排序的性能测试显示对包含100,000double类型元素的列表进行排序所需的时间。可以使用 System.Random 类中提供的伪随机数生成器来准备列表。

若要实现此测试,请使用以下代码补充该PerformanceTests类:

public static class PerformanceTests
{ 
    private static readonly Random random = new();
 
    private static List<double> PrepareList()
    {
        const int listSize = 100000;
 
        return Enumerable.Range(0, listSize)
                    .Select(r => random.NextDouble())
                    .ToList();
    }
 
    public static void ListSorting()
    {
        var list = PrepareList();
 
        list.Sort();
    }    
}

有三个新元素:

  • private static random成员的声明和初始化。这是伪随机数生成器的一个实例。
  • private static PrepareList方法创建一个伪随机列表,其中包含100,000个double类型的元素。若要生成此列表,请使用System.Linq 中的Enumerate.Range方法。伪随机数生成器创建此列表的每个元素。
  • 公共静态ListSorting方法首先调用PrepareList帮助程序方法来创建随机列表。然后,该Sort方法对这个随机列表进行排序。

矩阵乘法

现在,您实现了方阵乘法。首先,使用该GenerateRandomMatrix方法扩展PerformanceTests类的定义。在以下ListSorting位置将此方法添加到PerformanceTests.cs文件中:

private static double[,] GenerateRandomMatrix()
{
    const int matrixSize = 500;
 
    var matrix = new double[matrixSize, matrixSize];
 
    for (int i = 0; i < matrixSize; i++)
    {
        for (int j = 0; j < matrixSize; j++)
        {
            matrix[i, j] = random.NextDouble();
        }
    }
 
    return matrix;
}

此方法生成一个500 x 500的平方矩阵。一个两层for循环,其中每一步调用random.NextDouble,生成一个伪随机生成的double类型的值。

接下来,在PerformanceTests类中,添加以下方法:

private static double[,] MatrixMultiplication(double[,] matrix1, double[,] matrix2)
{
    if (matrix1.Length != matrix2.Length)
    {
        throw new ArgumentException("The matrices must be of equal size");
    }
 
    if (matrix1.GetLength(0) != matrix1.GetLength(1) || matrix2.GetLength(0) != matrix2.GetLength(1))
    {
        throw new ArgumentException("The matrices must be square");
    }
 
    int matrixSize = matrix2.GetLength(0);
 
    var result = new double[matrixSize, matrixSize];
 
    for (int i = 0; i < matrixSize; i++)
    {
        for (int j = 0; j < matrixSize; j++)
        {
            result[i, j] = 0;
 
            for (int k = 0; k < matrixSize; k++)
            {
                result[i, j] += matrix1[i, k] * matrix2[k, j];
            }
        }
    }
 
    return result;
}

MatrixMultiplication方法将两个平方矩阵作为输入,然后使用数学公式计算乘积。该result变量使用三层for循环来存储矩阵乘法的结果,该MatrixMultiplication方法将返回该结果。

最后,在PerformanceTests类中,实现以下公共方法,该方法生成两个平方矩阵,然后计算乘积:

public static void SquareMatrixMultiplication()
{
    var matrix1 = GenerateRandomMatrix();
    var matrix2 = GenerateRandomMatrix();
 
    MatrixMultiplication(matrix1, matrix2);
}

字符串操作

对于最后一个性能基准测试,请使用字符串操作。在PerformanceTests类中,定义loremIpsum变量,该变量存储占位符文本的片段:

private static readonly string loremIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
    "Curabitur ut enim dapibus, pharetra lorem ut, accumsan massa. " +
    "Morbi et nisi feugiat magna dapibus finibus. Pellentesque habitant morbi " +
    "tristique senectus et netus et malesuada fames ac turpis egestas. Proin non luctus lectus, " +
    "vel sollicitudin ante. Nullam finibus lobortis venenatis. Nulla sit amet posuere magna, " +
    "a suscipit velit. Cras et commodo elit, nec vestibulum libero. " +
    "Cras at faucibus ex. Suspendisse ac nulla non massa aliquet sagittis. " +
    "Fusce tortor enim, feugiat ultricies ultricies at, viverra et neque. " +
    "Praesent dolor mauris, pellentesque euismod pharetra ut, interdum non velit. " +
    "Fusce vel nunc nibh. Sed mi tortor, tempor luctus tincidunt et, tristique id enim. " +
    "In nec pellentesque orci. Nulla efficitur, orci sit amet volutpat consectetur, " +
    "enim risus condimentum ex, ac tincidunt mi ipsum eu orci. Maecenas maximus nec massa in hendrerit.";

然后,实现StringOperations public方法:

public static void StringOperations()
{
    loremIpsum.Split(' ');
 
    loremIpsum.Substring(loremIpsum.LastIndexOf("consectetur"));
 
    loremIpsum.Replace("nec", "orci");
 
    loremIpsum.ToLower();
 
    loremIpsum.ToUpper();
}

此方法使用空格分隔符将占位符文本拆分为子字符串。然后,它采用从单词consectetur的最后一个索引开始的子字符串。接下来,它将替换necorci,将字符串转换为小写,然后转换为大写,以模拟C#应用中字符串变量的典型操作。

把东西放在一起

现在,您可以在控制台应用程序中使用这些性能测试。通过将默认内容(Console.WriteLine("Hello, World!");)替换为以下语句来修改 Program.cs 文件:

using Arm64.Performance;
 
Console.WriteLine($"Processor architecture: " +
    $"{Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE")}");
 
const int trialCount = 5;
 
for ( int i = 0; i < trialCount; i++ )
{
    Console.WriteLine($"Trial no: {i + 1} / {trialCount}");
 
    PerformanceHelper.MeasurePerformance(PerformanceTests.ListSorting, 
        executionCount: 500, "List sorting");
 
    PerformanceHelper.MeasurePerformance(PerformanceTests.SquareMatrixMultiplication, 
        executionCount: 10, "Matrix multiplication");
 
    PerformanceHelper.MeasurePerformance(PerformanceTests.StringOperations, 
        executionCount: 500000, "String operations");
}

此代码导入定义PerformanceHelperPerformanceTests类的Arm64.Performance命名空间。然后,代码打印处理器体系结构(Arm64AMD64),具体取决于用于运行应用的SDK的体系结构。

您有一个常量trialCount,可用于指定性能测试集的执行频率。每个测试批处理运行ListSorting 500次,然后执行SquareMatrixMultiplication 10次和StringOperations 500,000次。这实现了测试批次的可比执行时间。单矩阵乘法比单字符串运算慢。因此,必须对后者进行更多处决。

Arm64上的.NET性能提升

您现在可以启动该应用程序以评估其在不同架构上的性能。首先使用Arm64运行应用。在Arm64.Performance文件夹中,输入:

dotnet run -c Release

这将启动控制台应用,你应看到以下输出。

现在,若要比较执行时间,请使用x64体系结构启动应用:

dotnet run -c Release -a x64

此命令将导致以下输出:

与在Arm64上本机执行这些操作相比,这些操作在模拟的x64上花费的时间都更多。平均而言,本机执行为列表排序提供了大约15%的性能提升,为矩阵乘法提供了291%的性能提升,为字符串运算提供了239%的性能提升。

下图总结了x64Arm64本机执行的代码的平均执行时间。

该图显示了执行时间的显著改善。

结论

本文演示了如何使用.NET SDK进行跨平台控制台应用开发。它介绍了如何使用不同的处理器体系结构(x64Arm64)创建项目应用程序模板并启动应用程序。

然后,它演示了如何使用标准和自定义代码对.NET应用程序进行基准测试。它使用后者来演示在Arm64驱动的设备上本机执行代码时显著提高性能——矩阵乘法的速度快了近三倍。在x64上运行的代码必须使用仿真层,这会消耗额外的CPU和内存。如果没有这个额外的层,本机Arm64将获得性能优势和更高的效率。

现在,你已了解如何利用Arm64的强大功能,请开始将 Arm64 用于.NET应用。

https://www.codeproject.com/Articles/5367981/NET-Performance-on-Arm64

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值