Вычислить таблицу 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;
}

Можете ли вы вычислить таблицу во время компиляции, тем самым избавившись от функции и флага состояния?

fredoverflow
источник
2
Каков объективный первичный критерий выигрыша здесь?
Джон Дворжак
кроме того, специфичные для языка вопросы здесь осуждаются. Вы должны удалить тег 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использует восемь вложенных его вызовов, возникает небольшой комбинаторный взрыв в размере кода:

$ cpp crc32table.c | wc -c
4563276

Я проверил это в GCC 4.6.0 и Clang 2.8 на 32-битном Linux, и оба создали правильную таблицу.

Джои Адамс
источник
Круто, я, конечно, не ожидал этого. Есть +1.
Р. Мартиньо Фернандес
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, генерировать инициализатор массива немного проще:

#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]);
    }
}
fredoverflow
источник
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 (багажник 134121).

Йоханнес Шауб - Литб
источник
Относительно C >> 1, не сдвигает ли отрицательные значения правильное неопределенное поведение? ;)
fredoverflow
Кроме того, вы можете объяснить, где вы определяете массив? Я немного потерян там ...
fredoverflow
@ Фред, ты прав. Я также должен сделать . Массив констант определяется как инициализируемый расширением пакета . является нетиповым шаблоном параметров шаблона. Как только GCC поддерживает это, можно также объявить массив в классе , но GCC пока не разбирает инициализированные в классе инициализаторы. Преимущество будет в том, что его можно использовать в константных выражениях позже в коде. Cunsigned longD...Dstatic unsigned long constexpr crc_table[] = { D... };compute<>::crc_table[I]
Йоханнес Шауб - лит
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_tableесть constexpr.

Йоханнес Шауб - Литб
источник
4

Это моя первая метапрограмма :

#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]);
    }
}

Я "жестко запрограммировал" вызовы шаблона, который выполняет вычисления :)

Р. Мартиньо Фернандес
источник
1
Если вы собираетесь сделать это таким образом, почему бы вам просто не закодировать действительные значения в программе? (После получения их другим способом, конечно.) Экономит время компиляции.
Мэтью Прочитал
+1 за тест и timesшаблон
fredoverflow
@ Матфея: только с C ++ 03, выбор невелик. Вы можете использовать препроцессор, как это делал Фред, но это не сократит время компиляции. И, видимо, его препроцессор задыхается от своего решения :)
Р. Мартиньо Фернандес
@ Matthew Суть в том, чтобы на самом деле вычислить его во время компиляции , а не кодировать его жестко. Ответ Фреда генерирует массив этой формы: 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
Р. Мартиньо Фернандес
Ах, я не уделял достаточного внимания требованиям вопроса. +1, хотя я не уверен, что мне больше нравится этот вопрос. Мне нравятся мои практические проблемы с кодом: P
Мэтью Рид
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.
Р. Мартиньо Фернандес
Для этого не нужно использовать строковый миксин, вот как мы это делаем в стандартной библиотеке D , genTables и call-site для сохранения результата в сегменте данных const.
Мартин Новак
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 вызовов макроса R.

Каждый вызов R расширяется на четыре фрагмента (макрос F) из четырех констант (макрос K), что в сумме составляет 16 «столбцов» данных.

Макрос K - это развернутый цикл, индексированный k в коде из исходного вопроса. Он обновляет значение c восемь раз, вызывая макрос C.

Это решение на основе препроцессора использует довольно много памяти при расширении макроса. Я попытался сделать его немного короче, добавив дополнительный уровень макроразвития, и мой компилятор рванул. Приведенный выше код компилируется (медленно) с Visual C ++ 2012 и g ++ 4.5.3 под Cygwin (64-битная оперативная память Windows 7, 8 ГБ).

Редактировать:

Фрагмент выше составляет 295 байт, включая пробелы. После расширения всех макросов, кроме 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 МБ не так плохо, как 54 МБ (предыдущий конечный размер всего расширения макросов), он все еще значителен.

CasaDeRobison
источник
Это не код-гольф , поэтому вам не нужно минимизировать количество персонажей.
Илмари Каронен
Ах. Так что, это. Мой плохой в этой части. Хотя я думаю, что эта реализация переносима (я имею в виду, что она соответствует языку 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)};

И никогда не пропускайте размер массива, так как компилятор затем проверит, что у вас есть правильное количество элементов.

сойка
источник