前言

矢量化是性能优化的重要技术,也是寄托硬件层面的优化技术。本篇来看下。

概括

一:矢量化支持的问题:

矢量化的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。文章首发。

.Net7矢量化的性能优化的更多相关文章

  1. 基础才是重中之重~LazyInitializer.EnsureInitialized对属性实现化的性能优化

    回到目录 LazyInitializer.EnsureInitialized是frameworks4.0引入的新东西,实现对属性延时初始化的功能,它作用在System.Threading命名空间下,所 ...

  2. React组件性能优化

    转自:https://segmentfault.com/a/1190000006100489 React: 一个用于构建用户界面的JAVASCRIPT库. React仅仅专注于UI层:它使用虚拟DOM ...

  3. asp.net mvc 性能优化——(1)静态化

    asp.net mvc 性能优化--(1)静态化 在改善页面性能的同时,可能会采用静态化的策略,对于不能实时静态化的内容,则采用缓存.本文主要讨论如何实现cshtml的静态化(实际上还不是完全的htm ...

  4. CSS3与页面布局学习总结(八)——浏览器兼容与前端性能优化

    一.浏览器兼容 1.1.概要 世界上没有任何一个浏览器是一样的,同样的代码在不一样的浏览器上运行就存在兼容性问题.不同浏览器其内核亦不尽相同,相同内核的版本不同,相同版本的内核浏览器品牌不一样,各种运 ...

  5. CSS3与页面布局学习笔记(八)——浏览器兼容性问题与前端性能优化方案

    一.浏览器兼容 1.1.概要 世界上没有任何一个浏览器是一样的,同样的代码在不一样的浏览器上运行就存在兼容性问题.不同浏览器其内核亦不尽相同,相同内核的版本不同,相同版本的内核浏览器品牌不一样,各种运 ...

  6. [转] 擎天哥as3教程系列第二回——性能优化

    所谓性能优化主要是让游戏loading和运行的时候不卡. 一  优化fla导出的swf的体积? 1,  在flash中,舞台上的元件最多,生成的swf越大,库里面有连接名的元件越多,swf越大.当舞台 ...

  7. [转]numpy性能优化

    转自:http://blog.csdn.net/pipisorry/article/details/39087583 http://blog.csdn.net/pipisorry/article/de ...

  8. luajit官方性能优化指南和注解

    luajit是目前最快的脚本语言之一,不过深入使用就很快会发现,要把这个语言用到像宣称那样高性能,并不是那么容易.实际使用的时候往往会发现,刚开始写的一些小test case性能非常好,经常毫秒级就算 ...

  9. 性能优化7--App瘦身

    1. 前言 如果你对App优化比较敏感,那么Apk安装包的大小就一定不会忽视.关于瘦身的原因,大概有以下几个方面: 对于用户来说,在功能差别不大的前提下,更小的Apk大小意味更少的流量消耗,也意味着更 ...

  10. 9 ArcGIS Server 性能优化

    1.系统性能影响因子 地图.服务类型.数据源.客户端技术.CPU.数据结构.网络.内存.存储.部署.架构.服务接口.SDE等. 2.ArcGIS Server性能优化 数据结构与数据源:数据结构(矢量 ...

随机推荐

  1. gralde-plugin->docker-compose的使用

    在java web项目中,本地开发经常会需要在本地使用docker启动数据库等之类的服务.gradle提供了一个插件,允许通过gradle task启动docker的容器.在这里我们介绍的一个gral ...

  2. MySQL 8.0:无锁可扩展的 WAL 设计

    这篇文章整理自MySQL官方文档,介绍了8.0在预写式日志上实现上的修改,观点总结如下: 在8.0以前,为了保证flush list的顺序,redo log buffer写入过程需要加锁,无法实现并行 ...

  3. [GIT]辨析/区别: git reset HEAD 与 git reset --hard HEAD | 版本回撤

    1 场景1: 撤销到远程仓库或本地仓库的最新最近一次的正式版本 1.1 文由 时常有这样一种场景,不小心改动了部分文件,或修改了部分文件却发现无用,此时可能还没有git push,也可能push了:又 ...

  4. 和我一起学 Three.js【初级篇】:1. 搭建 3D 场景

    欢迎关注「前端乱步」公众号,我会在此分享 Web 开发技术,前沿科技与互联网资讯. 0. 系列文章合集 本系列第 6,7 章节支持微信公众号内付费观看,将在全系列文章点赞数+评论数 >= 500 ...

  5. LeeCode 942 增减字符串匹配

    LeeCode 942 题目描述: 由范围 [0,n] 内所有整数组成的 n+1 个整数的排列序列可以表示为长度为 n 的字符串 s ,其中: 如果 perm[i] < perm[i + 1]  ...

  6. Spring中Bean的实例化详细流程

    还是举个例子,我有一个朋友小汪他远赴南方某城市打工.然后安定下来后他的朋友很想来家里玩,但是呢我这个朋友家里搞的很乱,所以他不好意思请朋友来家里玩.这时我的另一个朋友说那请一个保姆把家里好好整理一下就 ...

  7. [INS-40996] Installer has detected that the Oracle home (/home/grid) is not empty in the following nodes: [rac2] --求助帖?

    问题描述:12c安装grid的时候,一直再报一个[INS-40996] Installer has detected that the Oracle home (/home/grid) is not ...

  8. Python常见面试题017: Python中是否可以获取类的所有实例

    017. Python中是否可以获取类的所有实例 转载请注明出处,https://www.cnblogs.com/wuxianfeng023 出处 https://docs.python.org/zh ...

  9. LangChain vs Semantic Kernel

    每当向他人介绍 Semantic Kernel, 会得到的第一个问题就是 Semantic Kernel 类似于LangChain吗,或者是c# 版本的LangChain吗? 为了全面而不想重复的回答 ...

  10. git拉取代码总提示输入密码解决方法

    公司用的gitlab,在项目拉取(git clone)和更新(git pull)的时候,每次都提示输入用户名密码,不胜其烦,解决方法如下: # 首先 git config --global crede ...