函数buf_page_is_corrupted (innodb_plugin/buf/buf0buf.c)这个函数,是在InnoDB中获取page数据后,对page作校验,判断内容是否损坏。
在压力测试中发现,innodb.so最耗费cpu的就是这个buf_page_is_corrupted。
Percona对这个函数作了优化。本文介绍与此有关的配置、优化原理以及进一步的优化空间。(当前版本5.1.48 innodb-plugin 1.0.9)
1、函数主流程
每个page头部的前四个字节是本page的checksum, 在page写入时计算得到。因此函数主流程如下:
a) 读前四个字节得到checksum_field
b) 对page重新计算checksum, buf_calc_page_new_checksum(read_buf)
c) 若a、b的结果相同则认为page未损坏。
2、 函数瓶颈
显然函数主要计算时间在buf_calc_page_new_checksum中,后者直接调用ut_fold_binary(实现位置 ./include/ut0rnd.ic)。我们来看这个ut_fold_binary的函数实现:
UNIV_INLINE ulint ut_fold_binary( /*===========*/ const byte* str, /*!< in: string of bytes */ ulint len) /*!< in: length */ { const byte* str_end = str + len; ulint fold = 0; ut_ad(str || !len); while (str < str_end) { fold = ut_fold_ulint_pair(fold, (ulint)(*str)); str++; } return(fold); }
说明:其中ut_fold_ulint_pair的第二个参数表明,在该函数中,是以一个uint方式使用的(如下)。该函数在同一个文件中,通过两个uint的异或和移位得到返回值。但在第13行中的str++, 使得每个字节都被使用了4次。
ut_fold_ulint_pair( /*===============*/ ulint n1, /*!< in: ulint */ ulint n2) /*!< in: ulint */ { return(((((n1 ^ n2 ^ UT_HASH_RANDOM_MASK2) << 8) + n1) ^ UT_HASH_RANDOM_MASK) + n2); }
3、 优化
Percona版本的改进中,将实现了一个新的函数ut_fold_ulint_pair_32, 将步进修改为4个字节。
实际上,将16k数据签名成4个字节,使用前后两种方法的信息损失是相同的,因此优化版本概率上并不损失准确性。
在64位机器上,若每次直接取两个unsigned long作ut_fold_ulint_pair操作(当然需要为此写一个ut_fold_ulint_pair_64函数),计算速度相同,但步进长度加倍,能够进一步优化此函数性能。
代码上需要作如下修改
a) ./include/ut0rnd.ic 和 ./include/ut0rnd.h 中增加ut_fold_binary_64的定义和声明
b) buf/buf0buf.c中增加buf_calc_page_new_checksum_64, 并在buf_page_is_corrupted增加调用
c) 在buf_flush_init_for_writing(buf/buf0flu.c)中,写入page前换用buf_calc_page_new_checksum_64得到checksum
4、 测试结果
用sysbench作压力,并计算三种buf_calc_page_new_checksum的平均执行时间。得益于神器SystemTap,不需要再另外统计时间的代码(痛苦回忆中)。
函数 |
平均执行时间 |
buf_calc_page_new_checksum |
65ms |
buf_calc_page_new_checksum_32 |
24ms |
buf_calc_page_new_checksum_64 |
16ms |