全國最多中醫師線上諮詢網站-台灣中醫網
發文 回覆 瀏覽次數:4529
推到 Plurk!
推到 Facebook!

zlib最快的压缩/解压写法

 
mustapha.wang
資深會員


發表:89
回覆:409
積分:274
註冊:2002-03-13

發送簡訊給我
#1 引用回覆 回覆 發表時間:2003-06-19 14:51:39 IP:218.1.xxx.xxx 未訂閱
首先说明,这里不是横向比较zlib与别的引擎(rar,leo,powerarc...),是探索如何发挥zlib压缩/解压的最大效率。 先看看如下代码在效率上的差异:
var MS:TMemoryStream;
(1):
begin
  MS:=TMemoryStream.Create;
  MS.Size:=$400000;//4M
------------------------------------------------
(2):
var
  i:integer;
begin
  MS:=TMemoryStream.Create;
  for i:=1 to 1024 do MS.Size:=MS.Size 4096; 
你会发现,方法(1)只要1个毫秒,方法(2)却要20秒。 因此,如果把解压缩程序写成下面这样,会非常没有效率:
procedure ZlibDeCompress(instream,outStream:TStream);
var
  ACS:TDeCompressionStream;
  buf:array[1..4096] of byte;
  numread:integer;
begin
  inStream.Position:=0;
  ACS:=TDeCompressionStream.Create(inStream);
  try
    repeat
      numRead:=ACS.Read(buf,sizeof(buf));
      if numread>0 then outStream.Write(buf,numRead);
    until (numRead=0);
  finally
    ACS.Free;
  end;
end;
如果我们知道原始资料的大小,一次确定outStream.Size,效率就可以提高几十倍。方法很简单,我们可以在压缩时,把原始资料的Size写在压缩Stream的头部,如,写一个LongWord的大小,解压时就可以先读出Size,因此,最有效率的解压程序为:
procedure ZlibDecompressStream2(Source,Dest:TMemoryStream);
var
  zstream: TZStreamRec;
  SourceLen,DestLen:LongWord;
begin
  FillChar(zstream,SizeOf(TZStreamRec),0);
  SourceLen:=Source.Size;
  Source.Position:=0;
  Source.Read(DestLen,SizeOf(LongWord));
  Dest.Size:=DestLen;
  zstream.next_in:=Pointer(LongWord(Source.Memory) SizeOf(LongWord));
  zstream.avail_in:=SourceLen-SizeOf(LongWord);
  zstream.next_out:=Dest.Memory;
  zstream.avail_out:=DestLen;
  ZDecompressCheck(InflateInit(zstream));
  try
    ZDecompressCheck(inflate(zstream,Z_NO_FLUSH));
  finally
    ZDecompressCheck(inflateEnd(zstream));
  end;
end;
用一个4M的文件试试,效率提高近70倍。 同样道理,在压缩的时候,如果能预先知道压缩后的大小,也能提高效率不少,但这似乎是不可能的,也不能盲目的给outStream.Size一个"足够大"的数值,只能按引擎的原理估算一个最接近的数值,zlib推荐的为: ((SourceLen (SourceLen div 10) 12) 255) and not 255 因此,最有效率的压缩程序为:
procedure ZlibCompressStream2(Source,Dest:TMemoryStream;
  CompressLevel:TZCompressionLevel=zcFastest);
var
  zstream: TZStreamRec;
  SourceLen,DestLen:LongWord;
begin
  FillChar(zstream,SizeOf(TZStreamRec),0);
  SourceLen:=Source.Size;
  DestLen:=SizeOf(LongWord) ((SourceLen (SourceLen div 10) 12) 255) and not 255;
  Dest.Size:=DestLen;
  Dest.Position:=0;
  Dest.Write(SourceLen,Sizeof(LongWord));
  zstream.next_in:=Source.Memory;
  zstream.avail_in:=SourceLen;
  zstream.next_out:=Pointer(LongWord(Dest.Memory) SizeOf(LongWord));
  zstream.avail_out:=DestLen-SizeOf(longWord);
  ZCompressCheck(DeflateInit(zstream,ZLevels[CompressLevel]));
  try
    ZCompressCheck(deflate(zstream,Z_FINISH));
  finally
    ZCompressCheck(deflateEnd(zstream));
  end;
  Dest.Size:=zstream.total_out SizeOf(LongWord);
end;
=============== 久病成良医-多试 發表人 - mustapha.wang 於 2003/06/19 14:55:01
------
江上何人初见月,江月何年初照人
bundur
一般會員


發表:16
回覆:44
積分:22
註冊:2002-11-30

發送簡訊給我
#2 引用回覆 回覆 發表時間:2003-06-19 23:48:24 IP:218.104.xxx.xxx 未訂閱
请问您的 ZLib 版本是多少? 我用 Delphi7.0 编译您的程序,找不到以下的东东呀! [Error] Unit1.pas(41): Undeclared identifier: 'ZDecompressCheck' [Error] Unit1.pas(41): Undeclared identifier: 'InflateInit' [Error] Unit1.pas(50): Undeclared identifier: 'TZCompressionLevel' [Error] Unit1.pas(50): Undeclared identifier: 'zcFastest' [Error] Unit1.pas(65): Undeclared identifier: 'ZCompressCheck' [Error] Unit1.pas(65): Undeclared identifier: 'DeflateInit' [Error] Unit1.pas(65): Undeclared identifier: 'ZLevels' 而且我发现 TZCompressionLevel 应该是 TCompressionLevel 等等... 我在用 Delphi6 时,用 Zlib 也做了一个类似 WinZip 的小软件,可以压缩多个文件或整个目录,可查看压缩包中的文件,及解压其中的某些文件等。 我的做法与您所讲的类似。只是在压缩的时候,我直接用了 TCompressionStream 的 CopyFrom 功能。
mustapha.wang
資深會員


發表:89
回覆:409
積分:274
註冊:2002-03-13

發送簡訊給我
#3 引用回覆 回覆 發表時間:2003-06-20 09:12:37 IP:218.1.xxx.xxx 未訂閱
我用的1.1.4版的ZlibEx.pas,旧版的写法为:
procedure ZlibCompressStream2(Source,Dest:TMemoryStream;
  CompressLevel:TCompressionLevel=clFastest);
const
  ZLevels: array [TCompressionLevel] of ShortInt =
    (Z_NO_COMPRESSION, Z_BEST_SPEED, Z_DEFAULT_COMPRESSION, 
     Z_BEST_COMPRESSION);
var
  zstream: TZStreamRec;
  SourceLen,DestLen:LongWord;
begin
  FillChar(zstream,SizeOf(TZStreamRec),0);
  SourceLen:=Source.Size;
  DestLen:=SizeOf(LongWord) ((SourceLen (SourceLen div 10) 12) 255) and not 255;
  Dest.Size:=DestLen;
  Dest.Position:=0;
  Dest.Write(SourceLen,Sizeof(LongWord));
  zstream.next_in:=Source.Memory;
  zstream.avail_in:=SourceLen;
  zstream.next_out:=Pointer(LongWord(Dest.Memory) SizeOf(LongWord));
  zstream.avail_out:=DestLen-SizeOf(longWord);
  DeflateInit_(zstream,ZLevels[CompressLevel],ZLIB_VERSION,SizeOf(TZStreamRec));
  try
    deflate(zstream,Z_FINISH);
  finally
    deflateEnd(zstream);
  end;
  Dest.Size:=zstream.total_out SizeOf(LongWord);
end;    procedure ZlibDecompressStream2(Source,Dest:TMemoryStream);
var
  zstream: TZStreamRec;
  SourceLen,DestLen:LongWord;
begin
  FillChar(zstream,SizeOf(TZStreamRec),0);
  SourceLen:=Source.Size;
  Source.Position:=0;
  Source.Read(DestLen,SizeOf(LongWord));
  Dest.Size:=DestLen;
  zstream.next_in:=Pointer(LongWord(Source.Memory) SizeOf(LongWord));
  zstream.avail_in:=SourceLen-SizeOf(LongWord);
  zstream.next_out:=Dest.Memory;
  zstream.avail_out:=DestLen;
  InflateInit_(zstream,ZLIB_VERSION,SizeOf(TZStreamRec));
  try
    inflate(zstream,Z_NO_FLUSH);
  finally
    inflateEnd(zstream);
  end;
end;
=============== 久病成良医-多试
------
江上何人初见月,江月何年初照人
bundur
一般會員


發表:16
回覆:44
積分:22
註冊:2002-11-30

發送簡訊給我
#4 引用回覆 回覆 發表時間:2003-06-20 13:08:05 IP:218.104.xxx.xxx 未訂閱
可我用你的方法,压缩任何文件,得到的都是 1k (当然是错的). 为什么? 你的 ZLib 1.1.4 版的pas在哪可以下载? 我还是使用我以前 Delphi6 中的Zlib的写法. 我本来想比较一下,哪个快,我就改用哪个. 其实,不管你一次性把源文件大小给它,它还是以每次 $F000 (即60K) 的大小处理的. 不知道我说的对不对. 而如果知道它每次处理的最大值,就可以以最佳方式做压缩并显示其进度.那不是很好吗? 还有,以TMemoryStream的方法是有缺陷的,如果一个文件很大时,如200MB,比如是SQL的备份数据等,这样的压缩过程就会死机.如果改用TFileStream的话就可以了. ============================= 我的QQ是: 83699609 發表人 - bundur 於 2003/06/20 13:14:26
mustapha.wang
資深會員


發表:89
回覆:409
積分:274
註冊:2002-03-13

發送簡訊給我
#5 引用回覆 回覆 發表時間:2003-06-20 14:46:32 IP:218.1.xxx.xxx 未訂閱
哦,对不起,我对照改了改就post上来了,没想到会有错误,修订了一下,试过了,注意看有下划线的部分。
procedure ZlibCompressStream2(Source,Dest:TMemoryStream;
  CompressLevel:TCompressionLevel=clFastest);
const
  ZLevels: array [TCompressionLevel] of ShortInt =
    (Z_NO_COMPRESSION, Z_BEST_SPEED, Z_DEFAULT_COMPRESSION, Z_BEST_COMPRESSION);
var
  zstream: TZStreamRec;
  SourceLen,DestLen:LongWord;
begin
  FillChar(zstream,SizeOf(TZStreamRec),0);
  SourceLen:=Source.Size;
  DestLen:=SizeOf(LongWord) ((SourceLen (SourceLen div 10) 12) 255) and not 255;
  Dest.Size:=DestLen;
  Dest.Position:=0;
  Dest.Write(SourceLen,Sizeof(LongWord));

  zstream.zalloc:=zlibAllocMem;
  zstream.zfree:=zlibFreeMem;

  zstream.next_in:=Source.Memory;
  zstream.avail_in:=SourceLen;
  zstream.next_out:=Pointer(LongWord(Dest.Memory) SizeOf(LongWord));
  zstream.avail_out:=DestLen-SizeOf(longWord);
  CCheck(DeflateInit_(zstream,ZLevels[CompressLevel],ZLIB_VERSION,SizeOf(TZStreamRec)));
  try
    CCheck(deflate(zstream,Z_FINISH));
  finally
    CCheck(deflateEnd(zstream));
  end;
  Dest.Size:=zstream.total_out SizeOf(LongWord);
end;    procedure ZlibDecompressStream2(Source,Dest:TMemoryStream);
var
  zstream: TZStreamRec;
  SourceLen,DestLen:LongWord;
begin
  FillChar(zstream,SizeOf(TZStreamRec),0);
  SourceLen:=Source.Size;
  Source.Position:=0;
  Source.Read(DestLen,SizeOf(LongWord));
  Dest.Size:=DestLen;

  zstream.zalloc:=zlibAllocMem;
  zstream.zfree:=zlibFreeMem;

  zstream.next_in:=Pointer(LongWord(Source.Memory) SizeOf(LongWord));
  zstream.avail_in:=SourceLen-SizeOf(LongWord);
  zstream.next_out:=Dest.Memory;
  zstream.avail_out:=DestLen;
  CCheck(InflateInit_(zstream,ZLIB_VERSION,SizeOf(TZStreamRec)));
  try
    CCheck(inflate(zstream,Z_NO_FLUSH));
  finally
    CCheck(inflateEnd(zstream));
  end;
end;
你再试试 ====================== 久病成良医--多试 千人之诺诺,不如一士之谔谔--多听取反面意见 發表人 - mustapha.wang 於 2003/06/20 14:52:24
------
江上何人初见月,江月何年初照人
mustapha.wang
資深會員


發表:89
回覆:409
積分:274
註冊:2002-03-13

發送簡訊給我
#6 引用回覆 回覆 發表時間:2003-06-20 16:08:01 IP:218.1.xxx.xxx 未訂閱
确实,这是个空间换时间的问题,用TMemoryStream表示你要把资料全部放到Memory,只适合一般大小的资料(??M以下),处理一般系统的应用也够了。 但对于文件压缩工具等应用,要处理大资料,就只能在buffer里read-write-read-write......了。 ====================== 久病成良医--多试 千人之诺诺,不如一士之谔谔--兼听
------
江上何人初见月,江月何年初照人
bundur
一般會員


發表:16
回覆:44
積分:22
註冊:2002-11-30

發送簡訊給我
#7 引用回覆 回覆 發表時間:2003-06-20 17:54:50 IP:218.104.xxx.xxx 未訂閱
引言: 确实,这是个空间换时间的问题,用TMemoryStream表示你要把资料全部放到Memory,只适合一般大小的资料(??M以下),处理一般系统的应用也够了。 但对于文件压缩工具等应用,要处理大资料,就只能在buffer里read-write-read-write......了。 ====================== 久病成良医--多试 千人之诺诺,不如一士之谔谔--兼听
在这里可以讨论一下 ZLib 我想是个好主意. 经过我的测试(测试源文件大小为19,153KB, 压缩后为 2,275KB), 我发现我原先的方法要比您提供的方法要快很多, 特别是在解压方面 以下是测试数据(单位毫秒): 次数 压 (原) 解 (原) 1 1772 1712 2013 601 2 1673 1663 2053 611 3 1712 1673 2033 601 4 1823 1692 2123 591 5 1722 1703 1682 601 6 1693 1642 1843 591 仔细研究后发现,你的方法与ZLib本身的DecompressBuf的方法相似, 我认为DecompressBuf中已经对读写的过程进行了优化了.所以直接 用它提供的类TCompressionStream才是最优化的方案. 因为我的项目中要用到它,所以我特别关心它的速度能不能提高. 使用ZLib进行处理,要比RAR和WINZIP都快好几倍到几十倍,但压缩比要比 RAR差太多了, 用最大的压缩比压出的文件比WINZIP的普通压缩比还不及. 不过对我的项目来说已经够用了. ================ 交流是进步的良师 發表人 - bundur 於 2003/06/20 18:00:45
mustapha.wang
資深會員


發表:89
回覆:409
積分:274
註冊:2002-03-13

發送簡訊給我
#8 引用回覆 回覆 發表時間:2003-06-23 11:35:30 IP:218.1.xxx.xxx 未訂閱
我认为TCompressionStream和TDecompressionStream都没有什么优化,它是为了方便我们使用而包装的,都是利用TZStreamRec调用obj里面的一些function,也是buffer read--write-read-write在作。 我们不能知晓它如何压缩/解压,只能在如何使用它的函数上来想办法,因此,最快的就是在Memory足够情况下,少作loop,直接用obj文件里的function一次完成。 你的解压程序用了600ms,而ZlibUncompressStream2用了2000ms吗?太棒了,能把你的解压代码post上来大家看看吗? (我加了你的icq,希望直接讨论一下) ====================== 久病成良医--多试 千人之诺诺,不如一士之谔谔--兼听
------
江上何人初见月,江月何年初照人
系統時間:2024-06-28 19:55:35
聯絡我們 | Delphi K.Top討論版
本站聲明
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。
2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。
3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇!