Doclist压缩方法简介

标签: 其他 搜索引擎 | 发表时间:2011-07-11 14:12 | 作者:桂南 flychen50
出处:http://www.searchtb.com

本文是作者在学习doclist压缩时的一点总结,希望以尽可能简单明了的方式描述各个算法的思想和适用场景,帮助同学们理解和比较。本文并不涉及具体的算法实现,代码请大家自行google。这里需要强调的是“所谓的改进顺序”只是作者yy出来方便理解记忆,并不反应真实的压缩方法发展历程。

1.什么是doclist?

倒排表的基本组成部分,看例子:

Computer: 10,35,100,170,370,29000,30000,30010

表示computer这个词出现在编号(docid)为10,35,100,170,37029000,30000,30010的doc中,10,35,100,170,370,29000,30000,30010即为doclist,在下文中作者会多次使用这个例子来演示压缩算法以及计算压缩比,但请注意这个例子是作者虚构的,所以按照例子得到的结论并不能代表每个算法的真实优劣。

在搜索引擎倒排索引中需要大量保存这类数据,如何高效的存储和访问成为一个搜索引擎性能高低的关键。对一个压缩方法的效果进行评估主要看以下3个方面:1. 压缩速度,2.压缩比3.解压缩速度。可以认为这3个是互相影响和制约的,一般来说压缩速度越慢,压缩比越高,解压缩也越慢。而对搜索引擎来说这3个因素中最关注的是3和2,可以适当放宽的是1.为什么?因为doclist压缩只有一次即建索引的时候,而每次查询都需要解压缩;压缩比高则索引越小,更多的索引可以放内存,查询速度自然更快。

2.最naive的方法:定长int

方法:使用定长的int来精确记录每一个docid

小改进1:由于docid为不可能为负数,可以使用uint来保存

小改进2:根据docid的取值范围选择u16,u32,u64

小改进3:根据docid的取值范围选择合适的bit数N,N>=logMAXDocid,这样每个数只需要N个bit。这里有一个内存访问对齐的问题,每次取一个数效率不高,怎么办?一次取32(64)个数,自然就是对齐的了嘛。

10,35,100,170,370,29000,30000,30010 压缩算法 需要的bit 说明
U16 128 8*2=16byte=128bit
定长bit,N=15 120 15*8=120bit

3.改进1:Variable Byte

定长int方案的问题:小的docid需要使用和大的docid一样的存储空间,这是很不划算的。

改进目标:小的docid使用少的存储空间

改进方法:每个byte的第一位为flag,表示是否继续使用下一个byte,剩下7位为有效位,所有的有效位组成数字的2进制表示。

改进效果:小数字使用的byte明显减少

继续上面的例子:

10,35,100,170,370,29000,30000,30010 压缩算法 需要的bit 说明
Variable Byte 128 10,35,100:需要1个byte

170,370:需要2个byte

29000,30000,30010:需要3个byte

共1*3+2*2+3*3=16byte

4.改进2:存delta

改进1的最主要的问题:因为每个byte的可用bit少了1/8,大数字要占用更多的byte。就是上面29000,30000,30010的情况,本来2个byte可以保存的数现在需要3个byte了。直接导致在小数字省下3个byte的情况下总byte数和u16方式保存一样。

改进目标:在保证信息不丢失的情况下减少大数字的出现概率

改进方法:考虑到docid的有序特性,可以只保存和前一个docid的delta。以上面的例子来说明:

原值 Delta
10,35,100,170,370,29000,30000,30100 10,25,65,70,200,28630,1000,100

改进效果:大数字出现次数明显减少,压缩效果提升明显

10,25,65,70,200,28630,1000,100 压缩算法 需要的bit 说明
Variable Byte 96 10,25,65,70,100:1个byte

200,1000:2个byte

28630:3个byte

共5*1+2*2+3=12byte

5.改进3:unary code

问题: 存delta时,小数字变多,尤其是高频词,delta很小,按照byte粒度来编码浪费太大

改进目标:高效的存储小数字

改进方法:对数字N 编码为n个1,后面跟一个0

小改进:由于Delta取值范围[1,max-min],对数字N编码为n-1个1,后面跟一个0

改进效果:很小的数字编码效率极高。

数字 编码
1 0
2 10
3 110
10 1111111110

6.改进4:Group VarInt

问题:unary code只适合很小的数字,大数字相当不划算,看10的编码就知道了,更不用说100了。最终unary code是配合其他编码方式一起使用。从目前来看还是Variable Byte能兼顾大小数字。前面我们主要关注的是压缩比,但是从解压缩性能的角度看Variable Byte有一个问题,对每个byte的flag bit进行判断会导致cpu预测失败,打乱整个流水线的执行,从而使解压缩效率降低。

改进目标:减少解压缩时的判断

改进方法:4个数为1组,第一个byte里面每2个bit记录一个数的byte数,后续byte全是payload

改进效果:解压缩4个数,如果采用Variable Byte则最少4次判断,最多16次判断。而GroupVarInt只需要对第一个byte的每种组合进行switch判断,这样一次判断就可以解压缩4个数。但是从压缩率的角度看每个数字现在都需要多存2个bit,如果小数字比较多(<=127)则压缩率不如Variable Byte。

10,25,65,70,200,28630,1000,100 压缩算法 需要的bit 说明
Group VarInt 112 10,25,65,70:1+4*1=5

200,28630,1000,100:1+1+2+2+1=7个byte

共5+7=12个byte

7.改进5:γ code

问题:小数字按照byte来编码比较浪费,例子中:10,25,65,70 这四个数采用Variable Byte需要4个byte。

改进目标:每个数按照bit来编码

改进方法:把一个数的编码分为2个部分,1.length,即bit数 2.value;length部分由unary code来编码;value记录数字2进制表示时移除第一个1之后的部分

数字 Length Value 编码
1 0 0
2 10 0 100
3 10 1 101
4 110 00 11000
5 110 01 11001
6 110 10 11010
10 1110 010 1110010
25 11110 1001 111101001
65 1111110 000001 1111110000001
70 1111110 000110 1111110000110

8.改进6:δ code

问题:通过上面的例子可以看出γ code的压缩比不尽如人意,主要原因是γ code的length 压缩效率不高

改进目标:提高length部分的压缩比率

改进方法:length部分使用γ code再次编码

数字 Length Value 编码
10 101 010 101010
25 11000 1001 110001001
65 11010 000001 11010000001
70 11010 000110 11010000110

可以看到压缩比有提高。

9.改进7:Simple 9

问题:δ code 中length的2次压缩导致解压会变慢,能不能不存length?

改进目标:不存length

改进方法:32bit 分两块,4个bit记录后面28bit的使用方式,即模式。后续28个bit实际存数字。Simple9的意思就是有9个简单的模式,即1*28bit,2*14bit,3*9bit,4*7bit,5*5bit,7*4bit,9*3bit,14*2bit,28*1bit

数字 需要的bit
10 4
25 5
65 7
70 7
200 8
28630 15
1000 10
100 7

前4个数满足4*7bit的模式,所以只需要32bit即可存储。杯具的是28630这个数,只能用1*28bit的模式来存,导致200这个数也只能用1*28的模式来存。

10.改进8:Simple16

问题:Simple9还有bit的浪费。第一,4个bit可以表示16种模式,simple9用了9种。第二,3*9bit,5*5bit,9*3bit 这三种模式下没有完全利用28个有效位。

改进目标:绝不放过任何一个bit

改进方法:扩展为16个状态

举例:5*5-》3*6+2*5,2*5+3*6

添加2*10+8,8+16等模式

此时200,28630就可以采用8+16的模式在32bit里搞定了,压缩效果优于simple9.

11.改进9:PForDelta

问题:后续数字的组合不是所有的情况都能充分利用simple16。即某些情况下模式里的bit不能完全利用。上面的例子,由于有28630的存在,需要15个bit,在simple9的情况下必须使用28bit来存。统计发现doclist里面存在突变的情况,我们称之为异常数,而这种情况正是simple9,simple16的软肋。

改进目标:对异常数进行特殊处理,保证正常数的高效存储,

改进方法:

  1. 一次处理一批docid,目前常用的选择是128。想想看为啥?
  2. 找到一个数字N,90%以上的数字可以用N个bit存储
  3. 128*N bit构成一个数组,正常数直接存储,异常数对应位置存下一个异常值的offset,需要额外记录第一个异常数的offset,通过这个值和异常值位置的值可以找到全部异常数。
  4. 对异常numb单独存储在数组后,可以简单使用用定长int

10,25,65,70,200,28630,1000,100取N=8异常值为 28630,1000这两个数字存在数组后面



改进效果:多个论文说明PForDelta压缩比率和解压缩速度都相当牛B,目前是多个搜索引擎的不二选择。

12.改进10:NewPFD

问题:如果异常值刚好在128个数的尾部,N bit不能存下下一个异常的offset会出现什么情况呢?1.增大N,考虑到N的放大效应(N*128)这样压缩比必然受到影响。2.强制添加异常值,即把正常值当做异常值来保存。异常值的特殊处理会导致解压缩效率的降低。

改进目标:异常值出现在任何位置都不需要特殊处理

改进方法:

  1. 数组的N bit保存存异常值的低N bit
  2. 溢出的部分和异常值的位置链表存在数组后面,由于数字会比较小,采用S9或者S16进行编码

这里简单说明一下解压缩的过程,首先取出数组得到8个数的值。然后根据第一个异常的offset知道第6个数是异常,取第一个异常的值111和对应位置的214进行合并,得到原始值28630;然后读取111后下一个异常值的offset:1,知道第7个值也是异常值,取3和对应位置的232合并,得到原始值1000。

111,1,3这部分一般采用Simple9或者Simple16进行压缩,这里就不演示了。

13.有个小插曲,

PForDelta 或者NewPFD在异常值越少的情况下压缩效果越好。研究表明如果对doc进行预排序,以网页搜索为例如果把类似主题或者相同网站的网页组织在一起,文本倒排的doclist采用PForDelta 或者NewPFD压缩比随机组织会有较大提升。其中的原因你们懂得。

14.换个思路看压缩方法:Golomb code & Rice code

也换个例子:5,10,16,22,25,37,39,43

数字 Golomb code(取b=17) Rice code(取b=16)
5 0*17+5 0*16+5
10 0*17+10 0*16+10
16 0*17+16 1*16+0
22 1*17+5 1*16+6
25 1*17+8 1*16+9
37 2*17+3 2*16+5
39 2*17+5 2*16+7
43 2*17+9 2*16+11

这样任何数字就变成了绿色的2部分。第一部分可以用unary code编码,第二部分因为取值范围固定,可以用定长bit来保存。具体编码大家参考前面的内容就应该很容易搞定。

这个算法最tricky的是如何选择b,还好已经有论文告诉我们取b=0.69*average最优。Rice code是对Golomb code的优化,即取2的幂。这个优化主要是解压缩效率的提升,想想看为什么?

相关 [doclist 压缩 方法] 推荐:

Doclist压缩方法简介

- flychen50 - 搜索技术博客-淘宝
本文是作者在学习doclist压缩时的一点总结,希望以尽可能简单明了的方式描述各个算法的思想和适用场景,帮助同学们理解和比较. 本文并不涉及具体的算法实现,代码请大家自行google. 这里需要强调的是“所谓的改进顺序”只是作者yy出来方便理解记忆,并不反应真实的压缩方法发展历程. 倒排表的基本组成部分,看例子:.

android图片压缩方法

- - CSDN博客移动开发推荐文章
第一:我们先看下质量压缩方法.         image.compress(Bitmap.CompressFormat.JPEG, 100, baos);//质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中  .         while ( baos.toByteArray().length / 1024>100) {  //循环判断如果压缩后图片是否大于100kb,大于继续压缩         .

String压缩 解压缩

- - CSDN博客推荐文章
数据传输时,有时需要将数据压缩和解压缩,本例使用GZIPOutputStream/GZIPInputStream实现. 1、使用ISO-8859-1作为中介编码,可以保证准确还原数据. 2、字符编码确定时,可以在decompress方法最后一句中显式指定编码. * @return 压缩后的字符串. GZIPOutputStream os = null; // 使用默认缓冲区大小创建新的输出流.

Nginx GZip 压缩

- - 开心平淡对待每一天。热爱生活
  Nginx GZip 模块文档详见: http://wiki.nginx.org/HttpGzipModule 常用配置片段如下:. # 压缩比例,比例越大,压缩时间越长. 默认是1 gzip_types. text/css text/javascript; # 哪些文件可以被压缩 gzip_disable.

HDFS-压缩

- - Java - 编程语言 - ITeye博客
文件压缩带来了两大益处1)减少存贮空间2)加速网络(磁盘)传输. 基于大数据的传输,都需要经过压缩处理. 压缩格式 工具 算法 文件扩展名 可分块. Java代码 复制代码 收藏代码. 24.        // io.compression.codecs 定义列表中的一个 . Native gzip 库减少解压缩时间在50%,压缩时间在10%(同java实现的压缩算法).

uglifyjs批量压缩js

- - JavaScript - Web前端 - ITeye博客
jquery官方使用uglifyjs进行压缩的,压缩比较高. uglifyjs的安装方法. . 前端js压缩,使用uglifyjs压缩当前目录里的所有js文件,. 压缩后,会将原文件替换为压缩过的文件.

[原][hadoop2.7.1]I/O之压缩

- - 海兰
先来看下类图(hadoop2.7.1):. 对照类图,对每一种压缩算法做个简单介绍:. hadoop2.7.1中实际上就是DefaultCodec. 它同时使用了LZ77算法与哈夫曼编码(Huffman Coding)的一个无损数据压缩算法. 人们普遍认为DEFLATE不受任何专利所制约,并且在LZW(GIF文件格式使用)相关的专利失效之前,这种格式除了在ZIP文件格式中得到应用之外也在gzip压缩文件以及PNG图像文件中得到了应用.

数据是怎么被压缩的

- skyan - 果壳网 guokr.com - 果壳网
回答问题之前先来看看什么是压缩. 当你有天走在路上,碰见熟人对你说:“吃了. ”你一定知道他是在打招呼,既不是要请客也不是让你“没吃赶紧回家吃去”. 这一句简单的“吃了”是礼貌和问好的体现,也是一种信息的压缩. 笼统地说,把一系列已有信息通过一定方法处理,使得其长度缩短,并且信息含量基本或者完全不变,就称之为压缩.

MongoSpy, MongoWatch及MongoDB数据压缩

- gOODiDEA - NoSQLFan
本文源自openmymind博客的一篇文章,文中作者介绍了两个自己用Node.JS写的MongoDB监控小工具,MongoSpy和MongoWatch,然后提出了在对MongoDB进行文本存储时使用压缩以节约空间的设想. 这两上小工具功能并不怎么强大,实现也简单,如果你会用任何一种语言操作MongoDB的话,相信你都能写一个类似的东西.