PG-Strom学习总结

PG-Strom学习总结

PG-Strom是一个PostgreSQL的扩展模块,是连接PostgreSQL和GPU的桥梁,利用GPU来加速SQL上的操作。其GPU代码生成器会根据SQL语句生成对应的在英伟达的CUDA(统一计算架构)的GPU程序。目前主要支持SCAN、JOIN和GROUP BY操作。PG-Strom的“SSD-to-GPU Direct SQL”机制允许直接将数据从NVME的固态硬盘中传递到GPU。他的“PL/CUDA”和“gstore_fdw”允许运行高计算密度的问题。下面是他的具体要求配置和工作原理:
配置要求

  1. 硬件服务器:64位的能运行支持CUDA Toolkit(用来开发CUDA程序的工具)的Linux操作系统的x86硬件。另外“SSD-to-GPU Direct SQL”需要支持NVMe规范的固态硬盘,并且和GPU安装在同一个PCIe Root Complex下。
  2. GPU设备:系统中至少一个支持CUDA Toolkit的计算能力6.0的GPU。
  3. 操作系统:由CUDA Toolkit支持的x86 64位Linux操作系统。
  4. PostgreSQL:9.6版本之后的PostgreSQL。因为9.6版本为CPU并行执行和GROUP BY更新了自定义扫描接口,允许协调外部模块提供的定制计划。
  5. CUDA Toolkit:9.1版本的CUDA Toolkit,PG-Strom提供半精度浮点型(float2)内部使用half_t类型的CUDA C,所以老版本的CUDA Toolkit不行。

下面是PG-Strom的具体实现原理细节:

  1. NVME-Strom module:NVME-Strom内核模块是与PG-Strom的核心功能密切配合,如“SSD-to-GPU Direct SQL Execution”。当NVME-Strom内核收到SSD-to-GPU的直接数据传输请求时,首先,他检查需要的数据块是否在操作系统的页缓冲中,如果“fast_ssd_mode”是0,NVME-Strom将立刻将所请求的数据所在的页缓存写回给调用者的用户缓存空间中,然后指示应用程序通过CUDA API调用正常主机 - >设备数据传输。它适用于非快速NVME-SSD,如PCIe x4等级。使用PCIe x8级别的快速NVME-SSD或分段模式下的多个固态硬盘,传输将会更快。如果”fast_ssd_mode”不是0,NVME-Strom将踢出SSD-to-GPU直接数据传输请求。NVME-Strom内核模块为SSD-to-GPU直接的数据传输使用DMA请求,然后将他们排到NVME设备的IO队列。当异步的DMA请求过多时,DMA请求延迟将变得非常糟糕,因为NVME-SSD控制器是按到达顺序处理DMA请求的。当DMA请求的时间太长就可能会被认为出错了。
  2. 当想知道query语句是否由GPU执行时,可以用EXPLAIN指令。Query语句会被分成多个部分并执行,PG-Storm能在GPU上并行的执行SCAN,JOIN和GROUP BY,看到有GpuScan,GpuJoin,GpuPreAgg时,证明被GPU执行了。PG-Strom会与query优化器进行交互,会执行优化器提供的query执行计划。
  3. CPU-GPU混合式并行:在CPU并行模式下,Gather节点会启动多个后台工作进程,并收集后台进程的执行结果。PG-Strom提供的自定义扫描执行支持将执行交给后台工作,他们单独通过GPU执行他们的部分任务。CPU内核为提供数据给GPU而建立缓存比在GPU上执行SQL更花时间,所以CPU和GPU的混合式会有更好性能,另一方面,每个进程都会创建用于与GPU的交互的CUDA文件和消耗一定的GPU资源。
  4. 如果PG-Strom只是简单的用在GPU中执行的SCAN,JOIN,GROUP BY来替换PostgreSQL中的标准操作并不会有更好的性能。比如GPU执行了SCAN后写回主机缓冲区,然后执行JOIN时又从缓冲区中发送给GPU,执行完后又写回主机缓冲区,这样会让数据在CPU和GPU中来回的传输。为此,PG-Strom有一种特殊的模式来“上拉子底层计划”在GPU内核的一次单独调用时执行许多工作,这些操作的组合会上拉子计划:SCAN+JOIN;SCAN+GROUP BY;SCAN+JOIN+GROUP BY。比如在执行GpuJoin时会把SCAN嵌套在里面执行,而不是一个个的执行。
  5. MPS daemon(多进程服务守护进程):一个MPS daemon可以为48个客户端提供服务。不支持dynamic parallelism(动态并行性)。PL/CUDA用户定义的函数可能在CUDA设备运行时调用子内核使用动态并行性,所以不能用MPS来调用PL/CUDA函数。
  6. 索引:PG-Strom只支持BRIN-index。BRIN-index是在物理存储的相邻的记录有相似的键值,如果当块范围内的记录明显不符合scan的限定词,那么将跳过这些块。PG-Strom也利用了BRIN-index的这些特点,会跳过这些明显不必要的要加载到GPU的块。
  7. 划分:表划分是PostgreSQL支持的新的机制,他将逻辑上的大表划分成物理上的小表,在扫描是会跳过那些明显不符合的子表。当PG-Strom与表划分的PostgreSQL一起使用时,他的优化器会选择GpuScan扫描每个单独的将要扫描的子表,在Append节点合并GpuScan结果。通过GUC参数“pg_strom.enable_partitionwise_gpujoin”和“pg_strom.enable_partitionwise_gpupreagg”,PG-Strom可以将JOIN/GROUP BY应用到子表上,在子表上执行了JOIN/GROUP BY再执行Append。而不是让Append在SCAN和JOIN/GROUP BY中间执行,因为这样的话会让数据在内存和GPU之间来回传递。
  8. “pg_strom.enabled”参数可以开关PG-Strom,以此来看错误是出在PG-Strom还是PostgreSQL上。
  9. Crash dump:对于进程崩溃时生成崩溃转储(CPU端),需要更改操作系统对PostgreSQL服务进程可以产生的核心文件大小的资源限制。对于GPU内核的错误产生的崩溃转储,需要将PostgreSQL服务进程的“CUDA_ENABLE_COREDUMP_ON_EXCEPTION”环境变量设为1。
  10. SSD-to-GPU Direct SQL Execution:SSD-to-GPU Direct SQL Execution通过PCIe总线连接NVMe-SSD和GPU,这样提供以接近硬件有线速度的数据流来快速执行SQL。通常对于将存储中的数据块加载到CPU内存后会有大量的数据被过滤掉,结果数据集只是原数据集的一小部分,所以我们消耗了PCIe的带宽来移动垃圾数据。

SSD-to-GPU Direct SQL Execution改变了读存储的流,它直接将数据块使用DMA传到GPU,然后执行SQL语句来减少传给CPU的数据量。也就是说,他把GPU当作一个SQL语句的预处理器。这个功能的内部是用NVIDIA GPUDirect RDMA,它通过Linux 内核模块进行协调在GPU设备内存和第三方设备之间的PCIe总线来端到端传送。该机制要求DMA的两端设备必须链接到同样的PCIe root complex,并且推荐用专用的PCIe交换器链接两端。

因为PG-Strom用不了MVCC的可见性检查,所以PostgreSQL有一个可见性映射的基础结构,是一组用于指定特定数据块中记录是否可见的标识。SSD-to-GPU Direct SQL Execution利用这个结构来检查可见性,只有所有都可见的块会通过DMA来读取。可通过使用VACUUM指令来让PostgreSQL构建可见性映射表。

  1. GPU Memory Store(gstore_fdw):通常PG-Strom对GPU设备内存的使用只是临时目的,而gstore_fdw是一种保存GPU设备内存并将数据初始加载到内存的功能,它不需要为PL / CUDA函数的每次调用设置参数和加载,并且消除了1GB的可变长度数据限制。从字面上,gstore_fdw是通过使用PostgreSQL的外部数据包装器来实现的,你可以使用 由gstore_fdw管理的外部表中的“INSERT,UPDATE,DELETE”指令来修改GPU设备的数据结构。PL/CUDA函数可以通过外表引用存在GPU设备内存中的数据。目前通过SQL表述生成的GPU程序不能引用设备内存区域,未来将加上这个功能。Gstore_fdw的外表只能接受单个事务修改。任何写入该表的内容在事务提交前对其他对话是不可见的,这也是为了保证事务原子性。另外建议在修改了大量的行之后再提交事务。最后,gstore_fdw的外表是易丢失的,因此,我们加载到gstore_fdw外表上的内容应该可以由其他数据源重构。
  2. PL/CUDA: PostgreSQL支持通过CREATE LANGUAGE语句添加编程语言来实现SQL函数。 PL / CUDA是一个支持CREATE LANGUAGE命令的语言处理程序。它还允许用户运行手动实现为SQL函数的任意GPU程序,不仅仅是基于SQL的PG-Strom自动生成的GPU程序。参数是可以由PG-Strom支持的数据类型,参数通过PL/CUDA隐式的载入到GPU设备内存。也可以使用由gstore_fdw定义的外表作为PL/CUDA的参数,这样就没有必要为每个调用载入数据到GPU,并且可以使用大于1GB的数据。

一旦PL/CUDA用CREATE FUNCTION声明了一个函数,他会生成一个嵌套该函数定义的CUDA源代码,然后用GPU设备构建他,除了SQL函数提供的参数和要写回结果外,他和普通GPU软件一样。PL/CUDA实现的CUDA程序会作为子进程在PostgreSQL后台执行,他有独立的地址空间和从PostgreSQL获取的操作系统/GPU资源。CUDA程序包含宿主系统的宿主代码还有GPU上执行的设备代码。宿主代码可以用C来写,并且出于安全考虑只允许数据块超级用户能定义PL/CUDA。PL/CUDA的语言处理器根据代码块构造单独的CUDA C源文件,然后用nvcc编译器在声明或执行时进行编译,如果包含“#plcuda_include”,源代码只会在执行时构建,如果是已经构造过的相同的CUDA程序,我们可以重复使用。当SQL指令调用PL/CUDA函数时,PL/CUDA语言处理器会通过管道复制SQL函数的参数然后使用之前已经构造好的程序来运行,这些参数都存储在CUDA程序的参数缓冲区中。CUDA C程序中引用的数据类型会在参数缓冲区中被初始化为指针,这是由”cudaMallocManaged()”分配的托管内存区域,这些指针在主机系统和GPU设备间没有显示DMA可用。一种特殊情况是如果参数有reggstore类型,实际上Gstore_Fdw外表的OID(32位整数)如果被提供为PL/CUDA参数,就会用gstore_fdw外表替换GPU设备内存的引用。当数据量小的时候,我们可以用PosgtgreSQL支持的数组类型……当数据量太大,就应该考虑用Gstore_fdw外表。。

猜你喜欢

转载自blog.csdn.net/Han_L/article/details/88814664