ForUtil的原理与使用

lucene 中的倒排索引DocId压缩工具类,尽量将一组连续的int类型的docId压缩存储。


原理

看了源码中的forUtil.encode的代码,着实看不太懂,等待大佬指教。

// Inspired from https://fulmicoton.com/posts/bitpacking/
// Encodes multiple integers in a long to get SIMD-like speedups.
// If bitsPerValue <= 8 then we pack 8 ints per long
// else if bitsPerValue <= 16 we pack 4 ints per long
// else we pack 2 ints per long

javadoc中说是受了https://fulmicoton.com/posts/bitpacking/启发,将多个int元素编码到long元素中, 以期获得SIMD级别的速度。
如果int元素表示所需的bit个数<=8, 可以压缩8个int到一个long中;
如果int元素表示所需的bit个数<=16, 可以压缩4个int到一个long中;
其他情况,可以压缩2个int到一个long中。

使用

lucene 中有一个关于ForUtil的测试用例,如下:

public void testEncodeDecode() throws IOException {
    final int iterations = RandomNumbers.randomIntBetween(random(), 50, 1000);
    final int[] values = new int[iterations * ForUtil.BLOCK_SIZE];
	// 共iterations个迭代组,每个迭代组中BLOCK_SIZ个int正数,每个迭代组中的最大int正数
	// 不会超过bpv个bit位来表达
    for (int i = 0; i < iterations; ++i) {
      final int bpv = TestUtil.nextInt(random(), 1, 31);
      for (int j = 0; j < ForUtil.BLOCK_SIZE; ++j) {
        // 随机int正数,正数最多占用bpv个bit(显而易见,bpv不会大于31)
        values[i * ForUtil.BLOCK_SIZE + j] = RandomNumbers.randomIntBetween(random(),
            0, (int) PackedInts.maxValue(bpv));
      }
    }

    final Directory d = new ByteBuffersDirectory();
    final long endPointer;

    {
      // encode
      IndexOutput out = d.createOutput("test.bin", IOContext.DEFAULT);
      final ForUtil forUtil = new ForUtil();

      for (int i = 0; i < iterations; ++i) {
        long[] source = new long[ForUtil.BLOCK_SIZE];
        long or = 0;
        for (int j = 0; j < ForUtil.BLOCK_SIZE; ++j) {
          source[j] = values[i*ForUtil.BLOCK_SIZE+j];
          or |= source[j];
        }
        // 通过上面的or取或,得到本次迭代组(一个BLOCK)中的最大int正数需要bpv个bit来表示
        final int bpv = PackedInts.bitsRequired(or);
        out.writeByte((byte) bpv);
        // 编码
        forUtil.encode(source, bpv, out);
      }
      endPointer = out.getFilePointer();
      System.out.println("编码使用了" + endPointer + "个字节, 编码了" + values.length + "个int正数(正常应占用" + values.length*4 + "个字节)");
      out.close();
    }

    {
      // decode 解码
      IndexInput in = d.openInput("test.bin", IOContext.READONCE);
      final ForUtil forUtil = new ForUtil();
      for (int i = 0; i < iterations; ++i) {
        final int bitsPerValue = in.readByte();
        final long currentFilePointer = in.getFilePointer();
        final long[] restored = new long[ForUtil.BLOCK_SIZE];
        forUtil.decode(bitsPerValue, in, restored);
        int[] ints = new int[ForUtil.BLOCK_SIZE];
        for (int j = 0; j < ForUtil.BLOCK_SIZE; ++j) {
          ints[j] = Math.toIntExact(restored[j]);
        }
        assertArrayEquals(Arrays.toString(ints),
            ArrayUtil.copyOfSubArray(values, i*ForUtil.BLOCK_SIZE, (i+1)*ForUtil.BLOCK_SIZE),
            ints);
        assertEquals(forUtil.numBytes(bitsPerValue), in.getFilePointer() - currentFilePointer);
      }
      assertEquals(endPointer, in.getFilePointer());
      in.close();
    }

    d.close();
  }

随机几个输出结果如下:

编码使用了48707个字节, 编码了24960个int正数(正常应占用99840个字节)
编码使用了164001个字节, 编码了82048个int正数(正常应占用328192个字节)
编码使用了48117个字节, 编码了23168个int正数(正常应占用92672个字节)

可以看出,节省了一半的存储量。


参考

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值