工作中遇到的压缩方式总结

2022-03-28 15:14:41 浏览数 (1)

本文总结工作中使用过的数据压缩方法,主要有zlib,qatzip,igzip等 最后还进行了针对大规模数据多线程解压缩加速的分析

zlib库

zlib是用于数据压缩的函数库,使用deflate算法 deflate算法是同时使用了LZ77算法霍夫曼编码的一个无损压缩算法

主要函数有:

  • int compress (Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen); 压缩方法,将源缓冲中的数据压缩并放入目的缓冲区 注意目的缓冲区的大小有可能比压缩前还要大,因此destLen要留够空间,至少比sourceLen加12字节之后还大0.1% 返回Z_OK表示成功;Z_MEM_ERROR表示没有足够内存;Z_BUF_ERROR表示目的缓冲区不够大
  • int compress2 (Bytef *dest, uLongf *destLen,const Bytef *source, uLong sourceLen,int level); 功能与compress函数一样,增加了level参数,范围0-9,从0-9速度变慢,但压缩率提高,设置0表示不压缩,Z_DEFAULT_COMPRESSION 表示设置一个适中的参数 返回值与compress相同,多出Z_STREAM_ERROR 表示level参数无效
  • int uncompress (Bytef *dest, uLongf *destLen,const Bytef *source, uLong sourceLen); 解压缩 返回值与compress相同,多出一个Z_DATA_ERROR 表示输入数据被破坏
  • deflateInit() deflate() deflateEnd() 三个函数结合完成compress功能,参考zlib仓库example.c compress.c
  • inflateInit() inflate() inflateEnd() 三个函数完成uncompress功能
  • gz开头的函数,用来操作gz文件,类似stdio调用,如果gzopen,gzwrite等

简单的压缩示例代码:

代码语言:javascript复制
#include <zlib.h>

int gzCompress(Bytef *data, uLong ndata, Bytef *zdata, uLong *nzdata, int level)
{
	// error code 
	// Z_OK if success, Z_MEM_ERROR if there was not enough memory, 
	// Z_BUF_ERROR:-5 if there was not enough room in the output buffer, 
	// Z_STREAM_ERROR if the level parameter is invalid.
	z_stream c_stream;
	int err = 0;
	if (data && ndata > 0) 
	{
		c_stream.zalloc = NULL;
		c_stream.zfree = NULL;
		c_stream.opaque = NULL;
		if (deflateInit2(&c_stream, level, Z_DEFLATED,
			MAX_WBITS   16, 8, Z_DEFAULT_STRATEGY) != Z_OK) return -1;
		c_stream.next_in = data;
		c_stream.avail_in = ndata;
		c_stream.next_out = zdata;
		c_stream.avail_out = *nzdata;
		while (c_stream.avail_in != 0 && c_stream.total_out < *nzdata) 
		{
			try
			{
				if ((err = deflate(&c_stream, Z_BLOCK)) != Z_OK)
					return err;
			}
			catch (...)
			{
				cout << "deflate error: " << endl;
				return -1;
			}
		}
		if (c_stream.avail_in != 0) 
			return c_stream.avail_in;
		for (;;) 
		{
			if ((err = deflate(&c_stream, Z_FINISH)) == Z_STREAM_END) break;
			if (err != Z_OK) return err;
		}
		if (deflateEnd(&c_stream) != Z_OK) return -1;
		*nzdata = c_stream.total_out;
		return 0;
	}
	return -1;
}

关于压缩等级,从0-9,速度越来越慢,随之而来的是更低的压缩率

压缩文件是二进制的,由三部分组成

  1. 头信息
  2. 数据主体
  3. 校验

以下为标准格式的简要说明,详细解释可以看参考文档

qatzip库

通过硬件加速的方式进行压缩,即需要插入一张单独的intel的QAT卡;好处显而易见,正常压缩是消耗CPU资源,用另一张卡单独进行压缩,空闲出CPU资源可以进行其他计算,提高整体效率,缺点就是费钱,并占用一个PCIE插槽位置 另外只能运行于linux系统,不支持windows

qatzip_github代码仓库

简单的压缩代码:

代码语言:javascript复制
#ifdef COMPILE_QAT
#include <cpa.h>
#include <cpa_dc.h>

#include <qatzip.h>
#include <qatzip_internal.h>
#include <qz_utils.h>
extern QzSessionParams_T g_params_th;
#endif

int qzipCompress(Bytef *data, uLong ndata, Bytef *zdata, uLong *nzdata, int level)
{
#ifdef COMPILE_QAT
    QzSession_T session;
    int rc;

    rc = qzInit(&session, g_params_th.sw_backup);
    if (rc != QZ_OK && rc != QZ_DUPLICATE)
    {
        cout<<"qzInit failed, rc:"<<rc<<endl;
        return -1;
    }

    rc = qzSetupSession(&session, &g_params_th);
    if (rc != QZ_OK && rc != QZ_NO_INST_ATTACH)
    {
        cout<<"qzSetupSession failed, rc:"<<rc<<endl;
        return -1;
    }

    rc = qzCompress(&session, data, (uint32_t *)&ndata, zdata, (uint32_t *)nzdata, 1);
    if (rc != QZ_OK)
    {
        cout<<"qzCompress failed, rc:"<<rc<<endl;
        return -2;
    }

    qzTeardownSession(&session);
    return 0;
#else
    return gzCompress(data, ndata, zdata, nzdata, level);
#e

igzip库

intel工程师使用指令集优化zlib,针对genomic data比如bam sam数据,在几乎不降低压缩率的情况下,速度提升约4倍 igzip_github代码仓库 igzip的代码和isa-l代码仓库在一起

igzip使用代码示例:

代码语言:javascript复制
#include "igzip_lib.h"
int level_size_buf[10] = {
#ifdef ISAL_DEF_LVL0_DEFAULT
	ISAL_DEF_LVL0_DEFAULT,
#else
	0,
#endif
#ifdef ISAL_DEF_LVL1_DEFAULT
	ISAL_DEF_LVL1_DEFAULT,
#else
	0,
#endif
#ifdef ISAL_DEF_LVL2_DEFAULT
	ISAL_DEF_LVL2_DEFAULT,
#else
	0,
#endif
#ifdef ISAL_DEF_LVL3_DEFAULT
	ISAL_DEF_LVL3_DEFAULT,
#else
	0,
#endif
#ifdef ISAL_DEF_LVL4_DEFAULT
	ISAL_DEF_LVL4_DEFAULT,
#else
	0,
#endif
#ifdef ISAL_DEF_LVL5_DEFAULT
	ISAL_DEF_LVL5_DEFAULT,
#else
	0,
#endif
#ifdef ISAL_DEF_LVL6_DEFAULT
	ISAL_DEF_LVL6_DEFAULT,
#else
	0,
#endif
#ifdef ISAL_DEF_LVL7_DEFAULT
	ISAL_DEF_LVL7_DEFAULT,
#else
	0,
#endif
#ifdef ISAL_DEF_LVL8_DEFAULT
	ISAL_DEF_LVL8_DEFAULT,
#else
	0,
#endif
#ifdef ISAL_DEF_LVL9_DEFAULT
	ISAL_DEF_LVL9_DEFAULT,
#else
	0,
#endif

typedef unsigned long mylong;
typedef unsigned char mychar;

int igzipCompress(mychar* source, mylong source_len, mychar* dest, mylong* dest_len, 
	int level)
{
	struct isal_zstream stream; /* Holds stream information */
	struct isal_gzip_header gz_hdr;

	isal_gzip_header_init(&gz_hdr); /* Set gzip header default values */

	isal_deflate_init(&stream); /* Initialize compression stream data structure */

	int level_size = level_size_buf[level];
	unsigned char * level_buf = NULL;
	level_buf = (unsigned char*)malloc(level_size);

	stream.avail_in = 0; // Number of bytes available at next_in.
	stream.flush = NO_FLUSH;    // Flush type can be NO_FLUSH,SYNC_FLUSH or FULL_FLUSH.
	stream.level = level;   // Compression level to use.
	stream.level_buf = level_buf; // User allocated buffer required for different compression levels.
	stream.level_buf_size = level_size;   // Size of level_buf.
	stream.gzip_flag = IGZIP_GZIP_NO_HDR;   // Indicate if gzip compression is to be performed.
	stream.next_out = dest; // Next output byte.
	stream.avail_out = *dest_len;   // Number of bytes avaliable at next_out.

	isal_write_gzip_header(&stream, &gz_hdr);   /* Write gzip header to output stream. */

	stream.next_in = source;
	stream.avail_in = source_len;
	stream.end_of_stream = 1;

	int ret = isal_deflate(&stream);
	if (ret != ISAL_DECOMP_OK) {
		printf("igzip: Error encountered while compressing filen");
	}
	if (level_buf != NULL)
		free(level_buf);
	*dest_len = stream.next_out - dest;
	return ret;
}

bgzip库及多线程解压缩

bgzip:Block compression/decompression utility 用于bam/sam文件的格式,核心是将压缩数据分块(64KB),从而通过索引可以快速查询数据

注:bam/sam文件是高通量测序的标准格式文件,存储内容为fastq文件与参考基因组reference进行mapping之后的数据;其中sam为文本格式,bam为二进制格式,两者可以通过samtools工具相互转换;bam文件可以通过建立index,快速定位数据位置,从而加速访问

考虑这样一种情况,有一千个block的数据需要压缩并存放在一个文件中,这个文件可能很大,几百GB;假如我只想要分析某几个block的数据,传统的压缩方式需要将整个文件全部解压之后才能获取想要的数据,效率很低

而通过自定义压缩block的head信息,使用其中的extra filedcomment 字段就可以实现index功能,步骤如下:

压缩前,首先添加字段:comment添加block的ID,extra field添加压缩前和后的bytes大小;以zlib压缩举例

压缩后,更新extra filed中压缩前后数据长度

解压缩的时候,首先找到第一个block,读入头信息,获取当前block的标识ID,如果是想要的数据,则通过extra field获取数据长度,按照长度直接读取即可,然后跳到下一个block 因为对于无用的block数据,我们只要解析头信息,并根据长度进行偏移即可,所以遍历速度会很快 然后还可以通过多线程进行解压缩,主线程进行block的遍历,如果遇到目标数据,则从线程池中拿一个线程处理当前block 如果不需要解压缩,只是从1000个block中采样10个block进行后续的快速分析,则直接将10个block的二进制数据连续输出到磁盘文件即可,多个block可以直接cat到一起而不影响解压缩

参考文档

zlib压缩数据 zlib官网 High Performance DEFLATE Compression with Optimizations for Genomic Data Sets GZIP file format specification version 4.3 GZIP文件格式简介 zlib 1.2.11 Manual

0 人点赞