前言

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

概括

一:矢量化支持的问题:

矢量化的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. python3各数据类型的常用方法

    python3数据类型包括: 数字.字符串str.列表list.元组tuple.字典dict.集合set.布尔bool 1.字符串(str)-可变-用"".''定义 (1)uppe ...

  2. CTFSHOW-Reverse-刷题记录

    re2 打开初步分析为32位程序,无壳,拖入IDA32分析 找到关键行47-50,分析得出各函数内部的基本作用如图所示 首先进入分析输入后的处理 可以很明显看出,是对输入的字符串进行处理后再操作,因此 ...

  3. 在线编写Markdown

    部署 editor.md 实现在线编写MD 安装Nginx服务 apt install nginx yum install nginx 修改Nginx配置 root@cby:~# vim /etc/n ...

  4. pandas安装1

    Python 官方标准发行版并没有自带 Pandas 库,因此需要另行安装.除了标准发行版外,还有一些第三方机构发布的 Python 免费发行版, 它们在官方版本的基础上开发而来,并有针对性的提前安装 ...

  5. GPFS 文件系统部署步骤

    GPFS 文件系统部署步骤 参考文档: 简书网友提供: https://www.jianshu.com/p/a0ecc0838b3b?utm_campaign=maleskine&utm_co ...

  6. 面试官:服务器最大可以创建多少个tcp连接以及端口并解释下你对文件句柄的理解

    转载请注明出处: 1.最大可以创建多少个tcp连接 服务器最大可以创建多少个TCP连接取决于多个因素,例如服务器的硬件配置.网络带宽.操作系统设置等.一般来说,现代服务器的硬件资源和网络带宽都比较充足 ...

  7. KMeans算法与GMM混合高斯聚类

    一.K-Means K-Means是GMM的特例(硬聚类,基于原型的聚类).假设多元高斯分布的协方差为0,方差相同.   K-Means算法思想 对于给定的样本集,按照样本之间的距离大小,将样本集划分 ...

  8. 微服务 - Redis缓存 · 数据结构 · 持久化 · 分布式 · 高并发

    本篇内容基于 Redis v7.0 的阐述:官网:https://redis.io/ 本篇计划用 Docker 容器辅助部署,所以需要了解点 Docker 知识:官网:https://www.dock ...

  9. extend笔记

    JavaScript面向对象 继承extend 1. 概念(主要用途) 将子类中的共性代码 ( 属性和方法 ) 抽取出来 放到父类中 每当有一个新的子类需要用到共性的属性或者方法时 不需要在自己内容复 ...

  10. 关于在visual Studio 2022中无法找到 ASP.NET Core Web Application 或 ASP.NET Core Web 应用程序

    在学习 ASP.NET Core Web Application 时 发现无论如何都无法找到这个模板,在翻遍论坛后都没有看到解决的方法,在我下载 visual Studio 2017 中终于找到了 但 ...