无损压缩算法专题——miniLZO

目录

一、miniLZO介绍

二、miniLZO的使用

三、VS2013下文件压缩和解压测试

四、STM32F103ZET6平台下的测试

五、总结

六、参考资料


一、miniLZO介绍

miniLZO是一种轻量级的压缩和解压缩库,它是基于LZO压缩和解压缩算法实现的。LZO虽然功能强大,但是编译后的库文件较大,而minilzo编译后的库则小于5kb,因此miniLZO为那些仅需要简单压缩和解压缩功能的程序而设计,所以适用于单片机等嵌入式系统使用。另外miniLZO的压缩率并不是很高,LZO算法看重的是压缩和解压的速度。

miniLZO下载地址:http://www.oberhumer.com/opensource/lzo/

 

二、miniLZO的使用

miniLZO目录结构如下:

三个头文件加一个minilzo.c文件而已,其中testmini.c是一个测试demo,给出了miniLZO的使用方法示例,testmini.c的这个例子展示了将一段内存数据进行压缩和解压的用法。如下:

/* testmini.c -- very simple test program for the miniLZO library

   This file is part of the LZO real-time data compression library.

   Copyright (C) 1996-2017 Markus Franz Xaver Johannes Oberhumer
   All Rights Reserved.

   The LZO library is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2 of
   the License, or (at your option) any later version.

   The LZO library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with the LZO library; see the file COPYING.
   If not, write to the Free Software Foundation, Inc.,
   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

   Markus F.X.J. Oberhumer
   <[email protected]>
   http://www.oberhumer.com/opensource/lzo/
 */


#include <stdio.h>
#include <stdlib.h>


/*************************************************************************
// This program shows the basic usage of the LZO library.
// We will compress a block of data and decompress again.
//
// For more information, documentation, example programs and other support
// files (like Makefiles and build scripts) please download the full LZO
// package from
//    http://www.oberhumer.com/opensource/lzo/
**************************************************************************/

/* First let's include "minizo.h". */

#include "minilzo.h"


/* We want to compress the data block at 'in' with length 'IN_LEN' to
 * the block at 'out'. Because the input block may be incompressible,
 * we must provide a little more output space in case that compression
 * is not possible.
 */

#define IN_LEN      (128*1024ul)
#define OUT_LEN     (IN_LEN + IN_LEN / 16 + 64 + 3)

static unsigned char __LZO_MMODEL in  [ IN_LEN ];
static unsigned char __LZO_MMODEL out [ OUT_LEN ];


/* Work-memory needed for compression. Allocate memory in units
 * of 'lzo_align_t' (instead of 'char') to make sure it is properly aligned.
 */

#define HEAP_ALLOC(var,size) \
    lzo_align_t __LZO_MMODEL var [ ((size) + (sizeof(lzo_align_t) - 1)) / sizeof(lzo_align_t) ]

static HEAP_ALLOC(wrkmem, LZO1X_1_MEM_COMPRESS);


/*************************************************************************
//
**************************************************************************/

int main(int argc, char *argv[])
{
    int r;
    lzo_uint in_len;
    lzo_uint out_len;
    lzo_uint new_len;

    if (argc < 0 && argv == NULL)   /* avoid warning about unused args */
        return 0;

    printf("\nLZO real-time data compression library (v%s, %s).\n",
           lzo_version_string(), lzo_version_date());
    printf("Copyright (C) 1996-2017 Markus Franz Xaver Johannes Oberhumer\nAll Rights Reserved.\n\n");


/*
 * Step 1: initialize the LZO library
 */
    if (lzo_init() != LZO_E_OK)
    {
        printf("internal error - lzo_init() failed !!!\n");
        printf("(this usually indicates a compiler bug - try recompiling\nwithout optimizations, and enable '-DLZO_DEBUG' for diagnostics)\n");
        return 3;
    }

/*
 * Step 2: prepare the input block that will get compressed.
 *         We just fill it with zeros in this example program,
 *         but you would use your real-world data here.
 */
    in_len = IN_LEN;
    lzo_memset(in,0,in_len);

/*
 * Step 3: compress from 'in' to 'out' with LZO1X-1
 */
    r = lzo1x_1_compress(in,in_len,out,&out_len,wrkmem);
    if (r == LZO_E_OK)
        printf("compressed %lu bytes into %lu bytes\n",
            (unsigned long) in_len, (unsigned long) out_len);
    else
    {
        /* this should NEVER happen */
        printf("internal error - compression failed: %d\n", r);
        return 2;
    }
    /* check for an incompressible block */
    if (out_len >= in_len)
    {
        printf("This block contains incompressible data.\n");
        return 0;
    }

/*
 * Step 4: decompress again, now going from 'out' to 'in'
 */
    new_len = in_len;
    r = lzo1x_decompress(out,out_len,in,&new_len,NULL);
    if (r == LZO_E_OK && new_len == in_len)
        printf("decompressed %lu bytes back into %lu bytes\n",
            (unsigned long) out_len, (unsigned long) in_len);
    else
    {
        /* this should NEVER happen */
        printf("internal error - decompression failed: %d\n", r);
        return 1;
    }

    printf("\nminiLZO simple compression test passed.\n");
    return 0;
}


/* vim:set ts=4 sw=4 et: */

miniLZO库使用非常简单,在压缩和解压缩之前先调用lzo_init函数进行初始化,如果该函数返回LZO_E_OK就表明没有问题可以继续操作。需要压缩数据调用lzo1x_1_compress函数,需要解压数据就调用lzo1x_decompress函数或lzo1x_decompress_safe函数。这两个函数的区别是lzo1x_decompress_safe函数会对要解压缩数据的有效性进行验证,如果验证通过才会进行解压缩操作而lzo1x_decompress函数则不会这么做,如果数据不是有效的则会产生“段错误”。建议使用lzo1x_decompress_safe函数。以下是这些函数的详细操作说明:

  • lzo1x_1_compress函数进行压缩数据操作,其需要5个参数分别是:要压缩的数据、要压缩的数据的大小(单位为字节)、压缩后数据的缓冲区、压缩后缓冲区的大小(值结果参数,调用成功之后存储实际的压缩后的数据大小)、压缩工作缓冲区。压缩数据成功之后会返回LZO_E_OK;
  • lzo1x_decompress和lzo1x_decompress_safe函数进行数据的解压缩操作,其也需要5个参数分别是:要解压缩的数据、要解压缩数据的大小(单位为字节)、解压缩后数据的存放缓冲区、原始数据(未压缩数据)大小(值结果参数,执行成功之后返回解压缩后数据的实际大小)、解压缩不需要工作缓冲区可以为NULL。执行成功返回LZO_E_OK且其第4个参数要和原始数据大小一致(这个参数以指针方式传入,传入的值不正确也会解压失败,实验过程中发现使用lzo1x_decompress函数这个值可以随便给,lzo1x_decompress_safe就不行)。
  • 压缩过程需要一个工作内存wrkmem,在例子中经过了一系列宏可以发现他被定义为了一个64KB大小的数组,需要注意的一点是这个内存要4字节对齐,例子上特意加了这么一条注释来提醒。

注意:

(1)lzo1x_1_compress函数的第4个参数是值结果参数,传进去的值是用来指示存放压缩后数据的缓冲区大小,执行成功之后通过指针返回的结果是压缩后的数据实际使用的缓冲区大小,即压缩后的数据大小。压缩后需要的数据的缓冲区的大小上限是可以根据未压缩数据大小进行计算的公式:output_block_size = input_block_size + (input_block_size / 16) + 64 + 3;

(2)lzo1x_1_compress函数的第5个参数是压缩的时候需要使用的工作缓冲区,缓冲的生成在miniLZO库的提供测试例程(testmini.c)中有相关的宏生成该缓冲区,而解压的时候就不需要缓冲区;

(3)lzo1x_decompress和lzo1x_decompress_safe解压的时候需要的第4个参数是值结果参数,传进去的值是原始的未压缩数据的大小,执行成功之后通过指针返回的是实际解压缩后的数据的大小。所以压缩之后的数据在传输的时候需要将原始数据的大小和压缩后数据一起传输,否则对方在解压缩的时候将无法解压。

 

三、VS2013下文件压缩和解压测试

1、测试函数

接下来在VS2013下写了个测试函数压缩和解压一个文件:

int test(void)
{
	FILE *fileRead;
	FILE *fileOut;
	unsigned long bufRead[1024 * 1];
	unsigned long bufWrkMem[1024 * 16];
	unsigned long bufWrite[1024 * 1 + sizeof(bufRead) / 16 + 64 + 3];
	unsigned long ret;
	unsigned long readLen;
	unsigned long writeLen;
	unsigned long param[1500];  //存放压缩后的数据长度
	unsigned long paramCnt;

	printf("bufWrkMem sizeof = %d\n", sizeof(bufWrkMem));


	fileRead = fopen("testFile.txt", "rb");
	if (NULL == fileRead)
	{
		printf("Opren Read File Fail!!!\n");
	}
	fileOut = fopen("testFileCompress.txt", "wb");

	if (lzo_init() != LZO_E_OK)
	{
		printf("internal error - lzo_init() failed !!!\n");
		printf("(this usually indicates a compiler bug - try recompiling\nwithout optimizations, and enable '-DLZO_DEBUG' for diagnostics)\n");
		return 3;
	}

	paramCnt = 0;

	//压缩
	while ((readLen = fread(bufRead, 1, sizeof(bufRead), fileRead)) > 0)
	{
		ret = lzo1x_1_compress(bufRead, readLen, bufWrite, &writeLen, bufWrkMem);

		if (ret == LZO_E_OK)
		{
			fwrite(bufWrite, 1, writeLen, fileOut);
			param[paramCnt++] = writeLen;
			printf("compressed %lu bytes into %lu bytes\n", (unsigned long)readLen, (unsigned long)writeLen);
		}
		else
		{
			/* this should NEVER happen */
			printf("internal error - compression failed: %d\n", ret);
			return 2;
		}
	}

	param[paramCnt++] = sizeof(bufRead);

	fclose(fileRead);
	fclose(fileOut);

	fileRead = fopen("testFileCompress.txt", "rb");
	if (NULL == fileRead)
	{
		printf("Opren Read File Fail!!!\n");
	}
	fileOut = fopen("testFileDecompress.txt", "wb");

	paramCnt = 0;

	readLen = param[paramCnt++];

	//解压
	while ((fread(bufRead, 1, readLen, fileRead)) > 0)
	{
		ret = lzo1x_decompress(bufRead, readLen, bufWrite, &writeLen, NULL);
		if (ret == LZO_E_OK)
		{
			fwrite(bufWrite, 1, writeLen, fileOut);
			printf("decompressed %lu bytes back into %lu bytes\n", (unsigned long)readLen, (unsigned long)writeLen);
			readLen = param[paramCnt++];
		}
		else
		{
			/* this should NEVER happen */
			printf("internal error - decompression failed: %d\n", ret);
			
			return 1;
		}
	}

	fclose(fileRead);
	fclose(fileOut);

	return 0;
}

测试函数里我用的lzo1x_decompress来解压数据,所以第四个参数“压缩前的大小”随便传,因此在程序中我只记录了每一个数据块压缩后的数据大小。压缩函数所需要的工作内存wrkmem我也没有用默认的,而是创建了一个64KB的数组给它调用。下面是程序的执行输出(太长了,只截取一段):

bufWrkMem sizeof = 65536
compressed 4096 bytes into 1238 bytes
compressed 4096 bytes into 795 bytes
compressed 4096 bytes into 1306 bytes
compressed 4096 bytes into 1348 bytes
compressed 4096 bytes into 1323 bytes
compressed 4096 bytes into 1296 bytes
compressed 4096 bytes into 1289 bytes
compressed 4096 bytes into 748 bytes
compressed 4096 bytes into 691 bytes
compressed 4096 bytes into 953 bytes

/*************** 此处省略一大段 **************/

compressed 4096 bytes into 1296 bytes
compressed 4096 bytes into 1334 bytes
compressed 4096 bytes into 1284 bytes
compressed 4096 bytes into 1347 bytes
compressed 4096 bytes into 1303 bytes
compressed 4096 bytes into 1357 bytes
compressed 4096 bytes into 1314 bytes
compressed 4096 bytes into 1310 bytes
compressed 4096 bytes into 1308 bytes
compressed 4096 bytes into 1336 bytes
compressed 320 bytes into 181 bytes
decompressed 1238 bytes back into 4096 bytes
decompressed 795 bytes back into 4096 bytes
decompressed 1306 bytes back into 4096 bytes
decompressed 1348 bytes back into 4096 bytes
decompressed 1323 bytes back into 4096 bytes
decompressed 1296 bytes back into 4096 bytes
decompressed 1289 bytes back into 4096 bytes
decompressed 748 bytes back into 4096 bytes
decompressed 691 bytes back into 4096 bytes
decompressed 953 bytes back into 4096 bytes

/*************** 此处省略一大段 **************/

decompressed 1296 bytes back into 4096 bytes
decompressed 1334 bytes back into 4096 bytes
decompressed 1284 bytes back into 4096 bytes
decompressed 1347 bytes back into 4096 bytes
decompressed 1303 bytes back into 4096 bytes
decompressed 1357 bytes back into 4096 bytes
decompressed 1314 bytes back into 4096 bytes
decompressed 1310 bytes back into 4096 bytes
decompressed 1308 bytes back into 4096 bytes
decompressed 1336 bytes back into 4096 bytes
decompressed 181 bytes back into 320 bytes

下面是原始文件、压缩后的文件和解压后的文件:

2、数据比较工具Beyond Compare

可以看到从5021KB压缩到了1612KB。解压后的文件“testFileDecompress.txt”和原始文件“testFile.txt”的大小是一样的,当然不能说大小一样就认为解压成功了,还要真实去比较一下数据是不是真的一致。这里推荐一个数据比较的工具“Beyond Compare

 这个工具并不大,非常好用,平常公司同事之间互相同步代码也会用到这个工具,网上搜一下就能下载到。

 安装好工具之后,选中要比较的两个文件右键出现一个比较选项,点击出现一个界面,选择“十六进制比较”,然后选择“打开视图”。

 两个文件有数据不同的话工具会自动把不同的部分标成红色的,没看到红色的地方的话说明两个文件内容完全一样。从上图可以得知miniLZO的压缩和解压并没有问题。

 

3、实验分析

因为上面用的压缩方法是将原始文件切成很多份4KB的数据块为一个单位来进行压缩的,如果每一份的大小再调大些的话,压缩后的文件会更小的。既然用miniLZO,那肯定是看中它的“轻量型”了,一般都用在单片机这种RAM小的平台上,所以用到这个库对数据进行压缩,在待压缩数据较大的情况下,肯定是要把待压缩数据切成一块块的进行压缩,压缩后的数据也需要一块块的存储,上述压缩后的文件就是这样存的,解压再根据已有的每份的压缩大小去还原原始数据。

解压函数是必须要精准知道当前解压的数据是多大的,一次只能给一个压缩块给它解,给多个解不出来,试过了如果解压的这个块有2752字节,那么待解压数据大小(第二个形参)传成了2753也是解压报错的。所以压缩的时候还需要保存每个压缩块的数据大小。

按照上面的用法如果移植到单片机上的话,我们算算需要开辟一片多大的RAM空间。工作内存wrkmem要64KB、待压缩数据区4KB、解压数据区5.3KB,每个块的压缩长度记录数组5.9KB(当然这个会根据实际压缩数据的大小改变)。所以一共用了79.2KB的RAM空间,对于普通的单片机来说这个内存需求算挺大的了,考虑到待压缩数据缓存和解压数据缓存是可以根据实际情况再改小点的,只不过会损失掉一点压缩率,所以再综合考虑一下得要个70KB的空间吧,这样还是不太能接受。说白了就是工作内存wrkmem要的空间太大了,那么工作内存wrkmem可以给小一点吗?针对上面的文件压缩测试,我把wrkmem砍了一半的空间变成了32KB大小,发现程序运行没有问题。那再砍一半变成16KB大小,程序报错了,在如下函数挂了:

/*
*    minilzo.c的4395~4407行代码
*/

LZOLIB_PUBLIC(lzo_hvoid_p, lzo_hmemset) (lzo_hvoid_p s, int cc, lzo_hsize_t len)
{
#if (LZO_HAVE_MM_HUGE_PTR) || !(HAVE_MEMSET)
    lzo_hbyte_p p = LZO_STATIC_CAST(lzo_hbyte_p, s);
    unsigned char c = LZO_ITRUNC(unsigned char, cc);
    if __lzo_likely(len > 0) do
        *p++ = c;
    while __lzo_likely(--len > 0);
    return s;
#else
    return memset(s, cc, len);    //程序挂在了这里
#endif
}

程序在执行return memset(s, cc, len)的时候报错,看了下调用栈,找到了一个上一级的调用函数lzo_memset(wrkmem, 0, ((lzo_uint)1 << D_BITS) * sizeof(lzo_dict_t))。在这里D_BITS为宏定义14,sizeof(lzo_dict_t)的大小是2,即此处将wrkmem的内存前32KB清0,因为我们刚刚只给了16KB的空间,所以导致运行报错。突然又想了一下,为什么前面例子申请64KB空间,这里给我的一种感觉实际只用了32KB,而用多少空间和D_BITS这个宏有关,看了下源代码D_BITS这个宏貌似会影响算法的字典的大小,可以从6到19变化,而且数字越大压缩后的文件越小,相当于是个压缩等级吧。这么看的话wrkmem最小可以给到2^6*2=128字节的大小,这个内存对于大部分的单片机来说都可以接受了。

 

四、STM32F103ZET6平台下的测试

上面只是Windows下的测试,一切条件都很理想,咱得实际到单片机的平台去试试,是骡子是马拉出来溜溜。测试平台是STM32F103ZET6,CPU主频72MHz,测试代码如下:

#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "minilzo.h"
#include <stdlib.h>

void TIM4_Init(u16 per,u16 psc);

#define TEST_BUF_SIZE	(1024 * 1)
#define	WRKMEM_BUF_SIZE	(1 << (11 + 1))

#define BUF_UNIT_NUM	(TEST_BUF_SIZE/4)
#define WRKMEM_UNIT_NUM	(WRKMEM_BUF_SIZE/4)

u32 dwWrkmemBuf[WRKMEM_UNIT_NUM];
u32 dwRawDatBuf[BUF_UNIT_NUM];
u32 dwCompressBuf[BUF_UNIT_NUM + (sizeof(dwRawDatBuf) / 16 + 64 + 3) / 4];
u32 dwDecompressBuf[BUF_UNIT_NUM];

int test(void)
{
	u32 dwCompressLen;
	u32	dwDecompressLen;
	u32 dwRet;
	u32 i,j;
	u32 dwRnd;
	
	//生成随机数
	for(i = 0; i < sizeof(dwRawDatBuf) / sizeof(dwRawDatBuf[0]); )
	{
		dwRnd = rand();
		for(j = 0; j < 10; j++)
		{
			dwRawDatBuf[i + j] = dwRnd;
			i++;
		}
	}
	
	//开启定时器记时
	TIM4->CNT = 0;
	TIM_Cmd(TIM4,ENABLE); 
	
	//压缩
	dwRet = lzo1x_1_compress((const lzo_bytep)dwRawDatBuf, sizeof(dwRawDatBuf), (lzo_bytep)dwCompressBuf, (lzo_uintp)&dwCompressLen, dwWrkmemBuf);
	TIM_Cmd(TIM4,DISABLE); 
	if (dwRet == LZO_E_OK)
	{
		printf("compressed %lu bytes into %lu bytes, time = %d us\r\n", (unsigned long)sizeof(dwRawDatBuf), (unsigned long)dwCompressLen, TIM4->CNT);
	}
	else
	{
		/* this should NEVER happen */
		printf("internal error - compression failed: %d\r\n", dwRet);
		return 2;
	}
	
	//开启定时器记时
	TIM4->CNT = 0;
	TIM_Cmd(TIM4,ENABLE); 
		
	//解压
	dwRet = lzo1x_decompress((const lzo_bytep)dwCompressBuf, dwCompressLen, (lzo_bytep)dwDecompressBuf, (lzo_uintp)&dwDecompressLen, NULL);
	TIM_Cmd(TIM4,DISABLE); 
	if (dwRet == LZO_E_OK)
	{
		printf("decompressed %lu bytes back into %lu bytes, time = %d us\r\n", (unsigned long)dwCompressLen, (unsigned long)dwDecompressLen, TIM4->CNT);
	}
	else
	{
		/* this should NEVER happen */
		printf("internal error - decompression failed: %d\r\n", dwRet);
		
		return 1;
	}
	
	//检查原始数据和解压后的数据是否一致
	for(i = 0; i < sizeof(dwRawDatBuf) / sizeof(dwRawDatBuf[0]); i++)
	{
		if(dwRawDatBuf[i] != dwDecompressBuf[i])
		{
			printf("Decompress Fail !!!\r\n");
		}
	}
		
	return 0;
}

int main()
{
	SysTick_Init(72);
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  //中断优先级分组 分2组
	LED_Init();
	USART1_Init(57600);
	printf("开始测试\r\n");
	TIM4_Init(60000,72-1);  
	
	while(1)
	{
		test();
		led2=!led2;
	}
	
}

/*******************************************************************************
* 函 数 名         : TIM4_Init
* 函数功能		   : TIM4初始化函数
* 输    入         : per:重装载值
					 psc:分频系数
* 输    出         : 无
*******************************************************************************/
void TIM4_Init(u16 per,u16 psc)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);//使能TIM4时钟
	
	TIM_TimeBaseInitStructure.TIM_Period=per;   //自动装载值
	TIM_TimeBaseInitStructure.TIM_Prescaler=psc; //分频系数
	TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //设置向上计数模式
	TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitStructure);
	
	TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE); //开启定时器中断
	TIM_ClearITPendingBit(TIM4,TIM_IT_Update);
	
	NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;//定时器中断通道
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;		//子优先级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	
	
	TIM_Cmd(TIM4,DISABLE); //使能定时器	
}

/*******************************************************************************
* 函 数 名         : TIM4_IRQHandler
* 函数功能		   : TIM4中断函数
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/
void TIM4_IRQHandler(void)
{
	printf("goto IRQ\r\n");
	if(TIM_GetITStatus(TIM4,TIM_IT_Update))
	{
		led2=!led2;
	}
	TIM_ClearITPendingBit(TIM4,TIM_IT_Update);	
}

上述测试程序把miniLZO的D_BITS设置为了11,所以这里设置了一个4KB大小的dwWrkmemBuf作为工作内存。开启了一个定时器,1微秒计数一次,用来记录压缩和解压的时间。其他用到的内存情况如下(map文件信息里的):

dwWrkmemBuf                              0x20000020   Data        4096  main.o(.bss)
dwRawDatBuf                              0x20001020   Data        1024  main.o(.bss)
dwCompressBuf                            0x20001420   Data        1152  main.o(.bss)
dwDecompressBuf                          0x200018a0   Data        1024  main.o(.bss)

整个工程编译后占的RAM空间也才8KB左右,完全可以被单片机接受。

下面看下打印输出:

开始测试
goto IRQ
compressed 1024 bytes into 279 bytes, time = 711 us
decompressed 279 bytes back into 1024 bytes, time = 83 us
compressed 1024 bytes into 271 bytes, time = 711 us
decompressed 271 bytes back into 1024 bytes, time = 83 us
compressed 1024 bytes into 279 bytes, time = 712 us
decompressed 279 bytes back into 1024 bytes, time = 83 us
compressed 1024 bytes into 274 bytes, time = 710 us
decompressed 274 bytes back into 1024 bytes, time = 83 us
compressed 1024 bytes into 275 bytes, time = 711 us
decompressed 275 bytes back into 1024 bytes, time = 83 us
compressed 1024 bytes into 277 bytes, time = 711 us
decompressed 277 bytes back into 1024 bytes, time = 83 us
compressed 1024 bytes into 278 bytes, time = 712 us

可以看到,对于上面的例子,原始数据是每10个数据单位重复一次,1KB的数据压缩平均花了711微秒,再解压出来花了83微秒,这个速度感觉还行,压缩到了275字节还不错。接下来试了下原始数据dwRawDatBuf全部填充0,然后去做压缩和解压,性能如下:

开始测试
goto IRQ
compressed 1024 bytes into 32 bytes, time = 405 us
decompressed 32 bytes back into 1024 bytes, time = 51 us
compressed 1024 bytes into 32 bytes, time = 405 us
decompressed 32 bytes back into 1024 bytes, time = 51 us
compressed 1024 bytes into 32 bytes, time = 405 us
decompressed 32 bytes back into 1024 bytes, time = 51 us
compressed 1024 bytes into 32 bytes, time = 406 us
decompressed 32 bytes back into 1024 bytes, time = 50 us
compressed 1024 bytes into 32 bytes, time = 406 us
decompressed 32 bytes back into 1024 bytes, time = 50 us
compressed 1024 bytes into 32 bytes, time = 406 us
decompressed 32 bytes back into 1024 bytes, time = 51 us
compressed 1024 bytes into 32 bytes, time = 405 us
decompressed 32 bytes back into 1024 bytes, time = 51 us

从上面数据可以知道数据规律性越强,压缩解压速度越快,压缩能力越强。接下来再试一下往dwRawDatBuf填充完全的随机数:

开始测试
goto IRQ
compressed 1024 bytes into 1032 bytes, time = 648 us
decompressed 1032 bytes back into 1024 bytes, time = 46 us
compressed 1024 bytes into 1032 bytes, time = 648 us
decompressed 1032 bytes back into 1024 bytes, time = 46 us
compressed 1024 bytes into 1032 bytes, time = 648 us
decompressed 1032 bytes back into 1024 bytes, time = 46 us
compressed 1024 bytes into 1032 bytes, time = 648 us
decompressed 1032 bytes back into 1024 bytes, time = 46 us
compressed 1024 bytes into 1032 bytes, time = 648 us
decompressed 1032 bytes back into 1024 bytes, time = 46 us

可以看出来,全是随机数时,因为数据难以压缩,压缩后的数据反而更大了,但是有一个特点,压缩和解压速度仍然很快。

 

五、总结

本文分析了miniLZO库的移植和使用,分析了压缩和解压函数的调用方式及参数的注意点,通过在VS下进行实验发现了通过更改源代码里面D_BITS这个宏定义可以调整算法所需要的工作内存的大小,从而能够更灵活的适应单片机平台的需求。接着推荐了一个非常好用的数据对比工具Beyond Compare然后又将miniLZO库移植到STM32单片机上进行了实际的测试,从几个方面验证了miniLZO的压缩性能。最后总结一下miniLZO的优点和缺点:

优点:

(1)miniLZO库在压缩时拥有较好的时间性能,通常情况下都会有很快压缩和解压速度。

(2)miniLZO可以调整工作缓存(wrkmem)的大小,从而适应不同的嵌入式平台。

(3)miniLZO库代码量不多,很适合单片机这类ROM空间小的平台使用。

缺点:

(1)数据需要分块压缩,而且在解压时必须精确知道压缩块是多大的,不能实现数据流的连续压缩。举个例子,对于一个比较大的文件而言,我们需要人为把文件切成很多份来进行压缩,从压缩算法的角度上来说,相当于丢掉了各部分之间的关联性信息,表现为每次都要重新构建新的字典,那么压缩率必定不如连续数据流的压缩。我想着每次压缩可以不必严格一块一块数据分出来压缩,而是前一块数据压缩完后可以不用将前一次的字典信息等缓存清除,然后下一块数据可以继续利用上一次数据的字典信息等,这样就能再提高压缩率了,这个功能可以作为一个选项被用户选择。

总的来说,miniLZO还是很棒的。本文所述如有不对的地方,欢迎批评指正。

 

六、参考资料

文章:

《发一个单片机可用的无损数据压缩算法(miniLZO)源码及测试工程》 http://www.embed-net.com/forum.php?mod=viewthread&tid=192

《基于STM32的无损压缩算法miniLZO移植》 https://cloud.tencent.com/developer/article/1488747

《miniLZO的基本使用》 https://www.ubuntukylin.com/ukylin/forum.php?mod=viewthread&tid=4529

《LZO和MiniLZO编码介绍》 https://www.xuebuyuan.com/1596027.html

《LZO使用和介绍》 https://www.cnblogs.com/fnlingnzb-learner/p/5843367.html

《LZO实现代码参考》 https://www.codingnow.com/windsoul/package/lzoc.htm

 

资料:

《minilzo-2.10.tar.gz》

《STM32F1_miniLZO测试工程》

链接:https://pan.baidu.com/s/1saC4-pXI59uD3ssIMSyIrg  提取码:3m19

原创文章 22 获赞 29 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_34254642/article/details/104717110