Java提供了四种类型来存储一个整型:Byte,short,int,long。但是如果整数的范围在[0,100000],那么只需要17bits就足够存储了,因为2^17=131072。但是,你不能够选择short来存储,因为short存储[65536,100000]之间的数会溢出。如果你用int来存储,那么每个数至少要浪费15bits的空间,大约47%的内存空间。

Lucene/Solr4.0最值得期待一个改进就是内存效率。经过一些基准测试发现,相对Lucene3.x而言,像Solr或者ElasticSearch这类基于Lucene的应用可以减少2/3的内存使用。Lucene中用到的一项技术就是位压缩(bit-packing).这意味着整型数组的类型从固定大小(8,16,32,64位)4种类型,扩展到了[1-64]位共64种类型。如果用这种方式存储17-bits大小的整型,那么相对int[]而言,会节约47%的内存。

Bit-packing的接口定义如下:

interface Mutable {

longget(int index);

voidset(int index, long value);

int size();

}

在这个接口下面,有4种不同内存效率和运行效率的实现。

1.    Direct8, Direct16, Direct32 and Direct64 ,只是 byte[], short[],int[],long[]的一个简单包装,

2.    Packed64, 以块连续的方式把数据存储到 64-bits (long类型) 大小的内存块里面。一个数值可能会跨两个内存块。

3.    Packed64SingleBLock,看起来像 Packed64,但实际上利用位填充来做代替跨多个块的数据存储。 (每个值最大占用32 bits),

4.    Packed8ThreeBlocks和 Packed16ThreeBlocks, 用 3 bytes (每个值24 bits) or 3 shorts (每个值48 bits)来存储数据。

具体的实现可以参看Lucene的源代码。

Direct{8,16,32,64}

这些类的方法直接把操作映射到对应的数组里:

§  Direct8: byte[],

§  Direct16: short[],

§  Direct32: int[],

§  Direct64: long[].

这些类的执行效率会非常高,但是缺点也是相当明显。如果要存储17-bits的数,就需要用Direct32,内存依然浪费了47%。

Packed64

这种实现方式把数值依次存储到64-bits 块中。这种实现方式是最紧凑的,如果要存储100万的17-bits数值,就只需要17*1000000/8 ~= 2MB内存空间。唯一的一个缺陷就是一些数值会跨越两个不同的Blocks。为了提高程序运行效率,在编码实现上就需要一些技巧,比如以不同的偏移量和掩码值更新两个Block.

Packed64SingleBlock

这种实现和Packed64相似,但是一个数值不会跨越多个Block。例如:如果要存储21-bits数值,那么一个64-bits Block只能存储3个数值,剩下的1bit就浪费了(大约有2%的空间损失)。下表是类中已经实现的N-bits数值存储表

Bits per value

Values per  block

Padding bits

Space loss

32

2

0

0%

21

3

1

2%

16

4

0

0%

12

5

4

6%

10

6

4

6%

9

7

1

2%

8

8

0

0%

7

9

1

2%

6

10

4

6%

5

12

4

6%

4

16

0

0%

3

21

1

2%

2

32

0

0%

1

64

0

0%

Packed{8,16}ThreeBlocks

这种类型的接口实现是用3-bytes或者3-shorts来存储一个数值。所以,它们只适用于24-bits 或者 48-bits数值。但是其最大值是Integer.MAX_VALUE/3,所以保存这些数值的数组用一个int寻址就可以了。

性能比较

与byte,short,int.,long这些原生的存储方式相比,内存空间利用率有非常大的提高,但是执行时间呢?直觉告诉我们会有一定的差距,但是差距有多大呢?Lucene和核心开发者Adrien Grand经过测试,得出的结论如下:Direct*接口实现比Packed64快3倍,比如Packed64SingleBlock快2倍,然而,有趣的是Packed*ThreeBlocks接口实现几乎跟Direct*一样快。但是当数据大小只有1bit或者2bits时,由于CPU缓存的缘故,Packed64 and Packed64SingleBLock运行效率快很多。

这些实现方式的读写效率如何呢?Lucene 核心开发者 Daniel Lemire作了一个测试,用C++实现的程序,用GNU GCC4.6.2 compiler对程序进行了优化。程序运行在macbook air(Inter core i7)上。他得出了如下的结论:一般而言,bit宽度越小,unpacking(读操作)越快。Packing(写操作)速率在bit-witdh=8或者bit-width=16时会更快一些。如下图:

尽管bit-packing技术可以明显地减少程序中的内存的使用,但是很少有人在程序中使用。这是因为:1、大多数应用中,开发者并不知道每个数值究竟需要多少bits。2、8,16,32,64 bits的数据类型是编程语言内置的,但是bit-packing需要额外的编码处理。但是不管怎样,bit-packing技术在降低内存使用上是十分出色的,特别是读操作上,而且并没有过多地损失性能。

前面的内容算是翻译的吧,反正是别人的东西,我只是用我自己的理解或者仅仅是中文转述了一遍。下面的内容则是根据代码领悟出来的东西。以Packed64SingleBlock4为例,研究Lucene中到底是如何实现bit-packing的。

Packed64SingleBlock4是把一个long类型(64-bit block)以4-bit为单位,分成了16个格子。每个格子可以存储的最大值是15. 其实现的代码如下:

存储的规则是:低对低,高对高:

开始存储4-bit[0]=15时,blocks[0]=0x000000000000000f=15

然后存储4-bit[1]=15时,blocks[0]= 0x00000000000000ff=255

然后存储4-bit[2]=15时,blocks[0]= 0x0000000000000fff=4095

……

然后存储4-bit[15]=15时,blocks[0]=0xffffffffffffffff=-1

然后存储4-bit[16]=15时,blocks[1]= 0x000000000000000f=15

即下标小的在long的低位,下标大的排列的long的高位。

注释后的代码如下:

@Override

publiclongget(int index) {

//定位到blocks中相应的64-bits槽中

//notice: index >>> 4 = index/64

finalint o = index >>> 4;

//定位到64-bits槽相应的格子中,一个64-bits槽只有16个格子

finalint b = index & 15;

//确定格子的偏移位置,(一个格子要占4-bits)

//notice: b<< 2 = b * 4

finalint shift = b << 2;

//通过   blocks[o] >>> shift 去除低位的内容

//通过 &15L 去除高位的内容,这样就正好取得4-bits格子里面的数值

return(blocks[o] >>> shift) & 15L;

}

@Override

publicvoidset(int index, longvalue) {

//与get方法相同

finalint o = index >>> 4;

finalint b = index & 15;

finalint shift = b << 2;

/*

* 15L << shift 表示把清空滑窗移动到对应的格子位置

* blocks[o] & ~(15L << shift) 其实是两步:第一步 ~(15L << shift) 得到的结果就是 000011…1;第二步 blocks[o] & ~(15L << shift)就正好把4-bits格子清空

* value<< shift 表示把数值滑窗移动到对应的格子位置,

由于前面已经清空的格子的内容, (blocks[o] & ~(15L << shift)) | (value << shift) 用“或”操作就正好可以把数值写入到格子中

* */

blocks[o] = (blocks[o] & ~(15L << shift)) | (value << shift);

}

代码关键的地方在于位操作,如果对位操作不了解,那么理解起来就会很困难。

参考博客:

http://blog.jpountz.net/post/25530978824/how-fast-is-bit-packing

http://lemire.me/blog/archives/2012/03/06/how-fast-is-bit-packing/

转载于:https://blog.51cto.com/sbp810050504/1531624

Lucene源代码学习之 PackedInts相关推荐

  1. lucene源代码学习之 lucene的经典打分过程

    Lucene中默认的打分模型是VSM(Vector Space Model),其打分公式如下: 看到很多文章都是对这个公式进行解析,但问题的关键在于看了一大段的解析之后,依然不懂其中的细节.我们直接从 ...

  2. php7.0 cli,PHP-7.1 源代码学习:php-cli 启动流程

    前言 php cli main 函数 configure & make 默认构建目标为 php-cli,相关代码在 sapi/cli 目录下,php_cli.c 文件中能够找到 main(入口 ...

  3. eos 源代码学习笔记一

    文章目录 eos 源代码学习笔记 1.eos 中的常见合约类型 2.语言环境局部( locale )变量的使用简介(目的是通过 gettext 软件包 来实现软件的全球化) 3.eos 源代码的一些优 ...

  4. BT源代码学习心得(十):客户端源代码分析(相关对象一览) -- 转贴自 wolfenstein (NeverSayNever)

    BT源代码学习心得(十):客户端源代码分析(相关对象一览) Author:wolfenstein(NeverSayNever), BitTorrent/download.py中的Multitorren ...

  5. BT源代码学习心得(一):总体描述 -- 转贴自wolfenstein (NeverSayNever)

    BT源代码学习心得(一):总体描述 发信人: wolfenstein (NeverSayNever), 个人文集 标  题: BT源代码学习心得(一):总体描述 发信站: 水木社区 (Fri Jul ...

  6. BT源代码学习心得(七):跟踪服务器(Tracker)的代码分析(HTTP协议处理对象) -- 转贴自 wolfenstein (NeverSayNever)

    BT源代码学习心得(七):跟踪服务器(Tracker)的代码分析(HTTP协议处理对象) author: wolfenstein (NeverSayNever) 上次我们分析了Tracker类初始化的 ...

  7. BT源代码学习心得(二):程序运行参数的获取 -- 转贴自 wolfenstein (NeverSayNever)

    BT源代码学习心得(二):程序运行参数的获取 发信人: wolfenstein (NeverSayNever), 个人文集 标  题: BT源代码学习心得(二):程序运行参数的获取 发信站: 水木社区 ...

  8. BT源代码学习心得(四):种子文件的生成 -- 转贴自wolfenstein (NeverSayNever)

    BT源代码学习心得(四):种子文件的生成 author: wolfenstein 在知道种子文件采取的编码方式后,我们现在可以来看一个种子文件具体是如何生成的了.在BT中,生成种子文件的可执行模块是 ...

  9. BT源代码学习心得(三):种子文件的编码方式 -- 转贴自wolfenstein (NeverSayNever)

    BT源代码学习心得(三):种子文件的编码方式 author: wolfenstein BT的作者使用了一种比较简单易懂的编码方式来对设计种子文件.这种编码方式能够很简单得对python中的各种数据类型 ...

最新文章

  1. API Hooking 的原理
  2. VC++把输入的字符转换为十六进制
  3. 【opencv】5.cv::findContours和cv::drawContours()
  4. C# 操作XML入门
  5. python和matlab交互_MATLAB调用python,交互
  6. centos6安装mysql并远程连接_如何开启phpstudy中mysql的远程连接
  7. nginx的脚本引擎(一)
  8. 2.2_ 4_ FCFS、SJF、 HRRN调度算法
  9. knn机器学习算法_K-最近邻居(KNN)算法| 机器学习
  10. 学python电脑硬件_Python实现的读取电脑硬件信息功能示例
  11. 35岁-59岁-人生的二个世界
  12. CentOS上安装多版本Python问题
  13. java 获取excel的行数_关于Java:如何使用POI库获取Excel文件中的行数?
  14. 4、智慧变电站 - 外围电塔及电线绘制
  15. cs224w(图机器学习)2021冬季课程学习笔记15 Frequent Subgraph Mining with GNNs
  16. python 多因素方差分析_SPSS分析技术:多元方差分析
  17. SQL中where in的用法
  18. Lua:协程,coroutine.create,coroutine.resume, coroutine.yield
  19. 【转】Mac 下钥匙串不能授权访问怎么解决--不错
  20. Java中如何不用中间变量来使两个变量交换值

热门文章

  1. ThinkSNS+ 是如何计算字符显示长度的
  2. 没有JS的前端:体积更小、速度更快!
  3. 11月百度面试题(社招)
  4. 论SOA架构的几种主要开发方式
  5. Jsp内置对象及EL表达式的使用
  6. Intel芯片组命名规则
  7. go 用的不多的命令
  8. 《黑客与画家》读后感:你对技术一无所知(一些金句)
  9. 用 JS 做一个数独游戏(二)
  10. Python:打印目录下最大的十个文件