本文解读 PostgreSQL 19 中由 John Naylor 提交的关于校验和硬件加速的改进。这些改进主要涵盖两大平台:x86 平台的 AVX2 优化,以及 ARM 平台的 CRC32C 硬件加速支持。对于追求极致性能和数据安全性的 数据库 管理员和开发者而言,这是一个值得关注的重要更新。
相关 Commit
| Commit |
描述 |
5e13b0f24039 |
Use AVX2 for calculating page checksums where available |
fbc57f2bc2ee |
Compute CRC32C on ARM using the Crypto Extension where available |
effaa464afd3 |
Check for __cpuidex and __get_cpuid_count separately |
f6bd9f0fe25a |
Skip common prefixes during radix sort |
核心价值
- 数倍性能提升:AVX2 提供 256 位寄存器,相比之下 SSE2 只有 128 位,理论上能带来翻倍的吞吐量。
- 智能自动选择:运行时动态检测 CPU 硬件能力,自动选择最优的实现方案,对用户完全透明。
- 全面的跨平台支持:不仅覆盖 x86 (AVX2),也支持 ARM (CRC32C/PMULL) 平台,确保了在不同 系统 架构上都能受益。
原理详解
1. 页面校验和算法
1.1 算法文件 (src/include/storage/checksum_block.inc.c)
页面校验和采用的核心算法是 并行 FNV-1a 哈希算法。其基本原理是将数据页面视为一个字节流,通过迭代计算产生一个32位的校验和值,用于后续的数据完整性验证。
/*
* 页面校验和计算
* 原理:将页面分成多个列,并行计算 32 个校验和,然后 XOR 在一起
*/
static inline uint32
checksum_block(const PageHeader page, BlockNumber block)
{
uint32 sum = 0;
int i;
/* FNV-1a 哈希的种子 */
const uint32 FNV_32_PRIME = 0x01000193;
const uint32 FNV_32_OFFSET = 0x811c9dc5;
/* 对页面每个字节进行哈希 */
for (i = 0; i < BLCKSZ; i++)
{
sum = (sum ^ page[i]) * FNV_32_PRIME;
}
/* 混合块号 */
sum ^= block;
return sum;
}
1.2 改进版本(利用向量化)
上述 C 代码在编译时,如果启用了对应的编译器标志,可以被自动向量化。向量化级别取决于 CPU 支持的指令集:
SSE2: 128 位寄存器 → 同时处理 16 字节
AVX2: 256 位寄存器 → 同时处理 32 字节(性能提升 2 倍+)
关键在于,同样的源代码,针对不同的指令集目标编译,会生成效率迥异的机器码。PostgreSQL 19 的改进,正是为了充分利用 AVX2 等高级向量指令集。
2. AVX2 实现
2.1 分派函数 (src/backend/storage/page/checksum.c line 52-62)
PostgreSQL 采用了一种优雅的运行时分派机制。在首次需要计算校验和时,通过一个“选择器”函数检测当前 CPU 能力,并将计算函数指针指向最优的实现。
/*
* 运行时选择最佳实现
* 在首次调用时检测 CPU 能力并设置函数指针
*/
static uint32
pg_checksum_choose(const PGChecksummablePage *page)
{
pg_checksum_block = pg_checksum_block_fallback;
#ifdef USE_AVX2_WITH_RUNTIME_CHECK
if (x86_feature_available(PG_AVX2))
pg_checksum_block = pg_checksum_block_avx2;
#endif
return pg_checksum_block(page);
}
2.2 AVX2 函数声明 (line 39-45)
AVX2 版本的函数通过编译器属性明确指定了编译目标,确保编译器生成 AVX2 指令。
/*
* 使用 pg_attribute_target 指示编译器为此函数生成 AVX2 指令
* 同一份 C 代码会为 AVX2 目标编译出不同的机器码
*/
#ifdef USE_AVX2_WITH_RUNTIME_CHECK
pg_attribute_target("avx2")
static uint32
pg_checksum_block_avx2(const PGChecksummablePage *page)
{
#include “storage/checksum_block.inc.c”
}
#endif
2.3 备选函数 (line 30-34)
为了保证兼容性,始终保留一个不依赖特定指令集的“后备”实现(fallback)。
/* scalar fallback 实现 */
static uint32
pg_checksum_block_fallback(const PGChecksummablePage *page)
{
#include “storage/checksum_block.inc.c”
}
3. CRC32C 硬件加速
除了通过向量化提升通用哈希计算速度,PostgreSQL 还支持使用 CPU 内置的专用指令进行 CRC32C 计算,这在 ARM 和部分 x86 平台上效率极高。
3.1 Intel SSE4.2/AVX-512 实现 (src/port/pg_crc32c_sse42.c)
对于支持 SSE4.2 或 AVX-512 的 Intel/AMD CPU,PostgreSQL 使用编译器内置函数(intrinsics)直接调用硬件指令。
/*
* CRC32C 硬件加速(Intel SSE4.2)
* 使用 CRC32 指令计算 32 位循环冗余校验
*/
static uint32
pg_comp_crc32c_sse42(uint32 crc, const void *data, size_t len)
{
const unsigned char *p = data;
const unsigned char *end = p + len;
while (p < end)
{
/* 使用 CRC32B 指令(字节)*/
crc = __builtin_ia32_crc32qi(crc, *p);
p++;
}
return crc;
}
3.2 ARM CRC 扩展 (src/port/pg_crc32c_armv8.c line 29-83)
ARMv8 架构也提供了类似的 CRC 计算指令,PostgreSQL 对此进行了适配。
/*
* ARMv8 CRC 扩展
* 使用 __crc32cb, __crc32ch, __crc32cw, __crc32cd 等指令
*/
static inline uint32
pg_crc32c_armv8(uint32 crc, const void *data, size_t len)
{
const unsigned char *p = data;
while (len >= 4)
{
/* 使用 CRC32CW 指令(4 字节)*/
crc = __crc32cw(crc, *(uint32_t *)p);
p += 4;
len -= 4;
}
// ... 处理剩余字节
}
3.3 ARM Crypto Extension - PMULL (src/port/pg_crc32c_armv8.c line 85-206)
对于更高级的 ARM 处理器,PostgreSQL 还能利用加密扩展中的 PMULL 指令进行加速。
/*
* 使用 ARM Crypto Extension 的 PMULL(多项式长乘法)指令
* 通过 PCLMUL(Carry-less Multiply)实现 CRC
*/
static uint32
pg_comp_crc32c_pmull(uint32 crc, const void *data, size_t len)
{
/*
* PMULL 指令可以并行执行多项式乘法
* 用于加速 CRC 计算中的 GF(2) 多项式运算
*/
// ...
}
4. 运行时检测机制
硬件加速功能的前提是准确识别 CPU 能力。PostgreSQL 在多个平台实现了健壮的检测机制。
4.1 x86 CPU 检测 (src/port/pg_cpu_x86.c line 100-139)
在 x86 平台,通过执行 CPUID 指令来获取 CPU 特性标识。
/*
* 使用 CPUID 指令检测 CPU 特性
*/
static void
set_x86_features(void)
{
int info[4];
/* 获取 CPU 支持的特性 */
__cpuid(1, info[0], info[1], info[2], info[3]);
/* 检查 SSE4.2 (用于 CRC32C) */
if (info[2] & (1 << 20)) /* SSE4.2 位 */
x86_feature_enabled[X86_FEATURE_SSE42] = true;
/* 检查 AVX */
if (info[2] & (1 << 28)) /* AVX 位 */
x86_feature_enabled[X86_FEATURE_AVX] = true;
/* 检查 AVX2 */
if (has_avx2())
x86_feature_enabled[X86_FEATURE_AVX2] = true;
}
4.2 ARM 功能检测 (src/port/pg_crc32c_armv8_choose.c line 44-109)
在 ARM/Linux 平台上,通常通过读取 auxv(辅助向量)来获取硬件能力信息。
/*
* 使用 getauxval() 获取硬件能力
*/
static bool
pg_crc32c_armv8_available(void)
{
uint64_t hwcap = getauxval(AT_HWCAP);
/* 检查 CRC32 扩展 */
if (hwcap & HWCAP_CRC32)
return true;
return false;
}
static bool
pg_pmull_available(void)
{
uint64_t hwcap2 = getauxval(AT_HWCAP2);
/* 检查 PMULL(Carry-less Multiply)扩展 */
if (hwcap2 & HWCAP2_PMULL)
return true;
return false;
}
5. 函数指针分派机制
为了实现“一次检测,终身受用”,PostgreSQL 采用了函数指针分派模式。全局的函数指针初始指向一个“选择器”,首次调用时完成检测并替换为最优的实现函数。
5.1 CRC32C 分派 (src/port/pg_crc32c_sse42.c line 193)
/*
* 初始化时指向 choose 函数
* 首次调用时 choose 会检测并替换为最优实现
*/
pg_crc32c (*pg_comp_crc32c)(pg_crc32c crc, const void *data, size_t len) = pg_comp_crc32c_choose;
5.2 分派函数 (src/port/pg_crc32c_sse42.c line 170-191)
选择器函数包含了完整的检测逻辑,并设置了合理的后备方案。
/*
* 首次调用时检测 CPU 特性并替换为最优实现
*/
static pg_crc32c
pg_comp_crc32c_choose(pg_crc32c crc, const void *data, size_t len)
{
/* 设置软实现作为 fallback */
#ifdef USE_SSE42_CRC32C_WITH_RUNTIME_CHECK
pg_comp_crc32c = pg_comp_crc32c_sb8;
#endif
/* 检测 SSE4.2 */
if (x86_feature_available(PG_SSE4_2))
pg_comp_crc32c = pg_comp_crc32c_sse42;
#ifdef USE_AVX512_CRC32C_WITH_RUNTIME_CHECK
/* 检测 AVX-512 */
if (x86_feature_available(PG_AVX512_VL) &&
x86_feature_available(PG_AVX512_VPCLMULQDQ))
pg_comp_crc32c = pg_comp_crc32c_avx512;
#endif
return pg_comp_crc32c(crc, data, len);
}
6. 性能提升对比
那么,这些硬件加速手段究竟带来了多少性能提升呢?下面的表格提供了一个理论上的对比:
| 实现 |
寄存器宽度 |
每周期处理 |
相对性能 |
| Scalar |
32 位 |
1 字节 |
1x |
| SSE2 |
128 位 |
16 字节 |
~8x |
| SSE4.2 CRC |
128 位 + 硬件 CRC |
16 字节 + CRC 指令 |
~15x |
| AVX2 |
256 位 |
32 字节 |
~16x |
| AVX-512 |
512 位 |
64 字节 |
~32x |
注意:上表为理论最大值,实际性能受内存带宽、CPU微架构、编译器优化等多种因素影响。
使用实践
1. 查看校验和功能是否启用
在 PostgreSQL 中,你可以通过以下 SQL 命令检查集群是否启用了数据页校验和功能。
-- 检查数据校验和是否启用
SHOW data_checksums;
2. 验证 CPU 支持
在部署 PostgreSQL 19 之前,可以先确认你的服务器 CPU 是否支持相应的硬件加速指令。
# x86 检测 AVX2
grep -q avx2 /proc/cpuinfo && echo “AVX2 supported”
# ARM 检测 CRC32
grep -q crc32 /proc/cpuinfo && echo “CRC32 supported”
3. 使用 pg_test_timing 测试
PostgreSQL 自带一个名为 pg_test_timing 的工具,可以用来测试计时器的精度,新版中也可能包含对校验和性能的测试选项(请以实际版本手册为准)。
# 校验和性能测试(如果该版本支持此参数)
pg_test_timing --checksums
4. 监控校验和计算开销
启用校验和会引入额外的 CPU 计算。以下是一个概念性的性能开销估算(基于 pgbench 基准测试,scale factor=100):
基准测试(pgbench -s 100):
- 无校验和: ~5% CPU 用于写入
- 有校验和(SSE2): ~6% CPU 用于写入
- 有校验和(AVX2): ~5.5% CPU 用于写入
实际影响取决于工作负载和 CPU
可以看到,硬件加速(AVX2)能有效降低因启用校验和而带来的额外 CPU 开销,使其更接近于“无校验和”的状态。
5. 在不同平台上验证
x86_64 Linux
# 编译时查看支持的特性(部分信息)
pg_config --cflags
# 运行时查看数据库版本及编译选项
SELECT version();
ARM (aarch64)
# 检查 ARM 特性
cat /proc/cpuinfo | grep -E “Features|CPU architecture”
6. 启用数据校验和
如果你正在初始化一个新的集群,或者想为现有集群启用校验和(PostgreSQL 11+ 支持在线启用,但建议在维护时段操作):
# PostgreSQL 17+ 支持在线启用校验和
pg_checksums --enable -D $PGDATA
# 或者离线启用(在初始化集群时)
initdb -k -D $PGDATA
7. 性能调优建议
7.1 高写入场景
在校验和计算可能成为瓶颈的高写入负载场景下,可以考虑以下组合优化:
# 如果校验和计算成为瓶颈,考虑:
# 1. 使用支持 AVX2/CRC32C 的 CPU(硬件基础)
# 2. 调整 wal_compression 为 lz4(减少需校验的 I/O 数据量,且 lz4 本身CPU开销低)
相应的 postgresql.conf 配置:
wal_compression = lz4
7.2 I/O 密集场景
对于 I/O 是主要瓶颈的场景,校验和带来的额外 CPU 开销通常影响甚微。此时,优化重点应放在 I/O 本身。
# 校验和会轻微增加 CPU 开销
# 对于 I/O 瓶颈场景影响较小
# 可以增加 shared_buffers 提升缓存命中率,间接减少I/O和校验计算
shared_buffers = 8GB
8. 故障排除
8.1 校验和不匹配
如果存储系统出现静默错误,PostgreSQL 会在读取数据页时校验失败并记录错误。
-- 如果发现校验和不匹配,会在错误日志中报告
-- 检查 postgresql 日志
SELECT pg_read_file(‘postgresql.log’);
8.2 CRC32C 硬件指令错误
在极少数情况下,如果 CPU 报告支持某指令但实际执行失败(可能是虚拟机模拟问题或 CPU bug),可能会导致非法指令错误。
# 如果 CRC32C 硬件有问题,可能报非法指令错误
# 检查 dmesg 或系统日志
dmesg | grep -i “illegal instruction”
总结
PostgreSQL 19 中针对校验和计算的硬件加速改进,是数据库内核持续性能优化与现代化的重要一步。它巧妙地将编译时优化、运行时检测和函数指针分派结合在一起,在提升数据安全性的同时,最大限度地压榨了现代 CPU 的硬件潜力。
关键要点:
- AVX2 向量化:在 x86 平台利用 256 位宽寄存器,相比传统的 SSE2 实现,理论数据处理吞吐量翻倍。
- 专用指令支持:在 ARM 平台利用 CRC32 和 PMULL 扩展,在 Intel/AMD 平台利用 SSE4.2/AVX-512 的 CRC32C 指令,实现超高效的校验计算。
- 智能运行时选择:无需用户干预,数据库自动检测并选用当前硬件上的最优算法,平衡了性能与兼容性。
- 显著的性能收益:硬件加速能将校验和计算的开销降低数倍,使得“启用数据校验和”这一安全特性的代价变得更低。
- 对运维完全透明:DBA 无需进行额外配置或更改管理流程,升级后即可自动获益。
对实际运维的影响:
- 在已启用校验和的集群中,写入操作的 CPU 开销将进一步降低。
- 与 PostgreSQL 后期版本提供的在线启用校验和功能配合更佳,因为启用过程中的大量计算也能被加速。
- 现有的监控、备份和恢复流程不受任何影响。
这项改进体现了 PostgreSQL 社区对性能细节的执着追求。随着 ARM 服务器在数据中心日益普及,以及 x86 CPU 向量指令集的不断演进,此类底层硬件优化对于保持数据库竞争力至关重要。如果你正在管理高负载或对数据完整性要求极高的 PostgreSQL 集群,那么 PostgreSQL 19 的这项特性无疑值得你期待和尝试。
欢迎在 云栈社区 交流更多关于数据库性能优化的话题。