当前位置: 首页 > 科技观察

向量化如何提高数据库性能_0

时间:2023-03-15 09:42:48 科技观察

矢量化如何提高数据库性能每个人都明白这一点,但确保用户在不增加额外工作的情况下获得所需速度的最佳方法是什么?作为一名数据工程师,我经常面临这样的挑战。为了找到解决方案,一个研究团队启动了开放项目StarRocks,这是一种分析引擎,可以满足快速增长的分析性能需求,同时也易于使用和维护。随着开放项目和技术社区在过去几年中的发展,人们已经了解了很多关于在性能分析中做什么和不做什么的知识。今天,我分享了一些关于构建高性能分析引擎的关键技术之一的见解:矢量化。为什么矢量化可以提高数据库性能在深入研究StarRocks如何实施矢量化之前,请务必注意,当我们谈论矢量化时,我们是在谈论使用现代CPU架构的数据库的矢量化。有了这样的理解,我们就可以开始回答这个问题了:为什么向量化可以提高数据库性能?要回答这个问题,首先要回答以下几个问题:(1)如何衡量CPU性能?(2)影响CPU性能的因素有哪些?第一个问题的答案可以用这个公式表示:CPUTime=(NumberofInstructions)*CPI*(ClockCycleTime)Instructions=NumberofInstructionsGeneratedbytheCPURequiredCPUcycles时钟周期时间=CPU时钟花费的时间周期此公式提供了一些术语,可用于讨论影响性能的杠杆。由于您对时钟周期时间无能为力,因此您需要关注指令数和CPI以提高软件性能。另外,还有一个需要知道的重要信息是,CPU指令的执行可以分为五个步骤:(1)取(2)译码(3)执行(4)内存访问(5)写回结果(写寄存器)步骤1和步骤2由CPU前端执行,步骤3至步骤5由CPU后端处理。Intel发布了自上而下的微架构分析方法,如下图所示。自上而下的微架构分析方法(英特尔)下面是上述方法的简化版本。可以看出,导致CPU性能问题的主要原因是退役、猜错、前端绑定和后端绑定。这些问题背后的主要驱动因素分别是缺乏SIMD指令优化、分支预测错误、指令缓存未命中和数据缓存未命中。因此,如果将上述原因映射到前面介绍的CPU性能公式中,可以得出以下结论:那么,在这四个方面提高CPU性能的设计是什么?是的,它是矢量化。现在已经确定矢量化可以提高数据库性能。下面解释了矢量化是如何做到这一点的。向量化基础知识如果您已经对向量化有了很好的了解,则可以跳过本节并继续阅读有关数据库向量化的部分,但是如果您不熟悉向量化基础知识,或者可能需要复习一下,那么这里有一个简短的介绍你应该知道的概述。矢量化的讨论仅限于SIMD。SIMD向量化不同于一般的数据库向量化,这将在下面讨论。SIMD简介SIMD的意思是“单指令,多数据”。顾名思义,使用SIMD架构,一条指令可以同时对多个数据点进行操作。在SISD(单指令、单数据)架构中情况并非如此,其中一条指令只能对单个数据点进行操作。如上所述,在SISD架构中,操作是标量的,这意味着只处理一组数据。因此4个加法操作将涉及8个加载操作(每个变量一个)、4个加法操作和4个存储操作。如果使用128位SIMD,则只需要两次加载,一次加法和一次存储。理论上,性能比SISD提高了4倍。考虑到现代CPU已经拥有512位寄存器,预计性能提升可达16倍。如何向量化一个程序?上面已经看到了SIMD向量化是如何大幅度提升程序性能的。那么你如何开始在你自己的工作中使用它呢?调用SIMD的不同方法正如英特尔的这张图所示,有六种调用SIMD的方法。从上到下,每种方法都需要程序员更多的专业知识,并且需要更多的编码工作。方法1.编译器自动矢量化程序员不需要对他们的代码进行任何更改。编译器会自动将标量代码转换为矢量代码。只有一些简单的情况会自动转换为矢量代码。方法二、给编译器的提示在这个方法中,给编译器提供一些提示。通过提供额外的信息,编译器可以生成更多的SIMD代码。方法3.并行编程API借助OpenMP或IntelTBB等并行编程API,开发人员可以添加Pragmas来生成矢量代码。方法4.使用SIMD类库这些库包装了启用SIMD指令的类。方法5.使用SIMD内部函数内部函数是一组汇编编码函数,允许使用c++函数调用和变量而不是汇编指令。方法6.直接写汇编代码1和方法2。对于不能自动转换为向量代码的性能关键操作,使用SIMDintrinsics。验证程序是否真正生成了SIMD代码这里有一个重要的问题,当程序具有复杂的代码结构时,如何保证代码执行是向量化的?有两种方法可以检查和确认代码是否已矢量化。方法1.向编译器添加选项使用这些选项,编译器将生成关于代码是否矢量化的输出,如果不是,则输出原因。例如,您可以向GCC编译器添加--fopt-info-vec-all、-fopt-info-vec-optimized、-fopt-info-vec-missed和-fopt-info-vec-note选项,如如下图所示:方法二、查看执行的汇编代码可以使用https://gcc.godbolt.org/等网站或者Perf、Vtun等工具查看汇编代码。如果汇编代码中的寄存器是xmm、ymm、zmm等,或者指令以v开头,那么就知道代码是向量化的。现在已经掌握了矢量化的基础知识,是时候讨论矢量化数据库提高性能的能力了。数据库矢量化虽然StarRocks项目已经发展成为一个成熟、稳定、行业领先的MPP数据库(甚至是CelerData的企业级版本),但社区必须克服许多挑战才能实现它。数据库矢量化是最大的突破之一,也是最大的挑战之一。数据库矢量化挑战根据经验,对数据库进行矢量化要比简单地在CPU中启用SIMD指令复杂得多。这是一个庞大的系统工程。具体而言,它面临六大技术挑战:(1)端到端的柱状数据。数据需要以列格式跨存储、网络和内存层进行存储、传输和处理,以消除“阻抗失配”。需要重新设计存储引擎和查询引擎以支持列式数据。(2)所有运算符、表达式和函数都必须向量化。这是一项艰巨的任务,需要数年时间才能完成。(3)运算符和表达式应尽可能调用SIMD指令。这需要详细的逐行优化。(4)内存管理。为了充分利用SIMDCPU的并行处理能力,必须重新考虑内存管理。(5)新的数据结构。连接、聚合、排序等核心运算符中使用的所有数据结构都需要从头开始矢量化。(6)系统优化。StarRocks的目标是与其他市场领先的产品(具有相同的硬件配置)相比实现5倍的性能提升。为了实现这一目标,有必要确保数据库系统中的所有组件都得到优化。向量化运算符和表达式在对StarRocks进行向量化时,大部分工程工作都花在了向量化运算符和表达式上。这些工作可以概括为基于列的批处理计算,如下图所示:对应于本文前面讨论的英特尔自上而下的微架构分析方法,批处理减少了分支预测错误和指令缓存未命中。每列减少数据缓存未命中并使调用SIMD优化更容易。实现批量计算相对容易。困难的部分是对连接、聚合、排序和混洗等关键运算符的列处理。在进行列式处理时调用尽可能多的SIMD优化是一个更大的挑战。如何通过数据库矢量化提高数据库性能如上所述,数据库矢量化是一项系统工程练习。在过去几年中,StarRocks的开发过程中实施了数百项优化。以下是需要重点关注的7个最重要的优化领域。高性能第三方库。对于数据结构和算法,有很多优秀的开源库。StarRocks使用了很多第三方库,比如ParallelHashmap、Fmt、SIMDJson、HyperScan。数据结构和算法。高效的数据结构和算法可以将CPU周期减少一个数量级。正因为如此,StarRocks2.0发布时,引入了一个低基数的全局字典。使用这个全局字典,可以将基于字符串的操作转换为基于整数的操作。如下图所示,通过操作将两个基于字符串的组转换为一个基于整数的组。结果,扫描、散列、等于和mumcpy等操作的性能提高了很多倍,整体查询性能提高了300%以上。自适应优化。如果能够了解查询场景,就可以进一步优化查询执行。但是,查询上下文信息通常在执行之前不可用。因此,查询引擎必须根据查询执行过程中获得的上下文信息动态调整其策略。这称为自适应优化。下面的代码片段显示了一个基于选择性动态选择连接运行时过滤器的示例:指导上述示例的三个决策点:(1)如果一个过滤器不能过滤大部分数据,那么它将不会被使用。(2)如果一个过滤器可以过滤几乎所有的数据,那么我们只保留这个过滤器。(3)最多预留三个过滤器。SIMD优化。如下图所示,StarRocks在其运算符和表达式实现中做了很多SIMD优化。C++低级优化。即使使用相同的数据结构和算法,不同的C++实现的性能可能会有所不同。例如,可以使用移动或复制操作,可以保留向量,或者可以内联函数调用。这些只是一些必须考虑的优化。内存管理优化。batchsize越大,并发度越高,内存分配和释放越频繁,内存管理对系统性能的影响越大。借助StarRocks,实现了列池数据结构以重用列的内存并显着提高查询性能。以下代码片段显示了HLL(HyperLogLog)聚合函数内存优化。通过按块分配HLL内存并重用这些块,HLL的总体性能提高了五倍。CPU缓存优化。CPU缓存未命中对性能有巨大影响。这种影响可以从CPU周期的角度来理解。L1访问缓存需要3个CPU周期,L2访问缓存需要9个CPU周期,L3访问缓存需要大约40个CPU周期,主存访问缓存需要大约200个CPU周期。在调用SIMD优化并且性能瓶颈从CPU绑定转移到内存绑定后,CPU缓存未命中成为一个特别重要的因素。以下代码片段显示了如何通过预取减少CPU损失。不过这里要指出,预取应该是优化CPU缓存的最后手段。这是因为很难控制何时预取以及预取多远。回顾与反思既然我们已经踏上了StarRocks数据库矢量化的旅程,那么让我们回顾一下我们所学的内容。不同系统的基本原理是相似的。当我开始研究CPU的微架构时,我意识到CPU的架构和数据库的架构有相似之处。以StarRocks为例,前端管理SQL解析和查询计划,后端负责SQL执行和与存储层的交互。研究的系统和架构越多,对系统级相似性的理解就越好。构建高性能数据库不仅需要设计良好的模式,还需要密切关注工程细节。虽然良好的设计和良好的工程似乎都是显而易见的需求,但其中之一在数据库产品中往往缺失。如果你真的相信两者,你就不会只使用自下而上的方法(从算法和唯一的组件开始)来设计你的数据库,而不实施确保所有这些组件都能很好地协同工作的高级架构。不能选择Java或Go等编程语言来实现查询执行引擎和存储引擎,但可以使用C++等性能更强的语言。混合矢量化和编译。矢量化和编译是查询执行的两种主要方式,但它们并不相互排斥。尽管大多数开源数据库选择使用矢量化,但可以利用查询编译从查询执行期间获得的信息生成更高效的矢量代码。同时,查询编译不断改进。尝试GPUu和FPGA等新硬件。经过大量优化后,您可能已经接近CPU优化的收益递减点。可以考虑使用额外的新硬件来进一步提高StarRocks的性能。随着数据量的增长、数据源的扩展和用户期望的提高,数据工程师的角色在未来几年只会变得越来越重要。借助像StarRocks这样的项目和像数据库矢量化这样的创新,您遇到的任何性能需求都可以得到满足。原标题:向量化如何提升数据库性能,作者:JamesLi,KaisenKang