【问题标题】:Improve speed on Crc16 calculation提高 Crc16 计算的速度
【发布时间】:2010-09-28 07:17:45
【问题描述】:

我需要在大文件上使用 1021 美元的多项式计算 Crc16 校验和,下面是我当前的实现,但它在大文件上相当慢(例如,一个 90 MB 的文件大约需要 9 秒)。

所以我的问题是如何改进我当前的实现(使其更快),我用谷歌搜索并查看了一些实现表查找的示例,但我的问题是我不明白如何修改它们以包含多项式(可能我的数学不及格)。

{ based on http://miscel.dk/MiscEl/CRCcalculations.html }
function Crc16(const Buffer: PByte; const BufSize: Int64;
  const Polynom: WORD=$1021; const Seed: WORD=0): Word;
var
  i,j: Integer;
begin
  Result := Seed;

  for i:=0 to BufSize-1 do
  begin
    Result := Result xor (Buffer[i] shl 8);

    for j:=0 to 7 do begin
      if (Result and $8000) <> 0 then
        Result := (Result shl 1) xor Polynom
      else Result := Result shl 1;
    end;
  end;

  Result := Result and $FFFF;
end;

【问题讨论】:

  • 如何将文件加载到内存中?瓶颈可能是文件 IO,而不是您的 CRC 函数。即,什么是慢的:在文件上计算 CRC 的过程,或者在内存缓冲区上计算来自很久以前加载并且绝对没有被测量的源的 CRC 的过程?
  • 它被加载到一个 TMemoryStream 中(所以它是在内存中的缓冲区)所以我认为它必须是计算。

标签: delphi delphi-2010 crc


【解决方案1】:

如果你想让这个速度更快,你需要实现一个查表 CRC 算法。

A PAINLESS GUIDE TO CRC ERROR DETECTION ALGORITHMS INDEX V3.00 (9/24/96) 第10章

【讨论】:

  • +1 好文章,有助于理解查表的工作原理
【解决方案2】:

从 Jedi 代码库的 jclMath.pas 单元中查找 CRC 例程。它使用 CRC 查找表。

http://jcl.svn.sourceforge.net/viewvc/jcl/trunk/jcl/source/common/

【讨论】:

  • Jcl 实现产生与我的函数相同的结果(当我使用正确的多项式初始化表时)并且速度非常快(相同的大文件为 421 毫秒)
  • 我接受了这个答案,因为 Jcl 有一个使用查找表的 Delphi 实现。由于文章中的解释,我发现 Ira Baxter 的回答很有帮助,也感谢 Rob 的回答,因为其中有一些有用的部分。
【解决方案3】:

您的Result 变量是Word,这意味着它在进入内部循环时可能有64k 个值。计算循环可以生成的 64k 个可能结果并将它们存储在一个数组中。然后,不用为输入缓冲区的每个字节循环八次,只需在数组中查找校验和的下一个值。像这样的:

function Crc16(const Buffer: PByte; const BufSize: Int64;
  const Polynom: Word = $1021; const Seed: Word = 0): Word;
{$J+}
const
  Results: array of Word = nil;
  OldPolynom: Word = 0;
{$J-}
var
  i, j: Integer;
begin
  if (Polynom <> OldPolynom) or not Assigned(Results) then begin
    SetLength(Results, 65535);
    for i := 0 to Pred(Length(Results)) do begin
      Results[i] := i;
      for j := 0 to 7 do
        if (Results[i] and $8000) <> 0 then
          Results[i] := (Results[i] shl 1) xor Polynom
        else
          Results[i] := Results[i] shl 1;
    end;
    OldPolynom := Polynom;
  end;

  Result := Seed;
  for i := 0 to Pred(BufSize) do
    Result := Results[Result xor (Buffer[i] shl 8)];
end;

每当Polynom 发生更改时,该代码都会重新计算查找表。如果该参数在一组值之间变化,则考虑缓存您为它们生成的查找表,这样您就不会浪费时间重复计算相同的表。

如果Polynom始终为 1021 美元,那么甚至不必为它设置参数。提前计算所有 64k 值并将它们硬编码到一个大数组中,这样您的整个函数就可以简化为上面函数的最后三行。

【讨论】:

  • Rob,我只是复制/粘贴了您的代码进行测试,对于简短的测试字符串,结果与我的函数相同。但是在大文件上运行它时结果是不同的。需要进行更多测试才能了解原因
  • Rob,你的表和Jcl的表不同,Jcl表的第一行:(0, $1021, $2042, $3063, $4084, $50A5, $60C6, $70E7, $8108, $9129, $A14A, $B16B, $C18C, $D1AD, $E1CE, 表的第一行:(0, $100, $200, $300, $400, $500, $600, $700, $800, $900, $A00,
【解决方案4】:

老线程,我知道。这是我的实现(只有一个循环):

function crc16( s : string; bSumPos : Boolean = FALSE ) : Word;
var
 L, crc, sum, i, x, j : Word;

begin
  Result:=0;
  L:=length(s);
  if( L > 0 ) then
   begin
    crc:=$FFFF;
    sum:=length(s);
    for i:=1 to L do
    begin
            j:=ord(s[i]); 
            sum:=sum+((i) * j);
            x:=((crc shr 8) xor j) and $FF;
            x:=x xor (x shr 4);
            crc:=((crc shl 8) xor (x shl 12) xor (x shl 5) xor x) and $FFFF;
    end;
    Result:=crc+(Byte(bSumPos) * sum);
   end;
end;

还有一个好处是您可以使用它创建一个唯一的 id,例如获取文件名的唯一标识符,例如:

function uniqueId( s : string ) : Word;
begin
 Result:=crc16( s, TRUE );
end;

干杯, 欧文·汉杰斯

【讨论】:

  • 您的代码引发异常:第一次机会异常为 $7587C41F。带有消息“范围检查错误”的异常类 ERangeError。处理 Project1.exe (8836)
  • hmmm 奇怪,导致错误的字符串输入是什么?除了你可以禁用范围检查 {$R-},我默认关闭了它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-21
  • 1970-01-01
  • 2021-12-14
  • 2021-04-06
  • 2021-07-31
  • 1970-01-01
相关资源
最近更新 更多