筛子排序(SieveSort)

news2024/12/22 22:25:25

当你手头有了支持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 >=2
		mask = (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.coloradmin.cn/o/2158738.html

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

相关文章

How do you send files to the OpenAI API?

题意&#xff1a;你如何向 OpenAI API 发送文件 问题背景&#xff1a; For fun I wanted to try to make a tool to ask chatgpt to document rust files. I found an issue, in that the maximum message length the API allows seems to be 2048 characters. 为了好玩&…

LLMs之PE:AI for Grant Writing的简介、使用方法、案例应用之详细攻略

LLMs之PE&#xff1a;AI for Grant Writing的简介、使用方法、案例应用之详细攻略 目录 AI for Grant Writing的简介 AI for Grant Writing的使用方法—提示资源 1、提示集合 2、提示工程 3、快速提示 为了提高文本清晰度 为了让文本更有吸引力 为了改进文本的结构和流…

QT窗口无法激活弹出问题排查记录

问题背景 问题环境 操作系统: 银河麒麟V10SP1qt版本 : 5.12.12 碰见了一个问题应用最小化,然后激活程序窗口无法弹出 这里描述一下代码的逻辑,使用QLocalServer实现一个单例进程,具体的功能就是在已存在一个程序A进程时,再启动这个程序A,新的程序A进程会被杀死,然后激活已存…

MFC - 复杂控件_1

前言 各位师傅大家好&#xff0c;我是qmx_07&#xff0c;今天给大家讲解复杂控件的相关知识点 复杂控件 进度条 绘图准备: 调整windows窗口大小、设置 Progress Control 进度条设置Button 按钮 添加进度条变量 m_Progress,通过按钮触发 void CMFCApplication2Dlg::OnBnCl…

基于JAVA+SpringBoot+Vue的景区民宿预约系统

基于JAVASpringBootVue的景区民宿预约系统 前言 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN[新星计划]导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末附源码下载链接&#x1f345; 哈…

Django 数据库配置以及字段设置详解

配置PostGre 要在 Django 中配置连接 PostgreSQL 数据库&#xff0c;并创建一个包含“使用人”和“车牌号”等字段的 Car 表 1. 配置 PostgreSQL 数据库连接 首先&#xff0c;在 Django 项目的 settings.py 中配置 PostgreSQL 连接。 修改 settings.py 文件&#xff1a; …

C++ set 和 map学习

一、set(multiset)的基本知识和使用 set也是一种我们直接可以使用的容器&#xff0c;使用应该包含 #include <set> 这个头文件。此处暂且不讨论其底层&#xff0c;只探讨set如何使用即可。 我们看到&#xff0c;set 的模板参数有三个&#xff0c;第一个就是其存储的数据…

Python学习——【4.6】数据容器:dict 字典、映射

文章目录 【4.6】数据容器&#xff1a;dict 字典、映射一、字典的定义二、字典的常用操作三、字典的遍历四、字典的特点 【4.6】数据容器&#xff1a;dict 字典、映射 一、字典的定义 为什么使用字典 以生活中的新华字典举例。它的功能是&#xff0c;让我们通过【字】&#x…

Linux基础4-进程2(Linux中的进程状态,R,S,D,T,t,Z,X,僵尸进程,孤儿进程)

上篇文章&#xff1a;Linux基础4-进程1&#xff08;操作系统&#xff0c;进程介绍&#xff0c;Linux进程相关命令&#xff0c;getpid&#xff0c;fork&#xff09;-CSDN博客 本章重点&#xff1a; 进程状态相关知识 目录 1. 进程常见的状态 2.普遍的操作系统理解进程状态 3.…

c++263抽象类在继承中的应用

#include<iostream> using namespace std; //计算程序员工资 //1.要求能计算出junior_programmmer mid-programer adv-programmer的工资 //2.要求利用抽象类统一界面 方便程序的扩展 ex&#xff1a;新增计算架构师architect的工资class programmer { public:virtual void…

CTFHub技能树-SQL注入-Cookie注入

使用bp发现cookie的注入点 id1&#xff0c;发现为数字型 首先使用联合查询 id 1 order by 2 id 1 order by 3发现2的时候有回显&#xff0c;而3的时候无回显 Cookie: id-1 union select database(),user() 后面开始库->表->列->数据 Cookie: id-1 union select 1…

WebLogic文件任意上传漏洞CVE-2018-2894

1.环境搭建 cd vulhub-master/weblogic/CVE-2018-2894 docker-compose up -d 2.获取环境后台密码 docker-compose logs | grep password 3.开启web服务测试 设置web服务测试开启:域结构->base-domain->高级 ->启动Web服务测试页 4.修改访问目录 先进入/ws_utc/co…

Java基础知识扫盲

目录 Arrays.sort的底层实现 BigDecimal(double)和BigDecimal(String)有什么区别 Char可以存储一个汉字吗 Java中的Timer定时调度任务是咋实现的 Java中的序列化机制是咋实现的 Java中的注解是干嘛的 Arrays.sort的底层实现 Arrays.sort是Java中提供的对数组进行排序的…

每日一练:对称二叉树

101. 对称二叉树 - 力扣&#xff08;LeetCode&#xff09; 一、题目要求 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 示例 1&#xff1a; 输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;true示例 2&#xff1a; 输入&#xff1a;root [1,2,2,n…

对象【JavaScript】

在JavaScript中&#xff0c;对象是一种复合数据类型&#xff0c;它使用花括号 {} 包裹一组键值对。每个键&#xff08;属性名&#xff09;后面跟着一个冒号 : 和对应的值。键通常是字符串&#xff08;或符号&#xff09;&#xff0c;而值可以是任意数据类型。 1. 对象字面…

数据脱敏-快速使用

1.数据脱敏定义 数据脱敏百度百科中是这样定义的&#xff1a; 数据脱敏&#xff0c;指对某些敏感信息通过脱敏规则进行数据的变形&#xff0c;实现敏感隐私数据的可靠保护。 因为在真正的生产环境中,很多数据是不能直接返回,但是我们工作的时候可能经常性的需要返回一些用户信…

公司将被千万美金收购,工程师却误删数据库 —— 没 有 备 份!!!

前些天&#xff0c;Retention 和 RB2B 的 CEO&#xff0c;Adam Robinson&#xff0c;在领英&#xff08;LinkedIn&#xff09;发帖讲了一个恐怖故事。 2021 年 3 月&#xff0c;在他第一个创业公司即将以一千万美金被收购的两周前&#xff0c;他们的一位工程师不小心删除了整个…

M9410A VXT PXI 矢量收发信机,300/600/1200MHz带宽

M9410A PXI 矢量收发信机 -300/600/1200MHz带宽- M9410A VXT PXI 矢量收发信机&#xff0c;300/600/1200MHz带宽支持 5G 的 PXI 矢量收发信机&#xff08;VXT&#xff09;是一个 2 插槽模块&#xff0c;具有 1.2 GHz 的瞬时带宽 主要特点 Keysight M9410A VXT PXIe 矢量收发…

SpringBoot+Vue技术框架开发的ADR智能监测系统源码,Java语言的药品不良反应智能监测系统源代码

系统概述&#xff1a; 药品不良反应是指合格药品在正常用法用量下出现的与用药目的无关的有害反应。药品不良反应智能监测系统是一种用于监测和收集药品在使用过程中发生的不良反应的系统。它基于医院临床数据中心&#xff0c;运用信息技术实现药品不良反应的智能监测、报告管…

46.哀家要长脑子了!

1.435. 无重叠区间 - 力扣&#xff08;LeetCode&#xff09; 方法一&#xff1a;动态规划 实际上本质就是找最长的无重叠子序列&#xff0c;那么我们可以遍历这个区间的集合&#xff0c;只要前一个区间的右端点是小于等于后一个区间的左端点&#xff0c;那么这两个区间就不是重…