聊聊ClickHouse向量化执行引擎-过滤操作

news2024/9/27 15:30:19

俄罗斯Yandex开发的ClickHouse是一款性能黑马的OLAP数据库,其对SIMD的灵活运用给其带来了难以置信的性能。本文我们聊聊它如何对过滤操作进行SIMD优化。

基本思想

1、有一个数组data,即ColumnVector::data,存放数据

2、使用uint8类型,即1个字节类型的filter数组:ICloumn::Filter。他的大小是data数组大小,里面存放布尔值,标记data数组里面哪些数据满足过滤条件,应该筛选出来

3、最终生成一个新的数组res,根据filter数组,对data数组进行筛选,满足条件的拷贝到res数组中。标量逻辑的简单伪码:

let res = []
for (let i = 0; i < data.size(); i ++) {
    if (filter[i] != 0) {
        res.append(data[i])
    }
}

Clickhouse如何实现的呢?

4、上面代码耗时因素在于循环次数非常多,等于data数组的大小

5、如果可以降低循环次数,同时保证单次循环耗时变化不大,总体执行效率更高。要满足上面条件,需要在同一个指令周期做更多运算,SIMD指令可以做这样的运算。

6、SIMD指令目前最大支持512位数据,而filter本身一个值为8位,单词循环处理数据量为512 / 8 = 64个

7、每次取出来64个filter数组项(64字节),将其组成一个64位无符号整数值mask,这样每个filter数组项占用一个比特位

8、有两种特殊情况:1)mask 64位比特位都是1,本次循环中,64个data项都应该拷贝到res中。2)mask 64位比特位都是0,可以直接跳过循环。当然,这两种特殊情况经常出现在业务常见中

9、第三中情况是有一部分满足条件,此时是否需要循环64次?有没有进一步的优化方法?看看clickhouse咋处理

10、有多少项需要拷贝到新数组,取决于mask中比特位为1的个数,通过__builtin_clzll内置函数得到入参(64位)二进制表示形式中开头0的个数,从而得到最高比特位为1的索引,继而知道哪项数据需要拷贝。

11、最高1比特位的数据项拷贝后,需要将它置成0,这里有2个比较高效的方法blsr函数:一个是_blsr_u64指令,另一个是mask & (mask-1)

12、循环设置最高1的比特位,直到mask中所有比特位都为0

ColumnVector<T>::filter函数

5c3f99b1e8e69987c0267faf17c846c5.png

过滤函数主要是filter函数。其实分为3部分,AVX512VBMI2指令集、默认的操作和尾部数据处理。其中尾部数据处理是指处理数据不够64个时,剩余的部分处理方式,这种方式无法使用SIMD,沿用标量处理方式。

先看下默认操作方式:doFilterAligned即:模板函数

55d5309fd685b0b74293159ff71467b2.png

b6e51b52b8be5c6db0c52f72ce9b83c9.png 

这部分其实是对有一部分值满足条件场景的优化,主要有3个方面:

1)前导0个数,即data数组data[0]--data[i]都满足条件,需要拷贝到结果中

2)后导0个数,即data数组data[i]--data[63]都满足条件,需要拷贝到结果中

3)其他场景,比如0011 1100的场景,即两边都有不满足条件的,那就需要特殊处理了:计算出最低位起0的个数index,然后将data_pos[index]拷贝到结果中,即该数组满足条件,最后将index位置为0。

前缀和后缀拷贝的判断:

a07b2c33ba508a3991f45ff4d80385cf.png

蓝色框表示的意义:其实是去除前导0后,剩余的都是1,即mask值。也就是从0的索引开始,到64 - leading_zeroes都需要拷贝到结果中。蓝框计算效果,以2个字节大小为例,前导5个0:

4e22202c5a402b73a42f5ece6aa75a59.png

后导0的处理:其实可以调用__buitlin_ctzll函数

uint8_t suffixToCopy(UInt64 mask)
{
  const auto prefix_to_copy = prefixToCopy(~mask);//mask值取反
  return prefix_to_copy >= 64 ? prefix_to_copy : 64 - prefix_to_copy;//需要拷贝个数
}

效果如下图所示:

c27c4e56c4e0665b7119649ab032018b.png

64字节值转换成64位掩码值的计算函数Bytes64MaskToBits64Mask实现也很有讲究:

/// Transform 64-byte mask to 64-bit mask
inline UInt64 bytes64MaskToBits64Mask(const UInt8 * bytes64)
{
#if defined(__AVX512F__) && defined(__AVX512BW__)
    const __m512i vbytes = _mm512_loadu_si512(reinterpret_cast<const void *>(bytes64));
    UInt64 res = _mm512_testn_epi8_mask(vbytes, vbytes);
#elif defined(__AVX__) && defined(__AVX2__)
    const __m256i zero32 = _mm256_setzero_si256();
    UInt64 res =
        (static_cast<UInt64>(_mm256_movemask_epi8(_mm256_cmpeq_epi8(
        _mm256_loadu_si256(reinterpret_cast<const __m256i *>(bytes64)), zero32))) & 0xffffffff)
        | (static_cast<UInt64>(_mm256_movemask_epi8(_mm256_cmpeq_epi8(
        _mm256_loadu_si256(reinterpret_cast<const __m256i *>(bytes64+32)), zero32))) << 32);
#elif defined(__SSE2__)
    const __m128i zero16 = _mm_setzero_si128();
    UInt64 res =
        (static_cast<UInt64>(_mm_movemask_epi8(_mm_cmpeq_epi8(
        _mm_loadu_si128(reinterpret_cast<const __m128i *>(bytes64)), zero16))) & 0xffff)
        | ((static_cast<UInt64>(_mm_movemask_epi8(_mm_cmpeq_epi8(
        _mm_loadu_si128(reinterpret_cast<const __m128i *>(bytes64 + 16)), zero16))) << 16) & 0xffff0000)
        | ((static_cast<UInt64>(_mm_movemask_epi8(_mm_cmpeq_epi8(
        _mm_loadu_si128(reinterpret_cast<const __m128i *>(bytes64 + 32)), zero16))) << 32) & 0xffff00000000)
        | ((static_cast<UInt64>(_mm_movemask_epi8(_mm_cmpeq_epi8(
        _mm_loadu_si128(reinterpret_cast<const __m128i *>(bytes64 + 48)), zero16))) << 48) & 0xffff000000000000);
#elif defined(__aarch64__) && defined(__ARM_NEON)
    const uint8x16_t bitmask = {0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80};
    const auto * src = reinterpret_cast<const unsigned char *>(bytes64);
    const uint8x16_t p0 = vceqzq_u8(vld1q_u8(src));
    const uint8x16_t p1 = vceqzq_u8(vld1q_u8(src + 16));
    const uint8x16_t p2 = vceqzq_u8(vld1q_u8(src + 32));
    const uint8x16_t p3 = vceqzq_u8(vld1q_u8(src + 48));
    uint8x16_t t0 = vandq_u8(p0, bitmask);
    uint8x16_t t1 = vandq_u8(p1, bitmask);
    uint8x16_t t2 = vandq_u8(p2, bitmask);
    uint8x16_t t3 = vandq_u8(p3, bitmask);
    uint8x16_t sum0 = vpaddq_u8(t0, t1);
    uint8x16_t sum1 = vpaddq_u8(t2, t3);
    sum0 = vpaddq_u8(sum0, sum1);
    sum0 = vpaddq_u8(sum0, sum0);
    UInt64 res = vgetq_lane_u64(vreinterpretq_u64_u8(sum0), 0);
#else
    UInt64 res = 0;
    for (size_t i = 0; i < 64; ++i)
        res |= static_cast<UInt64>(0 == bytes64[i]) << i;
#endif
    return ~res;
}

我们看到,按照优先级尽可能利用位数多的指令集进行计算,同时在所有指令集都不可用及未64字节对齐(align)的部分进行了保底处理。其利用了以下指令集:

    AVX512F / AVX512BW

    AVX/AVX2

    SSE2

其中,_mm512_testn_epi8_mask函数功能:计算a和b两个入参值按8位整数逐位与(AND),产生中间8位值,如果中间值为0,则在结果掩码k中设置相应位:

FOR j := 0 to 63

  i := j*8

  k[j] := ((a[i+7:i] AND b[i+7:i]) == 0) ? 1 : 0

ENDFOR

所以,bytes64MaskToBits64Mask最终计算出的值需要取反。另外,其他指令集,比如AVX下,_mm256_cmpeq_epi8比较32位是否等于0,等于0表示不满足条件,当然等于零时该函数返回0xFF,所以同样最终结果需要取反。

另外一种操作方式:doFilterAligned即:模板函数

ccc5bcb83e9300ca8ff890cec96a54cf.png

主要是通过_mm512_mask_compressstoreu_epi8类似函数分别对1、2、4、8字节长度类型针对掩码进行数据拷贝,这里不再赘述。

参考

https://zhuanlan.zhihu.com/p/454657709

https://blog.csdn.net/u010002184/article/details/113977586

https://blog.51cto.com/u_15103025/2643507

https://zhuanlan.zhihu.com/p/449154820

https://www.zhihu.com/question/450069375/answer/1813516193

https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html

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

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

相关文章

Metasploit渗透测试:工作区使用帮助

目录 ​编辑 help 查看工作区 添加工作区 显示工作区详情

《智能手机心率和呼吸率测量算法的前瞻性验证》阅读笔记

目录 一、论文摘要 1.背景 2.方法 3.结果 4.结论 二、论文十问 Q1&#xff1a;论文试图解决什么问题&#xff1f; Q2&#xff1a;这是否是一个新的问题&#xff1f; Q3&#xff1a;这篇文章要验证一个什么科学假设&#xff1f; Q4&#xff1a;有哪些相关研究&#xff…

No.046<软考>《(高项)备考大全》【专项2】《案例分析 - 计算题(中)》

案例分析 - 计算题&#xff08;中&#xff09; 章节其他部分3 成本管理3.1 挣值分析3.1.1 概念3.1.2 公式3.1.3 参数关系3.1.4 题目 3.2 挣值管理3.3 预测3.3.1 ETC(完成尚需估算)3.3.2 EAC(完工估算)3.3.3 BAC(完工预算) 3.4 绩效审查 章节其他部分 案例分析 - 计算题&#x…

27从零开始学Java之详解复杂的二维数组与多维数组

作者&#xff1a;孙玉昌&#xff0c;昵称【一一哥】&#xff0c;另外【壹壹哥】也是我哦 千锋教育高级教研员、CSDN博客专家、万粉博主、阿里云专家博主、掘金优质作者 前言 在前几篇文章中&#xff0c;壹哥给大家介绍了Java里的一维数组&#xff0c;涉及到了数组的创建初始化…

黑马程序员Dubbo快速入门,Java分布式框架dubbo教程

分布式系统中的相关概念 &#xff08;一&#xff09;互联网项目架构目标-特点 &#xff08;二&#xff09;互联网项目架构目标-目标 &#xff08;三&#xff09;集群和分布式 &#xff08;四&#xff09;架构演进 Dubbo概述 &#xff08;一&#xff09;dubbo概述 1、dub…

Oracle 修改 sga_target 参数设置,虚拟内存值设置

记录一次Oracle数据库单实例维护里程。 数据库启动报错 sga_target参数设置太小&#xff0c;导致数据库无法启动&#xff0c;这种情况下需要修改数据库的spfile的sga_target参数 修复过程如下。 1、启动报错 2、根据spfile 创建pfile 文件。 create pfile‘/app/oracle/i…

项目经理在项目中是什么角色?

有人说&#xff0c;项目经理就是一个求人的差事&#xff0c;你是在求人帮你做事。 有人说&#xff0c;项目经理就是一个与人扯皮的差事&#xff0c;你要不断的与开发、产品、测试等之间沟通、协调。 确实&#xff0c;在做项目的时候&#xff0c;有的人是为了完成功能&#x…

MYSQL基本操作(增删改查)

数据库的列类型 int&#xff1a;整型 用于定义整数类型的数据 float&#xff1a;单精度浮点4字节32位 准确表示到小数点后六位 double&#xff1a;双精度浮点8字节64位 char&#xff1a;固定长度的字符类 用于定义字符类型数据&…

防抖节流(回顾)

防抖定义应用场景&#xff1a;实现思路&#xff1a;_.debounce 源码 节流定义应用场景&#xff1a;实现思路&#xff1a;_.throttle 防抖和节流的区别scroll 事件下的区别mousemove 事件下的区别 防抖 定义 强制函数在固定时间只执行一次&#xff0c;多余执行无效 应用场景&…

Node 10 接口

接口 简介 接口是什么 接口是 前后端通信的桥梁 简单理解&#xff1a;一个接口就是 服务中的一个路由规则 &#xff0c;根据请求响应结果 接口的英文单词是 API (Application Program Interface)&#xff0c;所以有时也称之为 API 接口 这里的接口指的是『数据接口』&#…

企业如何通过CRM系统有效触达客户,获取潜在商机

“守株待兔”式坐等客户上门的时代了已经过去了&#xff0c;尤其是在存量时代&#xff0c;企业想要提高销售&#xff0c;扩大客源&#xff0c;就要不断的通过各种渠道来去拓展自己的客户和销路&#xff0c;而互联网时代&#xff0c;获客的渠道也丰富多样&#xff0c;企业选择好…

权威重磅:全球区块链专利状况研究

文 | 国家知识产权局知识产权发展研究中心 区块链&#xff08;Blockchain&#xff09;是一种安全共享的去中心化的数据账本。近年来&#xff0c;区块链与大数据、云计算、人工智能、5G等新一代信息技术快速融合发展&#xff0c;应用已延伸到数字金融、物联网、智能制造、供应链…

Java面试题总结 | Java面试题总结11- Spring模块(持续更新)

Spring 文章目录 Spring什么是SpringSpringMVC、Spring、SrpingBoot的区别SSM和SpringBoot的区别IOC容器Spring Boot Starter有什么用SpringBoot启动原理说说你对AOP的理解spring用到的设计模式Spring是怎么解决循环依赖的&#xff1f;说说你对MVC的理解spring mvc执行流程什么…

CRM系统云部署和本地部署的优缺点有哪些

CRM客户管理系统有两种部署方式&#xff0c;分别是本地部署和云端部署。每一种部署方式都有相应的优势和劣势。下面我们就来说说&#xff0c;CRM本地部署和CRM云部署有哪些区别&#xff1f; 本地部署 CRM本地部署是指将CRM软件安装在企业自己的服务器上&#xff0c;这些服务器…

做电商数据分析可视化,这个国产BI软件很香

电商数据分析的数据采集整合工作量大&#xff0c;对实时性要求高&#xff0c;特别是跨境电商物流周期长不利于做库存计划不说&#xff0c;还容易出现运营、物流、财务、生产信息脱节等情况。难&#xff0c;但难不倒国产BI软件。在国内外BI软件中&#xff0c;国产BI软件明显更具…

php用一个单页读取数据库中带有超链接的内容并提供人工清理链接的功能(超链接部分可替换为任何查询条件)/ 代码拿去用

起因 某客户安全报告提示有一个过期网站的链接&#xff0c;且该过期网站变成了羞羞网站~~这个问题是他们平时发文的时候直接复制了别的网站内容&#xff0c;又没有用编辑器的清理工具导致的。事后&#xff0c;客户提出能不能把所有内容都查一遍&#xff0c;并去掉所有链接&…

风控系统就该这么设计(万能通用),那是相当稳定

一、背景 1.为什么要做风控? 2.为什么要自己写风控? 3.其它要求 二、思路 1.风控规则的实现 2.调用方式的实现 三、具体实现 1.风控计数规则实现 2.注解的实现 四、测试一下 1.写法 2.Debug看看 一、背景 1.为什么要做风控? 这不得拜产品大佬所赐&#xff0c;我们…

分享:集群吞吐量以1抵5,车企MySQL八大痛点的解决方案

本文来自社区分享&#xff0c;仅限交流探讨。原文作者&#xff1a;李婵玲&#xff0c;某智能车企DBA。欢迎访问 OceanBase 官网获取更多信息&#xff1a;https://www.oceanbase.com/ 最近一年&#xff0c;我们完成了从MySQL到OceanBase的替代过程&#xff0c;既降低了架构复杂度…

PostgreSQL 基础知识:psql 入门

PostgreSQL 有一个单独的命令行工具psql&#xff0c;该工具已经使用了几十年&#xff0c;并且包含在任何 PostgreSQL 安装中。许多 PostgreSQL 的长期用户、开发人员和管理员都依赖它来帮助他们快速连接到数据库、检查模式和执行 SQL 查询。 了解如何安装和使用基本psql命令是…

【每日一题】

文章目录 C 技术点多边三角形剖分的最低得分&#xff08;dp思路&#xff0c;选不选问题&#xff09;移动石子到连续&#xff08;思路&#xff09; C 技术点 1. string类型使用find函数。 int index s.find(""); if (inde ! string:npos){ xx }2. transform函数&…