.Net7矢量化的性能优化

news2024/11/20 5:10:01

前言

矢量化是性能优化的重要技术,也是寄托硬件层面的优化技术。本篇来看下。文章来源:微软官方博客

概括

一:矢量化支持的问题:
矢量化的System.Runtime.Intrinsics.X86.Sse2.MoveMask
函数和矢量化的Vector128.Create().ExtractMostSignificantBits()
函数返回的结果是一样的。但是前者只能在支持SSE2的128位矢量化平台上工作,而后者可以在任何支持128位矢量化平台上工作,包括Risc-V,Arm64,WASM等平台。这里以一段代码看下:

private static void Main()
{
   Vector128<byte> v = Vector128.Create((byte)123);
   while (true)
   {
      WithIntrinsics(v);
      WithVector(v);
      break;
   }
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static int WithIntrinsics(Vector128<byte> v) => Sse2.MoveMask(v);
[MethodImpl(MethodImplOptions.NoInlining)]
private static uint WithVector(Vector128<byte> v) => v.ExtractMostSignificantBits();

看下它的ASM代码:

WithIntrinsics:
G_M000_IG01:                ;; offset=0000H
       55                   push     rbp
       C5F877               vzeroupper
       488BEC               mov      rbp, rsp
       48894D10             mov      bword ptr [rbp+10H], rcx

G_M000_IG02:                ;; offset=000BH
       488B4510             mov      rax, bword ptr [rbp+10H]
       C5F91000             vmovupd  xmm0, xmmword ptr [rax]
       C5F9D7C0             vpmovmskb eax, xmm0

G_M000_IG03:                ;; offset=0017H
       5D                   pop      rbp
       C3                   ret


WithVector
G_M000_IG01:                ;; offset=0000H
       55                   push     rbp
       C5F877               vzeroupper
       488BEC               mov      rbp, rsp
       48894D10             mov      bword ptr [rbp+10H], rcx

G_M000_IG02:                ;; offset=000BH
       488B4510             mov      rax, bword ptr [rbp+10H]
       C5F91000             vmovupd  xmm0, xmmword ptr [rax]
       C5F9D7C0             vpmovmskb eax, xmm0

G_M000_IG03:                ;; offset=0017H
       5D                   pop      rbp
       C3                   ret

可以看到这两个函数生成的ASM几乎一模一样。

2.矢量化的一个例子
由于以上代码体现的SSE2的局限性,所以需要把一些代码矢量化,以便在任何平台上运行,这里看一个例子。

static bool Contains(ReadOnlySpan<byte> haystack, byte needle)
{
    for (int i = 0; i < haystack.Length; i++)
    {
        if (haystack[i] == needle)
        {
            return true;
        }
    }

    return false;
}

查找元素,找到了然后返回。怎么对这例子进行矢量化呢?首先需要判断你代码运行的硬件是否支持矢量化,可以通过Vector.IsHardwareAccelerated的返回值来判断。其次,传入的变量长度(haystack.length)必须的大于一个向量的长度(Vector .Count,win11加VS2022这个值是32)。那么改造之后如下:

static bool Contains(ReadOnlySpan<byte> haystack, byte needle)
{
    if (Vector.IsHardwareAccelerated && haystack.Length >= Vector<byte>.Count)
    {
        // ...
    }
    else
    {
        for (int i = 0; i < haystack.Length; i++)
        {
            if (haystack[i] == needle)
            {
                return true;
            }
        }
    }
    return false;
}

如果以上if的两个判断均为true的话,那么我们进入矢量化阶段。代码如下:

static unsafe bool Contains(ReadOnlySpan<byte> haystack, byte needle)
{
    if (Vector.IsHardwareAccelerated && haystack.Length >= Vector<byte>.Count)//判断当前运行的硬件是否符合矢量化以及变量的长度不能小于矢量化里面一个向量的长度。
    {
        fixed (byte* haystackPtr = &MemoryMarshal.GetReference(haystack))//获取变量的头指针
        {
            Vector<byte> target = new Vector<byte>(needle);//向量化需要查找的变量needle
            byte* current = haystackPtr;//变量haystack的头指针,以便于后面循环
            byte* endMinusOneVector = haystackPtr + haystack.Length - Vector<byte>.Count;//头指针+变量的长度减去一个向量的长度。同头指针current开始到endMinusOneVector在这个里面遍历循环,查找需要查找的变量target也就是向量化的needle,这里为什么要进去Vector<byte>.Count因为向量是从0开始查找的。
            do
            {
                if (Vector.EqualsAny(target, *(Vector<byte>*)current))//判断当前的指针是否与需要查找的变量相等
                {
                    return true;//相等就返回true
                }

                current += Vector<byte>.Count;//不相等指针就位移到下一个向量,继续遍历循环。
            }
            while (current < endMinusOneVector);//这里判断是否达到循环终点。
        }
    }
    else
    {
        for (int i = 0; i < haystack.Length; i++)
        {
            if (haystack[i] == needle)
            {
                return true;
            }
        }
    }
    return false;
}

以上代码几乎完成了90%,但是依然有点点问题。那就是最后一个向量endMinusOneVector没有被查找。所以还需要加上它的查找。最后的点如下,第一个Contains是不矢量化的,第二个Contains_Vector是矢量化之后的。

static bool Contains(ReadOnlySpan<byte> haystack, byte needle)
{
    for (int i = 0; i < haystack.Length; i++)
    {
        if (haystack[i] == needle)
        {
            return true;
        }
    }

    return false;
}
static unsafe bool Contains_Vector(ReadOnlySpan<byte> haystack, byte needle)
{
    if (Vector.IsHardwareAccelerated && haystack.Length >= Vector<byte>.Count)
    {
        fixed (byte* haystackPtr = &MemoryMarshal.GetReference(haystack))
        {
            Vector<byte> target = new Vector<byte>(needle);
            byte* current = haystackPtr;
            byte* endMinusOneVector = haystackPtr + haystack.Length - Vector<byte>.Count;
            do
            {
                if (Vector.EqualsAny(target, *(Vector<byte>*)current))
                {
                    return true;
                }

                current += Vector<byte>.Count;
            }
            while (current < endMinusOneVector);

            if (Vector.EqualsAny(target, *(Vector<byte>*)endMinusOneVector))
            {
                return true;
            }
        }
    }
    else
    {
        for (int i = 0; i < haystack.Length; i++)
        {
            if (haystack[i] == needle)
            {
                return true;
            }
        }
    }

    return false;
}

上面的代码几乎是完美的,测试下基准

private byte[] _data = Enumerable.Repeat((byte)123, 999).Append((byte)42).ToArray();//Enumerable.Repeat表示999个123的byte,放在数组,最后又加了一个42数值到数组

[Benchmark(Baseline = true)]
[Arguments((byte)42)]
public bool Find(byte value) => Contains(_data, value); // just the fallback path in its own method

[Benchmark]
[Arguments((byte)42)]
public bool FindVectorized(byte value) => Contains_Vectorized(_data, value); // the implementation we just wrote



|         Method | value |      Mean |    Error |   StdDev | Ratio | Code Size |
|--------------- |------ |----------:|---------:|---------:|------:|----------:|
|           Find |    42 | 508.42 ns | 2.336 ns | 2.185 ns |  1.00 |     110 B |
| FindVectorized |    42 |  21.57 ns | 0.342 ns | 0.303 ns |  0.04 |     253 B |

可以看到矢量化之后的性能,进行了夸张的25倍的增长。这段代码几乎完美,但是并不完美。这里是用的1000个元素测试,如果是小于30个元素呢?有两个方法,第一个是退回到没有矢量化的代码也就是Contains函数,第二个是把Vector切换到128位来操作。代码如下,几乎没变更:

static unsafe bool Contains128(ReadOnlySpan<byte> haystack, byte needle)
{
    if (Vector128.IsHardwareAccelerated && haystack.Length >= Vector128<byte>.Count)
    {
        ref byte current = ref MemoryMarshal.GetReference(haystack);

        Vector128<byte> target = Vector128.Create(needle);
        ref byte endMinusOneVector = ref Unsafe.Add(ref current, haystack.Length - Vector128<byte>.Count);
        do
        {
            if (Vector128.EqualsAny(target, Vector128.LoadUnsafe(ref current)))
            {
                return true;
            }

            current = ref Unsafe.Add(ref current, Vector128<byte>.Count);
        }
        while (Unsafe.IsAddressLessThan(ref current, ref endMinusOneVector));

        if (Vector128.EqualsAny(target, Vector128.LoadUnsafe(ref endMinusOneVector)))
        {
            return true;
        }
    }
    else
    {
        for (int i = 0; i < haystack.Length; i++)
        {
            if (haystack[i] == needle)
            {
                return true;
            }
        }
    }

    return false;
}

来进行一个基准测试:

private byte[] _data = Enumerable.Repeat((byte)123, 29).Append((byte)42).ToArray();

[Benchmark(Baseline = true)]
[Arguments((byte)42)]
public bool Find(byte value) => Contains(_data, value);

[Benchmark]
[Arguments((byte)42)]
public bool FindVectorized(byte value) => Contains_Vectorized(_data, value);


|         Method | value |      Mean |     Error |    StdDev | Ratio | Code Size |
|--------------- |------ |----------:|----------:|----------:|------:|----------:|
|           Find |    42 | 16.363 ns | 0.1833 ns | 0.1530 ns |  1.00 |     110 B |
| FindVectorized |    42 |  1.799 ns | 0.0320 ns | 0.0299 ns |  0.11 |     191 B |

同样的性能进行了16倍的提速。

结尾

作者:江湖评谈
欢迎关注公众号:jianghupt。文章首发。image

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/640942.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

get 、post请求 后台@RequestParam、@RequestBody 接收的方法集合

post、get请求 参数&#xff1a;数组、JSON对象、JSON字符串、地址栏 RequestParam、RequestBody 后端接收 //定义json对象&#xff0c;同时包含数组 var _queryData { jflb:"婚姻家庭纠纷",zlay:"xxxxx",ysCode:["0123","4567"]}…

机柜PDU与普通插座的区别,以及如何选择品牌专业PDU产品详解

PDU&#xff08;Power Distribution Unit&#xff09;&#xff0c;是将来自UPS的输出电流分配到各个IT设备的末端配电设备&#xff0c;是连接供电等基础设施与IT系统、关联机房内所有设备正常运转的关键设备。作为机房用电安全的重要保障&#xff0c;PDU设备的稳定与安全直接关…

FPGA驱动FT601实现USB3.0相机HDMI视频采集 提供工程源码和QT上位机源码

目录 1、前言2、FT601芯片解读和时序分析FT601功能和硬件电路FT601读时序解读FT601写时序解读 3、我这儿的 FT601 USB3.0通信方案4、详细设计方案5、vivado工程详解6、上板调试验证7、福利&#xff1a;工程代码的获取 1、前言 目前USB3.0的实现方案很多&#xff0c;但就简单好…

我们来谈谈udp

"却还有那些洗礼&#xff0c;那几句问候&#xff0c;那份温柔~" 一、 常用的Linux命令 (1) netstat查看网络状态 netstat是一个用来查看网络状态的重要工具,可以携带很多选项。 n 拒绝显示别名&#xff0c;能显示数字的全部转化成数字. l 仅列出有在 Listen…

【unity之IMGUI】所以你还想在百度上搜IMGUI的底层原理是什么吗?

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;uni…

安装python详细步骤(超详细,保姆级,一步一图)

❄️作者介绍&#xff1a;奇妙的大歪❄️ &#x1f380;个人名言&#xff1a;但行前路&#xff0c;不负韶华&#xff01;&#x1f380; &#x1f43d;个人简介&#xff1a;云计算网络运维专业人员&#x1f43d; 前言 作为一个实用学习的主义的学习者&#xff0c;最关心的问题一…

光亚展 | 移远通信Matter解决方案,照亮智能家居产业未来

6月9-12日&#xff0c;第二十八届广州国际照明展览会&#xff08;光亚展&#xff09;在中国进出口商品交易会展馆举行。本次展会以“光未来”为主题&#xff0c;整个照明、灯饰产业链的上下游企业、品牌在此汇聚&#xff0c;共同探讨照明行业未来的发展方向。 作为照明行业智能…

智见|黄铁军:未来的大模型生态中将会只有少数赢家

2023智源大会可谓群星璀璨。中外200余位人工智能顶级专家参会&#xff0c;人工智能领域最关键的人物、机构悉数亮相。 全面、专业、前沿&#xff0c;会场上大咖们观点激荡、多元碰撞&#xff0c;会场下观众们兴奋异常、座无虚席。 会上&#xff0c;北京智源人工智能研究院院长黄…

英语知识点-填空-考试酷

第一章 英语知识填空题 一、语法 1.词法&#xff1a;介词将关系建立在人/物A与人/物B&#xff0c;可能会形成修饰限制关系&#xff0c;有定语成分&#xff1b;介词将关系建立在事A与物B&#xff0c;可能有事A动作发生时环境因素&#xff0c;有状语成分&#xff1b;写作中经常用…

【Vue.js】1711- 深入浅出 Vue3 自定义指令

Vue.js[1] 提供了丰富的指令来简化开发者的工作。除了内置指令外&#xff0c;Vue.js 还支持自定义指令&#xff0c;开发者可以根据自己的需求扩展 Vue.js 的指令库。Vue.js 3.x 相较于 Vue.js 2.x 在自定义指令方面进行了一些改进&#xff0c;本文将介绍 Vue.js 3.x 中自定义指…

HLS 设计数字时钟

绪论 该项目的目标是展示 HLS 在设计数字系统方面的能力。为此&#xff0c;本文展示如何在 HLS 中描述数字时钟。如果有兴趣学习 HLS 编码技术&#xff0c;请参阅&#xff1a; ❝ https://highlevel-synthesis.com/ ❞ ❝ https://www.udemy.com/course/hls-combinational-circ…

2023 年的 5G 和网络安全风险

5G 网络的推出出奇地缓慢。作为一个概念&#xff0c;它于 2016 年推出&#xff0c;但直到 2019 年才在全球范围内推出。 四年后&#xff0c;在大多数国家地区&#xff0c;拥有 5G 设备的人数仍然很少。 不确定采用缓慢背后的原因是负担能力、缺乏必要性还是关于它的严重错误…

【redis】redis集群

这里是redis系列文章之《redis集群》&#xff0c;上一篇文章链接&#xff1a;【redis基础】哨兵_努力努力再努力mlx的博客-CSDN博客 目录 概念 作用 集群算法-分片-槽位slot 槽位与分配的概念及两者的优势 官网介绍分析 槽位 分片 两者的优势 slot槽位映射的三种解决方…

linux eventfd事件通知 比信号量更好用

专栏内容&#xff1a;linux下并发编程个人主页&#xff1a;我的主页座右铭&#xff1a;天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物&#xff0e; 目录 前言 概述 原理简介 使用场景 接口说明 头文件 参数说明 代码演示 默认参数 …

1740_使用Python+ImageMagick实现图像的批量压缩

全部学习汇总&#xff1a; GreyZhang/python_basic: My learning notes about python. (github.com) 前些年使用Linux的时候为了能够方便地往网络上上传照片&#xff0c;使用shell ImageMagick的组合进行照片的批量压缩一直觉得比较方便。不过&#xff0c;那时候即使这么简单的…

JMeter从入门到精通--开始你的第一个JMeter脚本

JMeter是一款在国外非常流行和受欢迎的开源性能测试工具&#xff0c;像LoadRunner 一样&#xff0c;它也提供了一个利用本地Proxy Server&#xff08;代理服务器&#xff09;来录制生成测试脚本的功能&#xff0c;但是这个功能并不好用。所以在本文中介绍一个更为常用的方法——…

软考A计划-2023系统架构师-知识点集锦(4/4)

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff…

redis的远程登录配置

目录 服务端&#xff1a; 客户端&#xff1a; 服务端&#xff1a; 步骤一&#xff1a;关闭防火墙 systemctl stop firewalld iptables -F setenforce 0 步骤二&#xff1a;配置/etc/redis.conf配置文件 vim /etc/redis.conf bind 127.0.0.1 改为 bind 0.0.0.0 &#xff08;…

【图书推荐 | 13】前端系列

【赠书活动第十三期 】 图书推荐 本期书籍&#xff1a;前端系列 图书列表&#xff1a; Vue.js核心技术解析Nuxt.js实战Nuxt.js Web开发实战HTML5CSS 从入门到精通Flutter2 开发实例精解Electron项目开发实战 Vue.js核心技术解析 Nuxt.js实战 Nuxt.js Web开发实战 HTML5CSS 从入…

机器鱼的制作分享

1. 运动功能说明 本文示例将实现R330样机机器鱼胸鳍能够灵活的上下摆动的功能。 2. 结构说明 本样机采用舵机模块来进行仿生机器鱼结构的设计。 胸鳍 整机 3. 电子硬件 在这个示例中&#xff0c;我们采用了以下硬件&#xff0c;请大家参考&#xff1a; 主控板 Basra主控板&…