คำนวณตาราง CRC32 ในเวลารวบรวม [ปิด]


16

ดำเนินการอ้างอิงของ CRC32คำนวณตารางการค้นหาที่รันไทม์:

/* Table of CRCs of all 8-bit messages. */
unsigned long crc_table[256];

/* Flag: has the table been computed? Initially false. */
int crc_table_computed = 0;

/* Make the table for a fast CRC. */
void make_crc_table(void)
{
    unsigned long c;

    int n, k;
    for (n = 0; n < 256; n++) {
        c = (unsigned long) n;
        for (k = 0; k < 8; k++) {
            if (c & 1) {
                c = 0xedb88320L ^ (c >> 1);
            } else {
                c = c >> 1;
            }
        }
        crc_table[n] = c;
    }
    crc_table_computed = 1;
}

คุณสามารถคำนวณตารางในเวลาคอมไพล์ได้ดังนั้นการกำจัดฟังก์ชันและสถานะแฟล็ก


2
เกณฑ์การชนะหลักวัตถุประสงค์คืออะไรที่นี่?
John Dvorak

นอกจากนี้ยังมีคำถามเฉพาะเกี่ยวกับภาษาอีกด้วย คุณควรลบแท็ก c ++
ภูมิใจ haskeller

คำตอบ:


12

นี่คือโซลูชัน C ธรรมดา:

crc32table.c

#if __COUNTER__ == 0

    /* Initial setup */
    #define STEP(c) ((c)>>1 ^ ((c)&1 ? 0xedb88320L : 0))
    #define CRC(n) STEP(STEP(STEP(STEP(STEP(STEP(STEP(STEP((unsigned long)(n)))))))))
    #define CRC4(n) CRC(n), CRC(n+1), CRC(n+2), CRC(n+3)

    /* Open up crc_table; subsequent iterations will fill its members. */
    const unsigned long crc_table[256] = {

    /* Include recursively for next iteration. */
    #include "crc32table.c"

#elif __COUNTER__ < 256 * 3 / 4

    /* Fill the next four CRC entries. */
    CRC4((__COUNTER__ - 3) / 3 * 4),

    /* Include recursively for next iteration. */
    #include "crc32table.c"

#else

    /* Close crc_table. */
    };

#endif

มันขึ้นอยู่กับ__COUNTER__แมโครที่ไม่เป็นมาตรฐานเช่นเดียวกับความหมายของการประเมินผลที่__COUNTER__มีการประเมินก่อนที่มันจะถูกส่งผ่านเป็นอาร์กิวเมนต์ไปยังแมโคร

โปรดทราบว่าเนื่องจากSTEPประเมินอาร์กิวเมนต์ของมันสองครั้งและCRCใช้การเรียกใช้แบบซ้อนกันแปดครั้งจึงมีการระเบิดขนาดเล็กในขนาดรหัส combinatorial:

$ cpp crc32table.c | wc -c
4563276

ฉันทดสอบสิ่งนี้ใน GCC 4.6.0 และ Clang 2.8 บน Linux แบบ 32 บิตและทั้งคู่ผลิตตารางที่ถูกต้อง


เยี่ยมมากฉันไม่ได้คาดหวังสิ่งนี้ มี +1
R. Martinho Fernandes

9

แกนวนรอบ

for (k = 0; k < 8; k++) {
    if (c & 1) {
        c = 0xedb88320L ^ (c >> 1);
    } else {
        c = c >> 1;
    }
}

สามารถแปลงเป็นฟังก์ชันเมตา:

template <unsigned c, int k = 8>
struct f : f<((c & 1) ? 0xedb88320 : 0) ^ (c >> 1), k - 1> {};

template <unsigned c>
struct f<c, 0>
{
    enum { value = c };
};

จากนั้นการเรียกใช้ฟังก์ชันเมตานี้ 256 ครั้ง (สำหรับตัวเริ่มต้นอาร์เรย์) จะถูกสร้างโดยตัวประมวลผลล่วงหน้า

#define A(x) B(x) B(x + 128)
#define B(x) C(x) C(x +  64)
#define C(x) D(x) D(x +  32)
#define D(x) E(x) E(x +  16)
#define E(x) F(x) F(x +   8)
#define F(x) G(x) G(x +   4)
#define G(x) H(x) H(x +   2)
#define H(x) I(x) I(x +   1)
#define I(x) f<x>::value ,

unsigned crc_table[] = { A(0) };

หากคุณมีการติดตั้ง Boost การสร้าง initializer อาร์เรย์นั้นค่อนข้างง่ายกว่า:

#include <boost/preprocessor/repetition/enum.hpp>

#define F(Z, N, _) f<N>::value

unsigned crc_table[] = { BOOST_PP_ENUM(256, F, _) };

ในที่สุดไดรเวอร์ทดสอบต่อไปนี้จะพิมพ์องค์ประกอบอาร์เรย์ทั้งหมดไปยังคอนโซล:

#include <cstdio>

int main()
{
    for (int i = 0; i < 256; ++i)
    {
        printf("%08x  ", crc_table[i]);
    }
}

6

โซลูชัน C ++ 0x

template<unsigned long C, int K = 0>
struct computek {
  static unsigned long const value = 
    computek<(C & 1) ? (0xedb88320L ^ (C >> 1)) : (C >> 1), K+1>::value;
};

template<unsigned long C>
struct computek<C, 8> {
  static unsigned long const value = C;
};

template<int N = 0, unsigned long ...D>
struct compute : compute<N+1, D..., computek<N>::value> 
{ };

template<unsigned long ...D>
struct compute<256, D...> {
  static unsigned long const crc_table[sizeof...(D)];
};

template<unsigned long...D>
unsigned long const compute<256, D...>::crc_table[sizeof...(D)] = { 
  D...
};

/* print it */
#include <iostream>

int main() {
  for(int i = 0; i < 256; i++)
    std::cout << compute<>::crc_table[i] << std::endl;
}

ใช้งานได้กับ GCC (4.6.1) และ Clang (trunk 134121)


เกี่ยวกับการC >> 1ไม่เปลี่ยนค่าลบเป็นพฤติกรรมที่ไม่ระบุใช่ไหม ;)
fredoverflow

นอกจากนี้คุณสามารถอธิบายส่วนที่คุณกำหนดอาร์เรย์ได้หรือไม่ ฉันหลงทางไปนิดนึง ...
fredoverflow

@ เฟร็ดคุณถูกต้อง ฉันยังจะทำให้ อาร์เรย์คงถูกกำหนดที่จะเริ่มต้นจากการขยายตัวแพ็ค เป็นชุดพารามิเตอร์เท็มเพลตที่ไม่ใช่ประเภท เมื่อ GCC สนับสนุนแล้วคุณสามารถประกาศอาร์เรย์ในคลาสด้วยได้ แต่ GCC ยังไม่ได้แยกวงเล็บ initializers ในคลาส ประโยชน์ที่จะได้รับคือสามารถใช้ในการแสดงออกคงที่ในภายหลังในรหัส Cunsigned longD...Dstatic unsigned long constexpr crc_table[] = { D... };compute<>::crc_table[I]
Johannes Schaub - litb

5

C ++ 0x constexprกับ ทำงานบน GCC4.6.1

constexpr unsigned long computek(unsigned long c, int k = 0) {
  return k < 8 ? computek((c & 1) ? (0xedb88320L ^ (c >> 1)) : (c >> 1), k+1) : c;
}

struct table {
  unsigned long data[256];
};

template<bool> struct sfinae { typedef table type; };
template<> struct sfinae<false> { };

template<typename ...T>
constexpr typename sfinae<sizeof...(T) == 256>::type compute(int n, T... t) { 
  return table {{ t... }}; 
}

template<typename ...T>
constexpr typename sfinae<sizeof...(T) <= 255>::type compute(int n, T... t) {
  return compute(n+1, t..., computek(n));
}

constexpr table crc_table = compute(0);

#include <iostream>

int main() {
  for(int i = 0; i < 256; i++)
    std::cout << crc_table.data[i] << std::endl;
}

จากนั้นคุณสามารถใช้crc_table.data[X]ที่รวบรวมเวลาเพราะเป็นcrc_tableconstexpr


4

นี่คือmetaprogram แรกของฉัน:

#include <cassert>
#include <cstddef>

template <std::size_t N, template <unsigned long> class T, unsigned long In>
struct times : public T<times<N-1,T,In>::value> {};

template <unsigned long In, template <unsigned long> class T>
struct times<1,T,In> : public T<In> {};

template <unsigned long C>
struct iter {
    enum { value = C & 1 ? 0xedb88320L ^ (C >> 1) : (C >> 1) };
};

template <std::size_t N>
struct compute : public times<8,iter,N> {};

unsigned long crc_table[] = {
    compute<0>::value,
    compute<1>::value,
    compute<2>::value,
    // .
    // .
    // .
    compute<254>::value,
    compute<255>::value,
};

/* Reference Table of CRCs of all 8-bit messages. */
unsigned long reference_table[256];

/* Flag: has the table been computed? Initially false. */
int reference_table_computed = 0;

/* Make the table for a fast CRC. */
void make_reference_table(void)
{
    unsigned long c;

    int n, k;
    for (n = 0; n < 256; n++) {
        c = (unsigned long) n;
        for (k = 0; k < 8; k++) {
            if (c & 1) {
                c = 0xedb88320L ^ (c >> 1);
            } else {
                c = c >> 1;
            }
        }
        reference_table[n] = c;
    }
    reference_table_computed = 1;
}

int main() {
    make_reference_table();
    for(int i = 0; i < 256; ++i) {
        assert(crc_table[i] == reference_table[i]);
    }
}

ฉัน "hardcoded" การเรียกไปยังเทมเพลตที่ทำการคำนวณ :)


1
หากคุณกำลังจะทำอย่างนั้นทำไมคุณจะไม่ฮาร์ดโค้ดค่าจริงลงในโปรแกรม? (หลังจากได้รับพวกเขาด้วยวิธีอื่นแน่นอน) ประหยัดเวลาในการรวบรวม
Matthew อ่าน

+1 สำหรับการทดสอบและtimesเทมเพลต
fredoverflow

@ Matewew: ด้วย C ++ 03 เท่านั้นมันไม่มีทางเลือก คุณสามารถใช้ตัวประมวลผลล่วงหน้าอย่างที่ Fred ทำ แต่จะไม่ทำให้เวลาในการรวบรวมสั้นลง และเห็นได้ชัดว่าเขา preprocessor ฉายาในการแก้ปัญหาของเขา :)
อาร์มาตินเฟอร์นันเด

@ Matthew จุดคือการคำนวณได้จริงในเวลารวบรวมไม่ให้พวกเขา hardcoded คำตอบของ Fred สร้างอาร์เรย์ของฟอร์มนี้: unsigned crc_table[] = { f<0>::value , f<0 + 1>::value , f<0 + 2>::value , f<0 + 2 + 1>::value , f<0 + 4>::value , f<0 + 4 + 1>::value , f<0 + 4 + 2>::value , f<0 + 4 + 2 + 1>::value , f<0 + 8>::value ,โดยใช้ตัวประมวลผลล่วงหน้า ใช้เวลาในการรวบรวมนานเท่านานของฉัน หากคุณต้องการคุณสามารถอ่านย่อหน้าสุดท้ายว่า "ฉันคลี่วงนอกออก" ไม่มีตัวเลือกอื่นใน C ++ 03
R. Martinho Fernandes

อาฉันไม่ได้ใส่ใจเพียงพอกับข้อกำหนดของคำถาม +1 แต่ฉันไม่แน่ใจว่าฉันชอบคำถามอีกต่อไป ฉันชอบรหัสที่ท้าทายความสามารถในการใช้งานจริง: P
Matthew อ่าน

3

D

import std.stdio, std.conv;

string makeCRC32Table(string name){

  string result = "immutable uint[256]"~name~" = [ ";

  for(uint n; n < 256; n++){
    uint c = n;
    for(int k; k < 8; k++)
      c = (c & 1) ? 0xedb88320L ^ (c >> 1) : c >>1;
    result ~= to!string(c) ~ ", ";
  }
  return result ~ "];";
}

void main(){

  /* fill table during compilation */
  mixin(makeCRC32Table("crc_table"));

  /* print the table */
  foreach(c; crc_table)
    writeln(c);
}

มันทำให้ C ++ น่าละอายใช่ไหม?


2
จริงๆแล้วฉันชอบโซลูชันที่ไม่ได้พิมพ์อย่างเข้มงวด evalลักษณะนี้มากเช่นรวบรวมเวลา
R. Martinho Fernandes

ไม่จำเป็นต้องใช้สตริงมิกซ์อินสำหรับสิ่งนี้นี่คือวิธีที่เราทำในไลบรารีมาตรฐานของgenTablesและcall-siteเพื่อเก็บผลลัพธ์ในส่วนข้อมูล const
Martin Nowak

3

C / C ++, 306 295 ไบต์

#define C(c)((c)>>1^((c)&1?0xEDB88320L:0))
#define K(c)(C(C(C(C(C(C(C(C(c))))))))),
#define F(h,l)K((h)|(l+0))K((h)|(l+1))K((h)|(l+2))K((h)|(l+3))
#define R(h)F(h<<4,0)F(h<<4,4)F(h<<4,8)F(h<<4,12)
unsigned long crc_table[]={R(0)R(1)R(2)R(3)R(4)R(5)R(6)R(7)R(8)R(9)R(10)R(11)R(12)R(13)R(14)R(15)};

ทำงานในสิ่งที่ตรงกันข้ามเราจบลงด้วยอาร์เรย์ยาวที่ไม่ได้ลงชื่อชื่อ crc_table เราสามารถตัดขนาดของอาร์เรย์ได้เนื่องจากแมโครจะตรวจสอบให้แน่ใจว่ามีองค์ประกอบทั้งหมด 256 รายการในอาร์เรย์ เราเริ่มต้นอาร์เรย์ด้วย 16 'แถว' ของข้อมูลโดยใช้ 16 invocations ของแมโคร R

การเรียกใช้ R แต่ละครั้งจะขยายออกเป็นสี่แฟรกเมนต์ (แมโคร F) ของค่าคงที่สี่ (แมโคร K) สำหรับข้อมูลทั้งหมด 16 คอลัมน์

มาโคร K เป็นลูปที่ไม่ได้ควบคุมซึ่งจัดทำดัชนีโดย k ในรหัสจากคำถามต้นฉบับ มันอัพเดตค่า c แปดครั้งโดยเรียกใช้แมโคร C

โซลูชันพื้นฐานของตัวประมวลผลล่วงหน้านี้ใช้หน่วยความจำสักเล็กน้อยระหว่างการขยายแมโคร ฉันพยายามทำให้สั้นลงเล็กน้อยด้วยการเพิ่มระดับการขยายตัวของแมโครและคอมไพเลอร์ของฉันก็ปุก รหัสข้างต้นคอมไพล์ (ช้า) กับทั้ง Visual C ++ 2012 และ g ++ 4.5.3 ภายใต้ Cygwin (RAM RAM Windows 7 64 บิต 8GB)

แก้ไข:

แฟรกเมนต์ด้านบนคือ 295 ไบต์รวมถึง white space หลังจากขยายมาโครทั้งหมดยกเว้น C จะเพิ่มเป็น 9,918 ไบต์ เมื่อขยายมาโคร C แต่ละระดับขนาดจะขยายตัวอย่างรวดเร็ว:

  1. 25182
  2. 54174
  3. 109086
  4. 212766
  5. 407838
  6. 773406
  7. 1455390
  8. 2721054

ดังนั้นเมื่อเวลาที่แมโครทั้งหมดถูกขยายไฟล์ 295 ไบต์ขนาดเล็กนั้นจะขยายเข้าไปในโค้ดขนาด 2.7 เมกะไบต์ซึ่งต้องถูกคอมไพล์เพื่อสร้างอาร์เรย์ 1024 ไบต์ดั้งเดิม (สมมติว่ามีค่าความยาว 32 บิตที่ไม่ได้ลงชื่อ)!

การแก้ไขอื่น:

ฉันแก้ไขแมโคร C โดยยึดตามแมโครจากคำตอบอื่นเพื่อบีบออกพิเศษ 11 ไบต์และลดขนาดแมโครที่ขยายเต็มได้อย่างมาก แม้ว่า 2.7 MB จะไม่เลวเท่า 54 MB (ขนาดสุดท้ายก่อนหน้าของการขยายแมโครทั้งหมด) แต่ก็ยังมีความสำคัญ


นี่ไม่ใช่โค้ดกอล์ฟดังนั้นคุณไม่จำเป็นต้องลดจำนวนตัวอักษร
Ilmari Karonen

อา. ดังนั้นมันจึงเป็น ส่วนที่ไม่ดีของฉัน แม้ว่าฉันจะคิดว่าการใช้งานนี้เป็นแบบพกพา (ซึ่งฉันหมายความว่ามันเป็นไปตามภาษา C และตัวประมวลผลล่วงหน้า; การพกพาที่แท้จริงของมันจะขึ้นอยู่กับข้อ จำกัด ที่แน่นอนของสภาพแวดล้อมในการขยายแมโคร)
CasaDeRobison

3

ฉันจะแก้ไขคำตอบก่อนหน้าโดยแทนที่สามบรรทัดสุดท้ายด้วย:

#define crc4( x)    crcByte(x), crcByte(x+1), crcByte(x+2), crcByte(x+3)
#define crc16( x)   crc4(x), crc4(x+4), crc4(x+8), crc4(x+12)
#define crc64( x)   crc16(x), crc16(x+16), crc16(x+32), crc16(x+48)
#define crc256( x)  crc64(x), crc64(x+64), crc64(x+128), crc64(x+192)

โดยที่ crcByte เป็นมาโคร K ของเขาที่ไม่มีจุลภาคต่อท้าย จากนั้นสร้างตารางด้วย:

static const unsigned long crc32Table[256] = { crc256( 0)};

และอย่าทิ้งขนาดของอาเรย์เนื่องจากคอมไพเลอร์จะตรวจสอบว่าคุณมีจำนวนองค์ประกอบที่ถูกต้อง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.