筛子排序(SieveSort)

当你手头有了支持AVX-512(SIMD)的i9-11900K,你最想做什么?

i9-11900K?现在都14代了,谁还用11代的?

但12代以上就没有AVX-512了!

AVX-512有什么特别之处?有了这个硬件支持,现在你可以做一些“超级酷”的事情,比如“同时把16个32位整数加起来”。

还有什么更酷的呢?比如同时把16个32位整数或者8个64位整数,从小到大或者从大到小进行排序。

另外,既然要搞单指令多数据,那能不能把256个32位整数也(几乎)同时排序呢?更多整数能否尽可能快的排序呢?

这就是刚刚研发的筛子排序法,下面具体讲讲这个方法。

先说明,我们不直接使用AVX-512,而是用c/c++编译器提供的intrinsic指令(长得就像函数一样,但在编译的时候会被替换成相应的机器指令),intrinsic这个词是一个形容词,指的就是“固有的,内在的,本身的”。

查找Intel Intrinsic Guide不难发现如下指令:

_mm512_mask_reduce_max_epu32

_mm512_mask_reduce_min_epu32

这两个指令指的是,512位寄存器当成16个32位(无符号)寄存器,找里面那个最大或者最小的32位无符号整数,给出一个mask(掩码),mask为16位整数,对应16个32位整数,如果对应的位为1,则参与最大最小值的查找,如果为0,则忽略(取最小值的时候忽略项被假定为全1即最大值,取最大值的时候忽略项目被假定为全0即最小值)那个对应的整数。

看看具体的说明文档:

unsigned int _mm512_mask_reduce_min_epu32 (__mmask16 k, __m512i a)

Synopsis

unsigned int _mm512_mask_reduce_min_epu32 (__mmask16 k, __m512i a)
#include <immintrin.h>
Instruction: Sequence
CPUID Flags: AVX512F

Description

Reduce the packed unsigned 32-bit integers in a by maximum using mask k. Returns the minimum of all active elements in a.

Operation

DEFINE REDUCE_MIN(src, len) {
    IF len == 2
        RETURN (src[31:0] &lt; src[63:32] ? src[31:0] : src[63:32])
    FI
    len := len / 2
    FOR j:= 0 to (len-1)
        i := j*32
        src[i+31:i] := (src[i+31:i] &lt; src[i+32*len+31:i+32*len] ? src[i+31:i] : src[i+32*len+31:i+32*len])
    ENDFOR
    RETURN REDUCE_MIN(src[32*len-1:0], len)
}
tmp := a
FOR j := 0 to 16
    i := j*32
    IF k[j]
        tmp[i+31:i] := a[i+31:i]
    ELSE
        tmp[i+31:i] := 0xFFFFFFFF
    FI
ENDFOR
dst[31:0] := REDUCE_MIN(tmp, 16)

把这个指令,和比较指令配合起来,我们就可以找到16个32位整数中的最小值,以及那个最小值所在的位置(最小值可能出现多次)。

一条指令就可以找到最小值,但如何找到它的位置呢?我们通过将这个最小值复制16次,平铺在512位寄存器之中,然后再把这个平铺的结果和原16个32位整数的寄存器内容进行相等比较,并获得掩码,也就是说,那些相等的,在结果掩码中会置1,不相等的清0。

__mmask16 _mm512_mask_cmpeq_epi32_mask (__mmask16 k1, __m512i a, __m512i b)
#include <immintrin.h>
Instruction: vpcmpeqd k {k}, zmm, zmm
CPUID Flags: AVX512F

Description

Compare packed 32-bit integers in a and b for equality, and store the results in mask vector k using zeromask k1 (elements are zeroed out when the corresponding mask bit is not set).

FOR j := 0 to 15
    i := j*32
    IF k1[j]
        k[j] := ( a[i+31:i] == b[i+31:i] ) ? 1 : 0
    ELSE
        k[j] := 0
    FI
ENDFOR
k[MAX:16] := 0

可见这个指令也是需要掩码操作的,输入掩码对应位为0的,不进行比较,或者假定不相等。

有了这两个条件,我们就可以求出16个32为整数构成的512位寄存器中,到底那个或者哪几个32位整数是最小的,以及它们的位置:

bool sieve_get_min(__mmask16 mask, __m512i a, uint32_t& _min, __mmask16& _mask_min) {if (mask != 0) {_mask_min = _mm512_mask_cmpeq_epi32_mask(mask,a, _mm512_set1_epi32(_min = _mm512_mask_reduce_min_epu32(mask, a)));return true;}return false;
}

内嵌的这一表达式 _min = _mm512_mask_reduce_min_epu32(mask, a)

先求出最小值,然后调用函数

_mm512_set1_epi32(
            _min = _mm512_mask_reduce_min_epu32(mask, a))

将最小值复制16次,铺满整个512位寄存器。

最后调用函数

_mask_min = _mm512_mask_cmpeq_epi32_mask(mask,a, _mm512_set1_epi32(
    _min = _mm512_mask_reduce_min_epu32(mask, a)))

获得最小值出现位置对应的掩码。需要注意的是,如果最初的掩码就是0,那就不需要做任何计算,直接返回失败即可。比如_mask_min==0x82,那么最小值出现在16个32位整数构成的数组中下标为1和7(从0开始)的两个位置上。

现在,我们能确定连续16个32位整数中的最小值,以及这些最小值的分布情况。

然后我们如何对这16个32位整数进行排序呢?

上述给出求最小值的方法,求最大值也是一样的,我们把两者一起求出来,写一个函数完成,

bool sieve_get_min_max(__mmask16 mask, __m512i a, uint32_t& _min, uint32_t& _max, __mmask16& _mask_min, __mmask16& _mask_max) {if (mask != 0) {_mask_max = _mm512_cmpeq_epi32_mask(a, _mm512_set1_epi32(_max = _mm512_mask_reduce_max_epu32(mask, a)));_mask_min = _mm512_cmpeq_epi32_mask(a, _mm512_set1_epi32(_min = _mm512_mask_reduce_min_epu32(mask, a)));return true;}return false;
}

然后呢?如何对16个数进行排序?

首先调用这个函数,求出16个数中最大和最小的,但是要注意,求出的结果,可能具有多个最大值或者最小值。对于这种情况,我们有两个选项,一个是多个值,只选其中一个,另一个选项是多个值一起处理。

如果要只选其中一个,那么我们需要一个函数,把获得的掩码进行裁剪,比如只保留最高位或者只保留最低位,

__mmask16 single_bit(int leading_or_trailing, __mmask16 old_mask, __mmask16 mask) {if (mask == 0) return mask;unsigned short lz = __lzcnt16(mask);unsigned short tz = _tzcnt_u16(mask);unsigned short pc = __popcnt16(mask);__mmask16 and_mask = mask & old_mask;unsigned short ac = __popcnt16(and_mask);if (ac > 1) {__mmask16 cover = ~((leading_or_trailing? ((lz == 0 || lz >= 16) ? mask : (1 << (15 - lz))): ((tz == 0 || tz >= 16) ? mask : (1 << tz))));mask &= cover;lz = __lzcnt16(mask);tz = _tzcnt_u16(mask);pc = __popcnt16(mask);}switch (pc) {case 0://return mask;break;case 1:mask = (lz == 0 || lz >= 16) ? mask : (1 << (15 - lz));break;default://count of 1 >=2mask = (leading_or_trailing? ((lz == 0 || lz >= 16) ? mask : (1 << (15 - lz))): ((tz == 0 || tz >= 16) ? mask : (1 << tz)));break;}return mask;
}

这个函数看上去比较复杂,要实现的就是上述的掩码修正。

有了这个基础,就可以进行最后的处理了:一共16个32位整数,第一次我们取最大和最小值各1一个,分别放在结果的最高和最低下标上(15和0),然后修改掩码,让下一次查找和比较不再考虑已经处理过的两个值,然后进行第二次比较和查找,并把结果放在内层的下标上(14和1),然后是(13和2),依次类推,最后只需要进行8次同样的比较和查找并将数值填入对应的高低位置,就完成了16个数值的比较过程。

代码如下(为了加速sieve_get_min的功能已经被合并在其中了),:

__forceinline __m512i sieve_sort32x16(__m512i a, uint32_t* result = nullptr) {uint32_t buffer[16] = { 0 };if (result == nullptr) _mm512_store_epi32(result = buffer, a);__mmask16 mask = 0xffff;mask &= ~(single_bit(0, mask, _mm512_cmpeq_epi32_mask(a, _mm512_set1_epi32(result[15] = _mm512_mask_reduce_max_epu32(mask, a))))| single_bit(1, mask, _mm512_cmpeq_epi32_mask(a, _mm512_set1_epi32(result[0] = _mm512_mask_reduce_min_epu32(mask, a)))));mask &= ~(single_bit(0, mask, _mm512_cmpeq_epi32_mask(a, _mm512_set1_epi32(result[14] = _mm512_mask_reduce_max_epu32(mask, a))))| single_bit(1, mask, _mm512_cmpeq_epi32_mask(a, _mm512_set1_epi32(result[1] = _mm512_mask_reduce_min_epu32(mask, a)))));mask &= ~(single_bit(0, mask, _mm512_cmpeq_epi32_mask(a, _mm512_set1_epi32(result[13] = _mm512_mask_reduce_max_epu32(mask, a))))| single_bit(1, mask, _mm512_cmpeq_epi32_mask(a, _mm512_set1_epi32(result[2] = _mm512_mask_reduce_min_epu32(mask, a)))));mask &= ~(single_bit(0, mask, _mm512_cmpeq_epi32_mask(a, _mm512_set1_epi32(result[12] = _mm512_mask_reduce_max_epu32(mask, a))))| single_bit(1, mask, _mm512_cmpeq_epi32_mask(a, _mm512_set1_epi32(result[3] = _mm512_mask_reduce_min_epu32(mask, a)))));mask &= ~(single_bit(0, mask, _mm512_cmpeq_epi32_mask(a, _mm512_set1_epi32(result[11] = _mm512_mask_reduce_max_epu32(mask, a))))| single_bit(1, mask, _mm512_cmpeq_epi32_mask(a, _mm512_set1_epi32(result[4] = _mm512_mask_reduce_min_epu32(mask, a)))));mask &= ~(single_bit(0, mask, _mm512_cmpeq_epi32_mask(a, _mm512_set1_epi32(result[10] = _mm512_mask_reduce_max_epu32(mask, a))))| single_bit(1, mask, _mm512_cmpeq_epi32_mask(a, _mm512_set1_epi32(result[5] = _mm512_mask_reduce_min_epu32(mask, a)))));mask &= ~(single_bit(0, mask, _mm512_cmpeq_epi32_mask(a, _mm512_set1_epi32(result[9] = _mm512_mask_reduce_max_epu32(mask, a))))| single_bit(1, mask, _mm512_cmpeq_epi32_mask(a, _mm512_set1_epi32(result[6] = _mm512_mask_reduce_min_epu32(mask, a)))));mask &= ~(single_bit(0, mask, _mm512_cmpeq_epi32_mask(a, _mm512_set1_epi32(result[8] = _mm512_mask_reduce_max_epu32(mask, a))))| single_bit(1, mask, _mm512_cmpeq_epi32_mask(a, _mm512_set1_epi32(result[7] = _mm512_mask_reduce_min_epu32(mask, a)))));return _mm512_loadu_epi32(result);
}

观察代码不难发现,这个算法叫做叫筛子算法,是因为,先前被找到的最大和最小值,不再参与下一次的搜索和比较过程,就像被筛掉了一样。由于整个过程都只是用掩码和位运算来重定向数值的输出,所以并无大量数据交换操作,整个算法过程就像一种由组合逻辑电路实现的数据重排过程,没有分支也没有循环,于是不用分支预测所有数据都能预取,显然跑起来是相当快的。

先预览一下代码,本文未完待续。

GitHub - yyl-20020115/SieveSort: New sort algorithm using Intel SIMD-AVX512New sort algorithm using Intel SIMD-AVX512. Contribute to yyl-20020115/SieveSort development by creating an account on GitHub.icon-default.png?t=O83Ahttps://github.com/yyl-20020115/SieveSort/

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

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

相关文章

Redis 的 Java 客户端有哪些?官方推荐哪个?

Redis 官网展示的 Java 客户端如下图所示&#xff0c;其中官方推荐的是标星的3个&#xff1a;Jedis、Redisson 和 lettuce。 Redis 的 Java 客户端中&#xff0c;Jedis、Lettuce 和 Redisson 是最常用的三种。以下是它们的详细比较&#xff1a; Jedis&#xff1a; 线程安全&…

安卓13设置动态修改设置显示版本号 版本号增加信息显示 android13增加序列号

总纲 android13 rom 开发总纲说明 文章目录 1.前言2.问题分析3.代码分析4.代码修改5.编译6.彩蛋1.前言 设置 =》关于平板电脑 =》版本号 在这里显示了系统的一些信息,但是这里面的信息并不包含序列号之类的信息,我们修改下系统设置,在这里增加上相关的序列号。 2.问题分析…

【Linux】Linux基本命令

目录 文件和目录操作&#xff1a; ls cd pwd cp mv rm mkdir rmdir touch clear history which/whereis 文件查看和编辑&#xff1a; cat less head tail vi 或 vim sz/rz echo 系统信息和管理&#xff1a; su uname hostname df free top ps ki…

高可用集群keepalived---实战案例1

目录 一、环境: 二、文件的配置 1、server1 下载keepalived 创建etc下的keepalived目录,编辑配置文件 开机启动keepalived 安装Nginx 启动keepalived 2、server2 下载keepalived 创建etc下的keepalived目录,编辑配置文件 开机启动keepalived 安装Nginx 启动keepali…

软件企业毛利率正在变得越来越低

软件开发毛利率逐渐降低的现象可能受到多种因素的影响&#xff1a; 市场竞争加剧&#xff1a;随着软件行业的快速发展&#xff0c;市场上的软件产品和服务越来越多&#xff0c;竞争也越来越激烈。为了在市场上保持竞争力&#xff0c;软件企业可能不得不降低价格&#xff0c;这直…

【word密码】word怎么限制格式,但可以修改文字?

想要限制word文件中文字的格式&#xff0c;但是又希望别人能够删除、输入文字&#xff0c;想要实现这种设置我们可以对word文件设置限制编辑。 点击word文件工具栏中的审阅 – 限制编辑&#xff0c;勾选上【限制对选定的样式设置格式】 然后在弹出的提示框中&#xff0c;输入我…

LDRA Testbed(TBrun)软件单元测试_常见问题及处理

系列文章目录 LDRA Testbed软件静态分析_操作指南 LDRA Testbed软件静态分析_自动提取静态分析数据生成文档 LDRA Testbed软件静态分析_Jenkins持续集成&#xff08;自动静态分析并用邮件自动发送分析结果&#xff09; LDRA Testbed软件静态分析_软件质量度量 LDRA Testbed软件…

太爱这5本书了,建议所有大模型人去翻烂它❗

要说现在最热门的技术&#xff0c;可谓非大模型莫属&#xff01; 不少小伙伴都想要学习大模型技术&#xff0c;转战AI领域&#xff0c;以适应未来的大趋势&#xff0c;寻求更有前景的发展~~ 然而&#xff0c;在学习大模型技术这条道路上&#xff0c;却不知道如何进行系统的学…

DQL学习

一、基础查询 1.查询多个字段 select 字段列表 from 表名; select * from 表名;-- 查询所有数据 但不建议使用&#xff01;&#xff01;&#xff01;&#xff01; 2.去除重复记录 select DISTINCT 字段列表 from 表名; 3.起别名 as&#xff1b;as也可以省略但中间要加空…

关于宝塔PHP getenv无法获取环境变量问题解决办法

今天有用ThinkPHP8接入阿里云OSS时&#xff0c;需要用的用到getenv()来读取环境变量&#xff0c;因为新版OSS SDK是用环境变更来设置AK的。 现象 正常执行PHP文件&#xff0c;可以取到环境变量&#xff1b;但是通过nginxphp-fpm调用脚本取到不到环境变量 原因 php-fpm为了防止…

车辆检测系统源码分享

车辆检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vision …

算法4-----综合训练(4)

一&#xff1a;单词搜索 题目&#xff1a; 给定一个m*n的二位字符网格和一个字符串单词。如果单词存在于网格中&#xff0c;返回true&#xff0c;不然&#xff0c;返回false。 注意&#xff1a;单词必须按照字母顺序&#xff0c;通过相邻的单元格内的字母构成&#xff0c;同…

停车场管理系统的设计与实现

摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统停车场管理系统信息管理难度大&#xff0c;容错率低&…

使用豆包Marscode 创建了一个”天气预报“小应用

以下是「豆包MarsCode 体验官」优秀文章&#xff0c;作者一拳干爆显示器。 前言 本文介绍了我第一次使用我在MarsCode IDE制作了一款天气预报的应用 其中在正文的头部以及结语部分发表了我在MarsCode编程中的体验情况&#xff0c;而正文的中间主要是我项目制作的细节步骤 豆…

SPWM正弦波控制

目录 前言一、PWM简介二、SPWM基本原理2.1 SPWM简介2.2 SPWM控制方法2.2.1 直接计算法2.2.2 自然采样法2.2.3 谐波法 2.3 SPWM的注意点2.3.1 死区效应2.3.2 过调制2.3.3 转矩与转速控制 三、SPWM实现四、补充 前言 本文主要介绍SPWM原理及C语言单片机的实现 一、PWM简介 PWM是P…

Vue 响应式监听 Watch 最佳实践

一. 前言 上一篇文章我们学习了 watch 的基础知识&#xff0c;了解了它的基本使用方法及注意事项&#xff0c;本篇文章我们继续了解在Vue 中 响应式监听 watch 的妙用。了解 watch 的基础使用请参考上一篇文章&#xff1a; 详解 Vue 中 Watch 的使用方法及注意事项https://bl…

ARM单片机的中断详细过程(重要)

ARM单片机的中断详细过程&#xff08;重要&#xff09; 一、ARM异常中断 ARM的异常&#xff08;中断源&#xff09;总共分为三类&#xff08;八种&#xff09;&#xff1a; 三类&#xff1a; &#xff08;1&#xff09;执行指令引起的直接异常&#xff1a;软件中断&#xff…

0920作业+思维导图

一、 写一个shell脚本&#xff0c;将以下内容放到脚本中&#xff1a; 在家目录下创建目录文件&#xff0c;dirdir下创建dir1和dir2把当前目录下的所有文件拷贝到dir1中&#xff0c;把当前目录下的所有脚本文件拷贝到dir2中把dir2打包并压缩为dir2.tar.xz再把dir2.tar.xz移动到…

13.第二阶段x86游戏实战2-动态模块地址

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 本人写的内容纯属胡编乱造&#xff0c;全都是合成造假&#xff0c;仅仅只是为了娱乐&#xff0c;请不要…

LabVIEW项目编码器选择

在LabVIEW项目中&#xff0c;选择增量式&#xff08;Incremental Encoder&#xff09;和绝对式&#xff08;Absolute Encoder&#xff09;编码器取决于项目的具体需求。增量式编码器和绝对式编码器在工作原理、应用场景、精度和成本等方面存在显著差异。以下从多方面详细阐述两…