Rsync Adler-32 算法

Rsync为了只同步文件变化的部分,使用了两种 hash 算法:弱校验算法和强校验算法。

弱校验用于快速分辨出不同的块,使用的是 Adler-32 算法。
强校验用于确保数据块是真的相同,因为弱校验很有可能hash冲突,加一层强校验,双保险!
强校验使用的是 MD5 算法。

这篇文章记录自己学习弱检验 Adler-32 算法的体会。

Adler-32算法简介

Adler-32 算法是由 Mark Adler 在1995年发明的一种滚动校验算法,算法因此也以作者名字命名。
除了Rsync,还有大名鼎鼎的 zlib 也在使用这一算法。

算法原理

这个算法的原理说起来也简单。

假设我们要为数组:A1, A2, A3, … An 计算出一个32 位的hash值。

sum = adler32(A1, A2, A3, … An)

记 sum 的低16位为:s1
记 sum 的高16位为:s2
sum = s2 × 216 + s1

其中s1 是指从A1到An每个数字依次相加得到的和。
s2 指每处理一位数字,s2的值就加上s1 (难以用文字描述,还是看公式吧)

s1 = A1 + A2 + A3 + … + An
s2 = A1 + (A1 + A2) + (A1 + A2 + A3) + … + (A1 + A2 + … + An)
     = A1 × n + A2 × (n - 1) + A3 × (n - 2) + … +An

我们来看下Rsync-1.0 版本中实现的 Adler32 算法

uint32 get_checksum1(char *buf,int len)
{
    int i;
    uint32 s1, s2;

    s1 = s2 = 0;
    for (i = 0; i < len; i++) {
	      s1 += buf[i];
	      s2 += s1;
    }
    return (s1 & 0xffff) + (s2 << 16);
}

怎么样?比起上面眼花缭乱的数学公式,代码看起来是不是更简单,更容易懂?

Rsync之后的版本对Adler32算法的实现进行了优化,最新的Rsync-3.2.3中Adler32算法实现如下:

/* a non-zero CHAR_OFFSET makes the rolling sum stronger, but is
   incompatible with older versions :-( */
#define CHAR_OFFSET 0

uint32 get_checksum1(char *buf1, int32 len)
{
	int32 i;
	uint32 s1, s2;
	schar *buf = (schar *)buf1;

	s1 = s2 = 0;
	for (i = 0; i < (len-4); i+=4) {
		s2 += 4*(s1 + buf[i]) + 3*buf[i+1] + 2*buf[i+2] + buf[i+3] + 10*CHAR_OFFSET;
		s1 += (buf[i+0] + buf[i+1] + buf[i+2] + buf[i+3] + 4*CHAR_OFFSET);
	}
	for (; i < len; i++) {
		s1 += (buf[i]+CHAR_OFFSET); s2 += s1;
	}
	return (s1 & 0xffff) + (s2 << 16);
}

不同于原始算法中一个字节一个字节的处理,优化后的算法一次处理4个字节,可以显著降低运算量,提高性能。
另外一次处理4个字节,是不是32位CPU刚好一次能容纳4个字节宽度的原因?我不确定。

算法中还引入了一个宏变量:CHAR_OFFSET
通过注释得知如果CHAR_OFFSET设置为非0值,可以提高算法健壮性,这一点也不明白为什么。

滚动校验

能计算出一组数据的校验和不算本事,市面上有很多种算法都能计算出校验和,甚至自己也可以设计一种算法。

Rsync对算法的要求是
在已知 [A1, A2, … An] 校验和的情况下,如果再来个数据:An+1,能够在之前校验和的基础上迅速推导出 [A2, A3, … An, An+1] 的校验和,也就是数据块向后滑动1字节后,迅速计算出下一个块的校验和, 而不是逐字节的计算[A2, A3, … An, An+1]的校验和,那样就太慢了。
在这里插入图片描述
我们记 [A1, A2, … An] 的校验和为: sum[1, n]
记 [A2, A3, … An, An+1] 的校验和为: sum[2, n+1]

sum[2, n+1]校验和求值的基本思路是:
在 sum[1, n]的基础上,去除 A1 相关部分,再加上 An+1 相关部分。

前面说过,sum 由低16位的s1和高16位的s2组成,我们做如下标记:
记滑动前的块 sum[1, n] s1 部分为:     s1[1, n]
记滑动前的块 sum[1, n] s2 部分为:     s2[1, n]
记滑动后的块 sum[2, n+1] s1 部分为: s1[2, n+1]
记滑动后的块 sum[2, n+1] s2 部分为: s2[2, n + 1]

那么我们可以做出如下数学推导:
sum[2, n+1] = s1[2, n+1] + s2[2, n + 1] × 216
                     = (s1[1, n] - A1 + An+1 ) + (s2[1, n] - n × A1 + (s1[1, n] - A1 + An+1 ) ) × 216

再来看看 Rsync-1.0 代码中关于块向后滑动一字节后,如何根据前一个块的校验和计算下一个块的校验和 (代码来自 match.c: hash_search() 函数,为了便于描述,做了抽取和变量名修改)

uint32 s1, s2, sum;

/* 1. 计算出第一块校验和, n代表数据块长度 */
sum = get_checksum1(buf, n);
s1 = sum & 0xFFFF;
s2 = sum >> 16;

/* 2. 数据块向后滑动1字节 */

/* 2.1 去除被滑动掉的第一个字节相关部分 */
/* Trim off the first byte from the checksum */
s1 -= buf[offset];
s2 -= n * buf[offset];

/* 2.2 添加新被滑入字节的相关部分 */
s1 += buf[offset+n];
s2 += s1;

/* 2.3 计算出滑动1字节后新块的校验和 */
sum = (s1 & 0xffff) + (s2 << 16);

代码和数学式对的上了么?我觉得直接看代码,反而更容易理解。

参考文章:
[1] Rsync官网对于adler-32算法的描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

duanbeibei

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值