震精 - PostgreSQL decimal64 decimal128 高效率数值 类型扩展

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
简介:

标签

PostgreSQL , decimal64 , decimal128 , float4 , float8 , numeric


背景

PostgreSQL内置的数值类型包括

整型、浮点、整型序列、"无限"精度数值

Name Storage Size Description Range
smallint 2 bytes small-range integer -32768 to +32767
integer 4 bytes typical choice for integer -2147483648 to +2147483647
bigint 8 bytes large-range integer -9223372036854775808 to +9223372036854775807
decimal variable user-specified precision, exact up to 131072 digits before the decimal point; up to 16383 digits after the decimal point
numeric variable user-specified precision, exact up to 131072 digits before the decimal point; up to 16383 digits after the decimal point
real 4 bytes variable-precision, inexact 6 decimal digits precision
double precision 8 bytes variable-precision, inexact 15 decimal digits precision
smallserial 2 bytes small autoincrementing integer 1 to 32767
serial 4 bytes autoincrementing integer 1 to 2147483647
bigserial 8 bytes large autoincrementing integer 1 to 9223372036854775807

其中除了 "无限"精度数值类型。他类型都是定长存储,使用时不需要调用palloc,效率较高。

如果你要使用超过双精能表示的有效范围的数值,目前只能选择decimal\numeric类型,而这个类型前面说了,由于是变长设计,需要调用palloc,效率一般。

那么在数据分析领域,或者需要处理非常多的数据记录时,numeric类型的开销是较大的。

PostgreSQL社区有一些扩展,可以解决这个问题,

1. 比如2nd的fixeddecimal插件,使用INT8来表示NUMERIC,精度可调。

《PostgreSQL fixeddecimal - 用CPU "硬解码" 提升1倍 数值运算能力 助力金融大数据量计算》

2. 比如社区的pgdecimal插件,支持decimal32和decimal64两种类型。

https://pgxn.org/dist/pgdecimal/1.0.0/

3. 比如vitesse的pgdecimal插件,也就是本文将提到的插件,支持decimal64与decimal128类型,精度基本上足够使用。

推荐使用vitesse提供的pgdecimal插件,因为它效率够高,精度够大。

pgdecimal插件介绍

有两个常见的decimal库,decNumber以及Intel提供的Intel ADX库。

pgdecimal插件选择了decNumber库,因为GCC也在用它(法律风险更小?)

https://github.com/gcc-mirror/gcc/tree/master/libdecnumber

decimal库的性能对比

http://speleotrove.com/decimal/dpintro.html

decNumber与Inter ADX性能接近,但是Inter ADX提供了decimal64/128, int32/64, float/double类型的相互转换,这个很给力。(也许将来vitesse会支持intel adx库吧)

pgdecimal 依赖的decNumber,因此我们必须先安装decNumber

decNumber安装

1. 下载 decNumber package

http://speleotrove.com/decimal/

wget http://speleotrove.com/decimal/decNumber-icu-368.zip  
unzip decNumber-icu-368.zip  

或者从本站链接下载

2. 安装decNumber到postgresql软件目录中(假设postgresql安装在/home/digoal/pgsql9.6)

首先要在postgresql软件的include目录中,创建一个空目录,

mkdir -p /home/digoal/pgsql9.6/include/decnumber  

在decNumber src目录中创建Makefile,install -D 修改为对应要安装的目录。

cd decNumber  
  
vi Makefile  
  
OBJS = decSingle.o decDouble.o decQuad.o decNumber.o decContext.o  
  
CFLAGS = -Wall -g -O2 -fPIC  
  
libdecnumber.a: $(OBJS)  
        ar -rcs libdecnumber.a $(OBJS)  
  
clean:  
        rm -f libdecnumber.a $(OBJS)  
  
install:  
        install -D *.h /home/digoal/pgsql9.6/include/decnumber  
        install -D libdecnumber.a /home/digoal/pgsql9.6/lib  

3. 编译安装decNumber

cd decNumber  
  
make   
make install  

4. decNumber的C库reference如下, pgdecimal插件中用到的decnumber库,需要了解细节的话请参考:

The decNumber C library

pgdecimal安装

git clone https://github.com/vitesse-ftian/pgdecimal  

或者从本站链接下载

cd pgdecimal  

有一个小BUG,.control的版本号没有与sql文件的版本号对齐

mv decimal--2.0.sql decimal--1.0.sql  

另外,需要修改一下Makefile,指定版本,以及decnumber的include和lib目录

vi Makefile  
  
PG_CPPFLAGS = -I/home/digoal/pgsql9.6/include/decnumber  
SHLIB_LINK = -L/home/digoal/pgsql9.6/lib -ldecnumber  
  
DATA = decimal--1.0.sql  

安装

export PATH=/home/digoal/pgsql9.6/bin:$PATH  
USE_PGXS=1 make clean  
USE_PGXS=1 make   
USE_PGXS=1 make install  
  
  
/bin/mkdir -p '/home/digoal/pgsql9.6/lib'  
/bin/mkdir -p '/home/digoal/pgsql9.6/share/extension'  
/bin/mkdir -p '/home/digoal/pgsql9.6/share/extension'  
/usr/bin/install -c -m 755  decimal.so '/home/digoal/pgsql9.6/lib/decimal.so'  
/usr/bin/install -c -m 644 .//decimal.control '/home/digoal/pgsql9.6/share/extension/'  
/usr/bin/install -c -m 644 .//decimal--1.0.sql  '/home/digoal/pgsql9.6/share/extension/'  

使用

psql  
  
postgres=# create extension decimal;  
CREATE EXTENSION  

pgdecimal性能对比

使用int8, float8, decimal64, decimal128, numeric(15,3) 几种类型,分别比较这几种类型的性能。

create table tt(ii bigint, d double precision, d64 decimal64, d128 decimal128, n numeric(15, 3));  
  
postgres=# \timing  
Timing is on.  
  
生成测试数据  
  
postgres=# insert into tt select i, i + 0.123, i + 0.123::decimal64, i + 0.123::decimal128, i + 0.123 from generate_series(1, 1000000) i;  
INSERT 0 1000000  
Time: 2125.723 ms  
  
postgres=# select * from tt limit 2;  
 ii |   d   |  d64  | d128  |   n     
----+-------+-------+-------+-------  
  1 | 1.123 | 1.123 | 1.123 | 1.123  
  2 | 2.123 | 2.123 | 2.123 | 2.123  
(2 rows)  

普通查询性能对比

postgres=# select count(*) from tt where (d + d*d + d*d*d + d*d*d*d) > 10000000;  
 count    
--------  
 999945  
(1 row)  
  
Time: 411.418 ms  
postgres=# select count(*) from tt where (n + n*n + n*n*n + n*n*n*n) > 10000000;  
 count    
--------  
 999945  
(1 row)  
  
Time: 1949.367 ms  
postgres=# select count(*) from tt where (d64 + d64*d64 + d64*d64*d64 + d64*d64*d64*d64) > 10000000;  
 count    
--------  
 999945  
(1 row)  
  
Time: 1165.304 ms  
postgres=# select count(*) from tt where (d128 + d128*d128 + d128*d128*d128 + d128*d128*d128*d128) > 10000000;  
 count    
--------  
 999945  
(1 row)  
  
Time: 1517.179 ms  

排序性能对比

postgres=# select * from tt order by d limit 2 offset 999000;  
   ii   |     d      |    d64     |    d128    |     n        
--------+------------+------------+------------+------------  
 999001 | 999001.123 | 999001.123 | 999001.123 | 999001.123  
 999002 | 999002.123 | 999002.123 | 999002.123 | 999002.123  
(2 rows)  
  
Time: 804.645 ms  
postgres=# select * from tt order by n limit 2 offset 999000;  
   ii   |     d      |    d64     |    d128    |     n        
--------+------------+------------+------------+------------  
 999001 | 999001.123 | 999001.123 | 999001.123 | 999001.123  
 999002 | 999002.123 | 999002.123 | 999002.123 | 999002.123  
(2 rows)  
  
Time: 2828.066 ms  
postgres=# select * from tt order by d64 limit 2 offset 999000;  
   ii   |     d      |    d64     |    d128    |     n        
--------+------------+------------+------------+------------  
 999001 | 999001.123 | 999001.123 | 999001.123 | 999001.123  
 999002 | 999002.123 | 999002.123 | 999002.123 | 999002.123  
(2 rows)  
  
Time: 1826.044 ms  
postgres=# select * from tt order by d128 limit 2 offset 999000;  
   ii   |     d      |    d64     |    d128    |     n        
--------+------------+------------+------------+------------  
 999001 | 999001.123 | 999001.123 | 999001.123 | 999001.123  
 999002 | 999002.123 | 999002.123 | 999002.123 | 999002.123  
(2 rows)  
  
Time: 2118.647 ms  

哈希JOIN性能对比

postgres=# explain select count(*) from tt t1 join tt t2 on t1.d64 * t1.d64 + t1.d64 = t2.d64 + t2.d64 * t2.d64;  
                                    QUERY PLAN                                      
----------------------------------------------------------------------------------  
 Aggregate  (cost=6875071228.00..6875071228.01 rows=1 width=8)  
   ->  Hash Join  (cost=36707.00..5625071228.00 rows=500000000000 width=0)  
         Hash Cond: (((t1.d64 * t1.d64) + t1.d64) = (t2.d64 + (t2.d64 * t2.d64)))  
         ->  Seq Scan on tt t1  (cost=0.00..20300.00 rows=1000000 width=8)  
         ->  Hash  (cost=20300.00..20300.00 rows=1000000 width=8)  
               ->  Seq Scan on tt t2  (cost=0.00..20300.00 rows=1000000 width=8)  
(6 rows)  
  
Time: 0.508 ms  
postgres=# select count(*) from tt t1 join tt t2 on t1.d64 * t1.d64 + t1.d64 = t2.d64 + t2.d64 * t2.d64;  
  count    
---------  
 1000000  
(1 row)  
  
Time: 1681.451 ms  
postgres=# select count(*) from tt t1 join tt t2 on t1.n * t1.n + t1.n = t2.n + t2.n * t2.n;  
  count    
---------  
 1000000  
(1 row)  
  
Time: 2395.894 ms  

嵌套循环性能对比

postgres=# explain select count(*) from tt t1, tt t2 where t1.ii < 10000 and t2.ii < 10000 and t1.d * t1.d + t1.d > t2.d + t2.d * t2.d;  
                                  QUERY PLAN                                     
-------------------------------------------------------------------------------  
 Aggregate  (cost=2699703.15..2699703.16 rows=1 width=8)  
   ->  Nested Loop  (cost=0.00..2614087.74 rows=34246165 width=0)  
         Join Filter: (((t1.d * t1.d) + t1.d) > (t2.d + (t2.d * t2.d)))  
         ->  Seq Scan on tt t1  (cost=0.00..22800.00 rows=10136 width=8)  
               Filter: (ii < 10000)  
         ->  Materialize  (cost=0.00..22850.68 rows=10136 width=8)  
               ->  Seq Scan on tt t2  (cost=0.00..22800.00 rows=10136 width=8)  
                     Filter: (ii < 10000)  
(8 rows)  
  
Time: 0.561 ms  
postgres=# select count(*) from tt t1, tt t2 where t1.ii < 10000 and t2.ii < 10000 and t1.d * t1.d + t1.d > t2.d + t2.d * t2.d;  
  count     
----------  
 49985001  
(1 row)  
  
Time: 19706.890 ms  
postgres=# select count(*) from tt t1, tt t2 where t1.ii < 10000 and t2.ii < 10000 and t1.n * t1.n + t1.n > t2.n + t2.n * t2.n;  
  count     
----------  
 49985001  
(1 row)  
  
Time: 70787.289 ms  
postgres=# select count(*) from tt t1, tt t2 where t1.ii < 10000 and t2.ii < 10000 and t1.d64 * t1.d64 + t1.d64 > t2.d64 + t2.d64 * t2.d64;  
  count     
----------  
 49985001  
(1 row)  
  
Time: 49861.689 ms  
postgres=# select count(*) from tt t1, tt t2 where t1.ii < 10000 and t2.ii < 10000 and t1.d128 * t1.d128 + t1.d128 > t2.d128 + t2.d128 * t2.d128;  
  count     
----------  
 49985001  
(1 row)  
  
Time: 65779.153 ms  

小结

PostgreSQL内置的numeric类型属于"无限"精度数值类型,其他类型都是定长存储,使用时不需要调用palloc,效率较高。

如果你要使用超过双精能表示的有效范围的数值,目前只能选择decimal\numeric类型,而这个类型前面说了,由于是变长设计,需要调用palloc,效率一般。

那么在数据分析领域,或者需要处理非常多的数据记录时,numeric类型的开销是较大的。

从前面的测试数据,可以观察到性能最好的是float8,其次是decimal64, decimal64不需要使用palloc,性能比numeric好1.5倍左右,而decimal128也比numeric性能好不少。

期待将来PostgreSQL内置decimal64, decimal128。

参考

《PostgreSQL fixeddecimal - 用CPU "硬解码" 提升1倍 数值运算能力 助力金融大数据量计算》

https://www.postgresql.org/message-id/flat/CAFWGqnsuyOKdOwsNLVtDU1LLjS%3D66xmxxxS8Chnng_zSB5_uCg%40mail.gmail.com#CAFWGqnsuyOKdOwsNLVtDU1LLjS=66xmxxxS8Chnng_zSB5_uCg@mail.gmail.com

https://github.com/vitesse-ftian/pgdecimal

https://pgxn.org/dist/pgdecimal/1.0.0/

https://github.com/2ndQuadrant/fixeddecimal

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
目录
相关文章
|
存储 NoSQL 关系型数据库
PostgreSQL列存扩展hydra简单测试
Hydra是一款PostgreSQL的扩展,为PostgreSQL增加了列存引擎,使得PostgreSQL的olap性能大幅提升,本文介绍Hydra基本的使用方法。
|
存储 安全 关系型数据库
PostgreSQL物化视图增量更新扩展 -- pg_ivm
PostgreSQL不支持物化视图增量更新,需要定期执行REFRESH MATERIALIZED VIEW命令刷新物化视图。Incremental View Maintenance (IVM)是一种使物化视图保持最新的方法,其中只计算增量更改并将其应用于视图,而不是REFRESH MATERIALIZED VIEW那样从头开始重新计算内容。当只更改视图的一小部分时,IVM可以比重新计算更高效地更新物化视图。
|
2月前
|
SQL 关系型数据库 C语言
PostgreSQL SQL扩展 ---- C语言函数(三)
可以用C(或者与C兼容,比如C++)语言编写用户自定义函数(User-defined functions)。这些函数被编译到动态可加载目标文件(也称为共享库)中并被守护进程加载到服务中。“C语言函数”与“内部函数”的区别就在于动态加载这个特性,二者的实际编码约定本质上是相同的(因此,标准的内部函数库为用户自定义C语言函数提供了丰富的示例代码)
|
12月前
|
关系型数据库 PostgreSQL
PostgreSQL pg_orphaned扩展
由于种种原因,PostgreSQL可能会产生一些孤儿文件,这些文件会占用磁盘空间,手工查找费时费力还容易出错,pg_orphaned扩展很好的解决了这个问题。
|
SQL 关系型数据库 Go
《增强你的PostgreSQL:最佳扩展和插件推荐》
《增强你的PostgreSQL:最佳扩展和插件推荐》
924 0
|
6月前
|
SQL 关系型数据库 C语言
PostgreSQL【应用 03】Docker部署的PostgreSQL扩展SQL之C语言函数(编写、编译、载入)计算向量余弦距离实例分享
PostgreSQL【应用 03】Docker部署的PostgreSQL扩展SQL之C语言函数(编写、编译、载入)计算向量余弦距离实例分享
90 0
|
6月前
|
SQL 关系型数据库 数据库
PostgreSQL【应用 02】扩展SQL之C语言函数(编写、编译、载入)实例分享
PostgreSQL【应用 02】扩展SQL之C语言函数(编写、编译、载入)实例分享
183 0
|
6月前
|
SQL 关系型数据库 编译器
PostgreSQL SQL扩展 ---- C语言函数(二)
可以用C(或者与C兼容,比如C++)语言编写用户自定义函数(User-defined functions)。这些函数被编译到动态可加载目标文件(也称为共享库)中并被守护进程加载到服务中。“C语言函数”与“内部函数”的区别就在于动态加载这个特性,二者的实际编码约定本质上是相同的(因此,标准的内部函数库为用户自定义C语言函数提供了丰富的示例代码)
|
6月前
|
SQL 存储 关系型数据库
PostgreSQL SQL扩展 ---- C语言函数(一)
可以用C(或者与C兼容,比如C++)语言编写用户自定义函数(User-defined functions)。这些函数被编译到动态可加载目标文件(也称为共享库)中并被守护进程加载到服务中。“C语言函数”与“内部函数”的区别就在于动态加载这个特性,二者的实际编码约定本质上是相同的(因此,标准的内部函数库为用户自定义C语言函数提供了丰富的示例代码)
|
SQL 存储 缓存
「PostgreSQL技术」扩展Postgresql到TB
「PostgreSQL技术」扩展Postgresql到TB

相关产品

  • 云原生数据库 PolarDB