使用.NET Hardware Intrinsics API加速机器学习场景
ML.NET 0.6版本刚刚发布不久,我们知道ML.NET代码已经依赖于使用本机代码库的性能矢量化。这是一个重新实现托管代码中现有代码库的机会,使用.NET Hardware Intrinsics进行矢量化,并比较结果。
什么是矢量化,什么是SIMD,SSE和AVX?
矢量化是用于同时将相同操作应用于阵列的多个元素的名称。在x86 / x64平台上,可以通过使用单指令多数据(SIMD)CPU指令在类似阵列的对象上操作来实现矢量化。
SSE(Streaming SIMD Extensions)和AVX(Advanced Vector Extensions)是x86架构的SIMD指令集扩展的名称。SSE已经存在很长时间了:CoreCLR底层.NET Core要求x86平台至少支持SSE2指令集。AVX是SSE的扩展,现在可以广泛使用。它的关键优势在于它可以在一条指令中处理内存中8个连续的32位元素,是SSE的两倍。
.NET Core 3.0将SIMD指令公开为可直接用于托管代码的API,从而无需使用本机代码来访问它们。
基于ARM的CPU确实提供了类似的内在函数,但.NET Core尚不支持它们(尽管工作正在进行中)。因此,当AVX和SSE都不可用时,必须使用软件回退代码路径。JIT使得以非常有效的方式执行此回退成为可能。当.NET Core确实公开ARM内在函数时,代码可以利用它们,此时软件回退很少需要。
项目目标
- 通过使用软件回退创建单个托管程序集,增加ML.NET平台范围(x86,x64,ARM32,ARM64等)
- 通过在可用的情况下使用AVX指令来提高ML.NET性能
- 验证.NET硬件内在函数API 并演示性能与本机代码相当
原本可以通过简单地更新本机代码来使用AVX指令来实现第二个目标,但是同时转移到托管代码我可以消除为每个目标架构构建和发布单独二进制文件的需要 - 它通常也更容易维护托管代码。
挑战
首先要熟悉C#和.NET,然后我的工作包括:
- 用于C#中CPU数学运算的基层实现。如果你不熟悉,请参阅这篇伟大的MSDN杂志文章C# - All About Span:探索新的.NET Mainstay以及文档。
- 根据可用性,启用AVX,SSE和软件实现之间的切换。
- 正确处理托管代码中的指针,并删除某些现有代码所做的对齐假设
- 使用multitargeting允许ML.NET继续在没有.NET Hardware Intrinsics API的平台上运行。
多目标
.NET Hardware Intrinsics将在.NET Core 3.0中提供,目前正在开发中。ML.NET还需要在.NET Standard 2.0兼容平台上运行 - 例如.NET Framework 4.7.2和.NET Core 2.1。为了支持这两者,我选择使用多目标创建一个同时针对.NET Standard 2.0和.NET Core 3.0的.csproj 文件。
- 在.NET Standard 2.0上,系统将使用具有SSE硬件内在函数的原始本机实现
- 在.NET Core 3.0上,系统将使用带有AVX硬件内在函数的新托管实现。
代码之初
在原始代码中,机器学习中使用的每个培训师,学习者和转换最终都称为包装器方法,对输入数组执行CPU数学运算,例如 SseUtils
MatMulDense,它将两个密集数组的矩阵乘法解释为矩阵,和SdcaL1UpdateSparse,执行稀疏数组的随机双坐标上升的更新步骤。
这些包装器方法假定优先选择SSE指令,并在另一个类中调用相应的方法,该方法用作托管代码和本机代码之间的接口,并包含直接调用其本机等效项的方法。文件中的这些本机方法依次使用包含SSE硬件内在函数的循环实现CPU数学运算。
打破托管代码路径
对于这段代码,我为在.NET Core 3.0上变为活动的CPU数学运算添加了一个新的独立代码路径,并保持原始代码路径在.NET Standard 2.0上运行。以前所有方法的调用站点现在都称为相同名称的方法,保持CPU数学运算的API签名相同。 SseUtils CpuMathUtils
CpuMathUtils 是一个新的分部类,它包含表示CPU数学运算的每个公共API的两个定义,其中一个仅在.NET Standard 2.0上编译,而另一个仅在.NET Core 3.0上编译。此条件编译功能为方法创建两个独立的代码路径。在.NET Standard 2.0上编译的那些函数定义直接调用它们的对应物,它们基本上遵循原始的本机代码路径。
用software fallback编写代码
另一方面,在.NET Core 3.0上编译的其他函数定义根据运行时的可用性切换到同一CPU数学运算的三个实现之一:
- 一个用包含AVX硬件内在函数的循环实现操作的方法,
AvxIntrinsics - 一个用包含SSE硬件内在函数的循环实现操作的方法
SseIntrinsics - 软件后备,以防AVX和SSE都不受支持。
每当代码使用.NET硬件内在函数时,您通常会看到此模式 - 例如,这是向向量添加标量的代码:
如果支持AVX,则首选,否则使用SSE(如果可用),否则使用软件回退路径。在运行时,JIT实际上只为这三个块中的一个生成代码,适用于它自己发现的平台。
为了给你一个想法,这里的AVX实现看起来像上面的方法调用:
您将注意到它使用AVX以8为一组进行操作,然后使用SSE对任何4组进行操作,最后为任何剩余的进行软件循环。
由于托管代码中的和方法直接实现类似于最初在文件中的本机方法的CPU数学运算,因此代码更改不仅消除了本机依赖性,还简化了公共API和基础层硬件内在函数之间的抽象级别。
在进行此替换后,我能够使用ML.NET执行任务,例如具有随机双坐标上升的列车模型,进行超参数调整,以及在Raspberry Pi上执行交叉验证,此时ML.NET需要x86 CPU。
这就是现在架构的样子(图1):

性能改进
那么这对性能有何不同?
我使用Benchmark.NET编写测试来收集测量数据。
首先,我禁用了AVX代码路径,以便在使用相同的SSE指令时公平地比较本机和托管实现。如图2所示,性能具有可比性:在测试运行的大型向量上,托管代码添加的开销并不显着。

图2
其次,我启用了AVX支持。图3显示微基准测试的平均性能增益比单独的SSE高约20%。

图3
将两者结合起来 - 从本机代码中的SSE实现升级到托管代码中的AVX实现 - 我测量了微基准测试的18%的改进。有些操作的速度提高了42%,而其他一些涉及稀疏输入的操作则有进一步优化的潜力。
最重要的当然是真实场景的表现。在.NET Core 3.0上,K-means聚类和逻辑回归的训练模型加快了约14%(图4)。

图4
我希望这已经证明了.NET硬件内在函数的强大功能,并且我鼓励您在.NET Core 3.0预览可用时考虑在自己的项目中使用它们的机会。
使用.NET Hardware Intrinsics API加速机器学习场景的更多相关文章
- 一场完美的“秒杀”:API加速的业务逻辑
清晨,我被一个客户电话惊醒,客户异常焦急,寻问CDN能不能帮助他们解决“秒杀”的问题,他们昨天刚刚进行了“整点秒杀活动”,结果并发量过大,导致服务宕机,用户投诉. 为了理清思路,我问了对方三个问题: ...
- WEB API系列(一):WEB API的适用场景、第一个实例
在我前一篇博客中已经给各位简单介绍了HTTP协议与RestFul API的关系,以及一些基本的HTTP协议知识,在这些知识的铺垫下,今天,我们一起来讨论一下WEB API的适用场景,然后写我们第一个W ...
- WCF与Web API 的应用场景
Web api 主要功能: 支持基于Http verb (GET, POST, PUT, DELETE)的CRUD (create, retrieve, update, delete)操作 请求的回 ...
- 【转】WCF与Web API 区别(应用场景)
Web api 主要功能: 支持基于Http verb (GET, POST, PUT, DELETE)的CRUD (create, retrieve, update, delete)操作 请求的回 ...
- 常见数组&字符串API及其应用场景总结
数组API: String(arr):将arr中每个元素转化为字符串,逗号连接 场景:用于鉴别数据有没有修改等. ps:String是万能的 toString 只能转换除null和unde ...
- WCF与Web API 区别(应用场景)
Web api 主要功能: 支持基于Http verb (GET, POST, PUT, DELETE)的CRUD (create, retrieve, update, delete)操作 请求的回 ...
- jedis实现操纵redis的常用api及使用场景
简单记录一下,和描述一下常用的业务场景.好记性不如烂笔头. pom.xml <!--整合redis--> <dependency> <groupId>redis.c ...
- kafka系列九、kafka事务原理、事务API和使用场景
一.事务场景 最简单的需求是producer发的多条消息组成一个事务这些消息需要对consumer同时可见或者同时不可见 . producer可能会给多个topic,多个partition发消息,这些 ...
- Redis数据类型的常用API以及使用场景
一.通用命令 1.keys 遍历出所有的key 一般不在生产环境使用 2.dbsize key的总数 3.exists key 4.del key 删除指定key-value 5.expire k ...
随机推荐
- poi实现Excel文件的读取
1.前端代码 $("#upload").on('click', function() { var formData = new FormData(); var name = $(& ...
- Spring学习-01
一.Srping 一个轻量级DI.IOC.AOP的容器框架 DI:依赖注入 IOC:控制反转 AOP:面向切面 二.构造器注入 Constructor-arg 属性:index/name/type/r ...
- SpringCloud消息总线
我们在springcloud(七):配置中心svn示例和refresh中讲到,如果需要客户端获取到最新的配置信息需要执行refresh,我们可以利用webhook的机制每次提交代码发送请求来刷新客户端 ...
- 下划线“_”在oracle中不是单纯的表示下划线的意思,而是表示匹配单一任何字符!
[解决办法]1.使用 escape() 函数escape关键字经常用于使某些特殊字符,如通配符:'%','_'转义为它们原来的字符的意义,被定义的转义字符通常使用'\',但是也可以使用其他的符号.例如 ...
- OPC转发阿里云alink工具
这个最近还在做 2019-04-24 今天抽空吧基本mqtt上传,OPC遍历,导出物模型功能先做了 上报操作日志,上报错误信息,导入参数,导出参数还没做 有需要可以联系微信NBDX123
- maven配置parent pom查找策略
当我们在pom.xml中添加parent pom的时候,通常maven会按照如下顺序查找parent依赖: relativePath标签指向的路径. 默认的relativePath路径".. ...
- MVC实例应用
MVC是Model-View-Controller的简称,即模型-视图-控制器.MVC是一种设计模式, 它把应用程序分成三个核心模块:模型.视图.控制器,它们各自处理自己的任务. 1.模型(Model ...
- CentOS6.8手动安装MySQL5.6(转)
1.安装mysql5.6依存包 2.下载编译包 wget https://dev.mysql.com/get/Downloads/MySQL-5.6/mysql-5.6.35-linux-glibc2 ...
- [ 10.05 ]CF每日一题系列—— 962B贪心和思维?
Description: 非 * 号的地方可以放A或B,不能AA或BB,一共有a个A,b个B,问你最多放几个 Solution: 1.模拟一下,找连续空位长度,如果长度为奇数,则我可以有一个位置放任意 ...
- Java 线程池(ThreadPoolExecutor)原理解析
在我们的开发中“池”的概念并不罕见,有数据库连接池.线程池.对象池.常量池等等.下面我们主要针对线程池来一步一步揭开线程池的面纱. 有关java线程技术文章还可以推荐阅读:<关于java多线程w ...