暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Vast+产品展厅 | Vastbase G100性能优化技术介绍之【number数据类型】

海量数据 2023-06-01
1321




PostgreSQL的numeric类型,用于大范围和高精度的小数存储和运算。当bigint、double precision等数值类型的范围或者精度无法满足使用需求时,就可以使用numeric类型。然而,测试显示numeric的比较、运算操作非常慢。本文将介绍Vastbase引入的number数据类型及其实现原理,相比numeric类型,其比较和运算性能得到较大的提升。



 

PostgreSQL的numeric类型


在介绍Vastbase的number类型之前,我们先介绍一下PostgreSQL的numeric类型。浮点数的通用表示方式为:


  符号(sign),s决定这是负数(s=1)还是正数(s=0)
 底数(base),为该小数的进制。numeric的底数为10000,即numeric是10000进制表示的浮点数
 尾数(significand),M是一个BASE进制的小数,为了方便比较和运算,我们通常把尾数规范化为1到BASE之间的一个小数。M决定了一个浮点数可以取到多少个有效数字,即数值的精度。精度越高,需要占用的存储空间就越大。这个设计使numeric的最大精度非常高,M值仅受TOAST字段大小的限制,最多可以使用1GB空间,取到2147483646个有效数字。
 阶码(exponent),又称权重(weight),E的作用是对浮点数进行加权,权重是底数的E次幂。E和BASE的值决定了浮点数的表示范围,BASE值一定的情况下,E值占用的空间越多,则浮点数能表示的范围越大。


PostgreSQL为了节省存储空间,并兼顾其最大表示范围,把numeric分成NumericShort和NumericLong两种存储格式。NumericShort的表示范围:。NumericLong的表示范围高达:。小范围的数值使用NumericShort格式存储,可以节省存储空间。当数值的表示范围超过NumericShort的范围时,PostgreSQL把数值切换为NumericLong格式进行存储,以占用2字节的代价换取更大的数值表示范围。但同时,这种实现方式增加了运算和比较的复杂度,使性能降低。


 特殊值,numeric除了可以表示普通的数值外,还可以表示NAN、

  +INF、-INF这些特殊值,比较和运算时,需要判断numeric是否为特殊

  值,大量的if分支判断增加比较和运算的开销,性能下降。


    代码片段1:
    /*在header里设置了相应的特殊值,则浮点数可以表示NAN、+INF、-INF这些特殊值*/
    #define NUMERIC_IS_NAN(n) ((n)->choice.n_header == NUMERIC_NAN)
    #define NUMERIC_IS_PINF(n) ((n)->choice.n_header == NUMERIC_PINF)
    #define NUMERIC_IS_NINF(n)  (n)->choice.n_header == NUMERIC_NINF)


    对numerc有了一定的认识之后,可以总结出numeric的一些优缺点:

    01

     

    精度高,几乎没有限制。但同时,精度越高,需要占用存储空间就越大,运算时,需要在堆上申请一次内存,增加其运算开销。


      代码片段2:
      /*由于numeric最多可以占用1G空间,中间结果无法在栈上分配内存
      *需要在堆上申请足够的内存来保存中间结果,运算完成后,又要释放其中间结果
      *频繁申请和释放内存,增加了numeric运算的开销 */
      Numeric numeric_add(NumericVar arg1, NumericVar arg2)
      {
      add_var(&arg1, &arg2, &result); *需要在堆上申请内存保存中间结果*/
      res = make_result_opt_error(&result);
      free_var(&result); /*释放中间结果的内存*/
      return res;
      }

      02

        

      小范围的数值使用NumericShort格式存储,可以节省存储空间。超出NumericShort的表示范围后,PostgreSQL自动把存储格式转为NumericLong,可以表示更大范围的数值,用户无需关注numeric的底层实现,同时兼顾节省存储空间和表示范围,使用非常灵活。然而,因为有两种不同的存储格式,使运算和比较操作变得复杂,运算开销增大,性能下降。

        代码片段3:
        /*因为numeric有两种存储格式,无法直接进行运算和比较
        *计算开始前,需要先从存储格式转为内存格式
        *计算完成后,根据数值的范围,选择一种合适的存储格式,又把内存格式转为该存储格式
        *这些转换操作,增加了numeric的运算和比较的开销,导致性能下降
        */
        Numeric numeric_add(Numeric num1, Numeric num2)
        {
        NumericVar arg1;
        NumericVar arg2;
        init_var_from_num(num1, &arg1); *num1从存储格式转为内存格式*/
        init_var_from_num(num2, &arg2); *num2从存储格式转为内存格式*/
          add_var(&arg1, &arg2, &result); /*使用内存格式进行运算*/
        res = make_result(&result); *把结果从内存格式转换为合适的存储格式*/
        return res;
        }

        03

         

        比较操作的实现较为繁琐,NAN、+INF、0、-INF这些特殊值之间的比较问题,需要通过大量的if分支判断实现,大量的分支判断增加CPU开销。

        总的来说,numeric的优点是高精度、大范围、易用性强,但因为前面提及的原因,导致其运算和比较都不高效。那么,Vastbase的number类型应该如何解决这些问题,提高性能?

         

        Vastbase的number类型



        为了解决numeric遇到的问题,我们从下以下几点出发:

        01

        限制其最大精度,使数值运算的中间结果可以保存在栈上,避免保存运算的中间结果时,频繁地在堆上申请和释放内存。


        Vasebase在设计number类型时,参考了Oracle的number类型的精度和表示范围,以满足Oracle的兼容性要求,同时能满足大部分场景的精度和范围要求。

        02

        统一存储格式,比较和运算时,直接使用存储格式,减少其比较和运算的复杂度。

        03

        降低排序和比较的复杂度,优化复杂的if分支判断,降低CPU流水线分支预测错误带来的性能损失。

        04

        number和numeric可以非常高效的互换,当numeric的性能不能满足要求时,用户可以用较低的代价把numberic转为number;当number类型的精度和范围不能满足要求时,用户也可以用较低的代价把number转为numeric,降低转换成本。

        根据上面提到的几点,Vastbase重新设计了number类型的内存布局、排序规则。我们先来看看number的内存布局。


        number类型的内存布局




        BASE10000,即number10000进制的小数,这一点可以保证
           numbernumeric的互转是非常高效的。
         scale,控制number的有效位数,全是1时(0xff)特殊用途,表示
           NAN,即非法值,其余scale表示该number是一个合法值。
         sign,符号位,1表示正数,0表示负数。
         E,阶码,占7位,可以取到128个不同的值。

        我们规定:E为-64时,表示该number的值为0,E为+63时,表示该number的值为无穷大,其余E值用于表示普通小数,则普通小数的E值取值范围为:[-63, +62]。后面我们将介绍,这个设计使得number的比较操作变得非常简洁。


         M,尾数,根据数值的精度要求进行存储,精度越高,占用字节数越多,最多可以占用20个字节。


        我们规定,尾数规范化为[1, 10000) 之间的一个小数。规范化操作非常重要,考虑符号相等、E值不等的两个数值:,M值已经规范化,我们通过比较E值的大小,即可得出两个数值的大小关系,从而避免复杂度较高的M值比较。


        下表列出各个浮点数类型的最大精度、表示范围、占用空间等指标,以作对比。


        可以看出Vastbase的number具备以下特点:

        1

        范围和精度均比Oracle number类型大,可以满足Oracle兼容性要求,满足大部分场景的使用需求。

        2

        通过限制其最大精度,使得number运算时,不再需要在堆上申请内存保存中间结果,减少了运算的开销。

        3

         统一了存储格式,运算和比较时,可以直接使用存储结构,避免存储格式和内存格式间的互转带来的开销。




        number类型的排序规则

        本节我们将介绍number类型的排序规则,这个规则是提升排序性能的关键。

        Vastbase把sign值和E值,合成一个uint8数值,并且按照特定的规则做偏置,把偏置后的uint8进行存储。比较时,直接比较该uint8的值,即可得出INF、0、正负号、不同E值之间的大小关系,从而推断number的大小关系,Vastbase的number比较逻辑如下:


        1

        比较uint8的值,如果两个uint8大小不相等,则可以直接推断出两个number的大小关系

        2

        uint8的值相等,说明其符号、E值均相等,只需要再比较M值,就可以得出number的大小关系


        可以看出,number的比较逻辑非常简洁,性能大大提升。实现的关键在于,如何偏置上述uint8的数值,使其反映出INF、0、正负号、E值之间的大小关系?

        解决这个问题之前,我们先总结一下number数值的大小关系,从大到小,依次为:


        1

        +INF

        2

        普通正数,E值越大,number越大(注意E值为负数的情况)

        3

         0

        4

        普通负数,E值越小,number越大(注意E值为负数的情况)

        5

        -INF


        uin8的偏置规则如下:


        1

        对于所有number,E值+64,记作v。我们把+64称为偏置值,这条规则保证所有E值偏置后都大于等于0。

        2

        对于所有number,v = v + 128(等价于v值与sign值取或运算,注意规则3对负数取反码,正好抵消了+128这个操作),这条规则保证了所有正数比负数都大

        3

        如果number为负数,v = 255 - v(等价于uint8按位取反码),这条规则保证了所有负数的number,v值越大,number的值越大。


        下面我们通过归纳法,证明v值越大,number的值越大:


        ● +INF,E偏置前取值为+63,根据规则,算出来v值为255,为uint8里最大的整数。
        ● 普通正数,E值偏置前取值范围为[-63, +62],根据规则,算出来v的范围为:[129,254],小于+INF,随着E值的增大,v值增大,number的数值也在增大。
        ● +0,E偏置前取值为-64,根据规则,算出来v值为128,比所有正数的v值都要小。
        ● -0,E偏置前取值为-64,根据规则,算出来v值为127,比+0小(实际工程实现时,我们为了使+0和-0相等,丢弃了-0这个值)。
        ● 普通负数,E值偏置前取值范围为[-63, +62],根据规则,算出来v值的范围为[1,126],比-0要小。不难发现,随着E值的减少,v值增加,number的数值也在增加。
        ● -INF,E偏置前取值为+63,根据规则,算出来v值为0,为uint8里的最小值,比所有负数都要小。


        综上所述,v值越大,number的值越大,我们可以通过对比v值的大小,推断出number值的大小关系。v值相等的number,则需要对比M值,并根据其正符号推断出number的大小关系。Vastbase通过这个原理,把复杂的number比较运算,转化为uint8、尾数的对比运算,大大简化了对比运算的复杂度,从而提升其性能。

        附表:不同number对应的v值,可以看出,v值越大,number越大。



        性能对比



        --生成测试数据
        create table tbl(a number, b number);
        insert into tbl (a, b) select generate_series(1,10000*10000, 1) + random() ,generate_series(1,20000*10000, 2) + random();
        --普通加减法运算
        explain (verbose, analyze) select a+b, a-b, generate_series(1, 1) from tbl;
        --排序运算
        explain (analyze, verbose) select * from tbl order by a desc limit 1000;
        --聚集运算
        explain (analyze, verbose) select sum(a), sum(b) from tbl;



        运算



        只统计ProjectSet算子,即只包含数值运算,number的运行时间是numeric的69.9%

        numeric: 57.779s

        number: 40.425s



        排序



        只统计Sort算子,即只包含排序运算,number的运行时间是numeric的24.8%
        numeric: 79.823s
        number: 19.806s



        聚集



        只统计Aggregate算子,即只包含聚集运算,number的运行时间是numeric的66.8%
        numeric: 11.246s
        number: 7.521s




        总结:

        测试结果显示,number的运算、聚集、排序的性能均比numeric有较大的提升,其中排序性能提升约4倍。


        图文编辑|程筱淇

        内容审核|市场营销部


        于海量数据

        北京海量数据技术股份有限公司(股票代码:603138.SH)成立于2007年,是国内首家以数据库为主营业务的主板上市企业。公司十余年来秉承“专注做好数据库”的初心,始终致力于数据库产品的研发、销售和服务。核心产品海量数据库Vastbase系列、数据库一体机Vastcube系列,全栈国产化,应用满足度高,目前广泛应用于政务、制造、金融、通信、能源、交通等多个重点行业,已成为国产企业级数据库的首选之一。


        文章转载自海量数据,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

        评论