Forum: PC Hard- und Software Schnelles CRC16


von Harper B. (harper)


Lesenswert?

Ich habe eine Routine geschrieben, die ein CRC16 korrekt berechnet. 
Diese Routine compiliere ich mit dem Microsoft C++ Compiler von VS2005. 
Programmversion cl.exe: 14.00.50727.762

Die Routine arbeitet zwar, könnte aber durchaus schneller sein.

Der Code sieht so aus:
1
//  CRC16/CCITT
2
//  width: 16
3
//  polynome: 0x1021 (x16 + x12 + x5 + 1)
4
//  reflect: yes (bit transmission order: 0,1,2,3,4,5,6,7)
5
static unsigned short CRCtable[] = {
6
    0, 4489, ... 
7
};
8
9
#pragma optimize("gt", on)
10
11
// \brief calculate the CRC 
12
// \param pImageStart pointer to first byte
13
// \param pImageEnd pointer beyond the last last
14
// \param wOldCRC CRC start value, usually 0xFFFF
15
// \return new CRC value
16
WORD CRC16(const BYTE* pImageStart, const BYTE* pImageEnd, WORD wOldCRC)
17
{
18
  WORD wCRC;
19
  WORD w;
20
  const BYTE* pData;
21
22
  wCRC = wOldCRC;
23
  pData = pImageStart;
24
25
  while (pData < pImageEnd)
26
  {
27
    w = (wCRC ^ *pData++) & 0xFF;
28
    wCRC = wCRC >> 8 ^ CRCtable[w];
29
  }
30
  return wCRC;
31
}

Der Compiler erzeugt daraus diesen Code:
1
CRC16:
2
    ; load parameters
3
    mov     ecx, DWORD PTR _pImageStart$[esp-4]
4
    movzx   eax, WORD PTR _wOldCRC$[esp-4]
5
    push    esi
6
    mov      esi, DWORD PTR _pImageEnd$[esp]
7
8
    ; initial check (pImageStart < pImageEnd)
9
    cmp      ecx, esi
10
11
12
    jae      SHORT $LN1@CRC16
13
14
    ; ----- calculation loop -----
15
    movzx   edx, BYTE PTR [ecx]
16
    xor     dl, al
17
18
    movzx   ax, ah
19
    add      ecx, 1
20
    and     edx, 255    ; 000000ffH
21
    movzx   edx, dx
22
    xor      ax, WORD PTR _CRCtable[edx*2]
23
    
24
    cmp      ecx, esi
25
    movzx   eax, ax
26
    jb      SHORT $LL2@CRC16
27
    ; ----- end of calculation loop -----
28
29
    pop esi
30
    ret
Der spannende Teil dieses Codes ist die caculation loop, da diese für 
jedes Byte ausgeführtwird. In dieser Loop sind 10 Assembleranweisungen. 
Dabei habe ich einen Verdacht, dass diese nicht optimal sind. Hier sind 
die (nach meiner Meinung) kritischen Zeilen:
1
    movzx   edx, BYTE PTR [ecx]
2
    xor     dl, al
3
    ; ...
4
    and     edx, 255    ; 000000ffH
5
    movzx   edx, dx
Jede Anweisung ändert das Register DL oder EDX.

In der ersten wird ein Byte in das 32 Bit Register geladen. Das sollte 
die oberen Bytes auf 0 setzen.

Das XOR ändert nur das untere Byte, die drei oberen bleiben 0.

Das AND sorgt dafür, dass ... die oberen Bytes auf 0 gesetzt werden.

Das MOVZX erweitert die unteren 16 Bit (DX) auf 32 Bit, wobei die oberen 
beiden Bytes auf 0 gesetzt werden.


Kann ich meinem Eindruck trauen, dass die letzten beiden Zeilen 
überflüssig sind? Kann der Compiler so schlecht sein?

Gibt es einen andere Algorithmus, gerne auch in 486 Assembler, der 
schneller ist? Dabei könnte man berücksichtigen, dass mit der Routine 
das CRC16 über einen Bereich von mehreren Megabyte berechnet werden 
soll. Ein Epilog für verbleibende x Bytes wäre kein Problem.

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Am schnellsten ist halt eine Tabelle, aber die braucht 128 KiB für
die Werte.

von ... (Gast)


Lesenswert?

Die byte-weise Verarbeitung der Daten macht den Prozessor wahrscheinlich 
auch langsam. Ich hab vor paar Jahren mal im optimization guide von 
Intel gelesen und da stand drin, dass bei byte-weisen Zugriffen sehr 
viele wait states anfallen. Ich weiß allerdings nicht wie das bei 
aktuellen Prozessoren ist.

von foo (Gast)


Lesenswert?

Du kannst z.B. mal Loop Unrolling machen.

Deine Daten sind ja wahrscheinlich länger.

Also z.B: Input 300 bytes,
in der loop immer z.B. 32 bytes verrechen.
Bleibt 300 % 32 -> 12 restbytes zu verrechnen.

Erspart Vergleiche, kostet Codegröße.

von Peter II (Gast)


Lesenswert?

foo schrieb:
> Erspart Vergleiche, kostet Codegröße.

dafür gibt es doch extra:

http://en.wikipedia.org/wiki/Duff's_device

könnte man hier eventuell verwenden.

von Harper B. (harper)


Lesenswert?

Jörg Wunsch schrieb:
> Am schnellsten ist halt eine Tabelle, aber die braucht 128 KiB für
> die Werte.

In meinem Code ist schon eine CRCtable mit 512 16-Bit Werten enthalten. 
Wie in etwas muss ich mir eine 128 kByte große Tabelle vorstellen?

von Harper B. (harper)


Lesenswert?

foo schrieb:
>> Dabei könnte man berücksichtigen, dass mit der Routine
>> das CRC16 über einen Bereich von mehreren Megabyte berechnet
>> werden soll.
>
> Deine Daten sind ja wahrscheinlich länger.

Ja wirklich, dass wäre also durch n-faches Hinschreiben des 
Schleifenkörpers zu erledigen, wobei vorher die Anzahl durch n dividiert 
werden muss.

> Erspart Vergleiche, kostet Codegröße.
Codegröße ist in der konkreten Anwenung nicht kritische. Das könnte ich 
mal probieren.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Harper Blues schrieb:
> In meinem Code ist schon eine CRCtable mit 512 16-Bit Werten enthalten.
> Wie in etwas muss ich mir eine 128 kByte große Tabelle vorstellen?

Jaja, stimmt schon, ich hatte mich vertan.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.