Расчет CRC32 для буфера / файла с нулевым заполнением

Если я хочу вычислить значение CRC32 для большого количества последовательных нулевых байтов, есть ли формула постоянного времени, которую я могу использовать с учетом длины серии нулей? Например, если я знаю, что у меня есть 1000 байт, заполненных нулями, есть ли способ избежать цикла с 1000 итерациями (просто пример, фактическое количество нулей не ограничено для этого вопроса)?

Да, есть. Вы знаете, как работают полиномы над GF (2)?

Matt Timmermans 27.10.2018 05:39

Один из методов регистрации порядка (количества нулей) описан в crc32_combine Марка Адлера в исходном коде zlib. Его можно обобщить на другие алгоритмы CRC.

gammatester 27.10.2018 09:18

@rcgldr для нулевых байтов п, CRC - начальное_значение * (x ^ 8n) mod poly. Вы можете вычислить x ^ 8n мод поли, используя возведение в степень возведением в квадрат: en.wikipedia.org/wiki/Exponentiation_by_squaring ... но это не принесет никакой пользы OP, если я скажу это, когда он не знает, что это значит.

Matt Timmermans 27.10.2018 14:32

@MattTimmermans - Я удалил свой предыдущий комментарий. OP запросил "формулу" постоянного времени, что возможно, если n является константой.

rcgldr 27.10.2018 16:59

@rcgldr, п не обязательно должно быть постоянным. Это просто должно быть ограничено. Если п> 2 ^ 32, то вы можете уменьшить его по модулю 2 ^ 32-1, потому что шаблон CRC с нулями п повторяется с этим периодом. При обычном предположении, что вы можете выполнять арифметические операции с п за постоянное время, возведение в степень возведением в квадрат занимает не более 32 шагов, то есть может быть выполнено за постоянное время. Хорошо, это предположение в данном контексте - немного шутка, но для реальных практических целей это постоянное время, если только функция не принимает п как bignum. Если п - bignum, тогда это O (журнал n) только для модуля.

Matt Timmermans 28.10.2018 02:44

@MattTimmermans - Некоторые реализации этого могут быть O (log2 (n)) (возможно, менее 32 шагов) по сравнению с O (1), хотя в этом случае O (log2 (n)) будет меньше шагов, чем O (1) (постоянные 32 шага). Мне не ясно, рассматривает ли OP случай, когда 1000 байт является константой, или просто пример возможного значения для n.

rcgldr 28.10.2018 10:09

@rcgldr Это был просто пример. Это могло быть неограниченное количество нулей.

Mike Marynowski 30.10.2018 19:55

@MattTimmermans Ваши комментарии были весьма полезными в сочетании с ответами.

Mike Marynowski 30.10.2018 20:14

@MikeMarynowski - я обновил свой ответ примером кода (я думал, что делал это раньше). Временная сложность может быть уменьшена до O (1), используя поиск по таблице для генерации констант в форме полинома pow (2,8 * i)% для i = от 1 до n. Затем для обработки от 1 до n нулевых байтов выполняется поиск по таблице, за которым следует программный многочлен умножения без переноса (32 итерации).

rcgldr 03.04.2020 08:49
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
9
949
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Сложность по времени может быть уменьшена до O (1) с помощью поиска в таблице с последующим умножением. Объяснение и пример кода показаны в третьем разделе этого ответа.

Если 1000 является константой, предварительно вычисленная таблица из 32 значений, каждое из которых представляет может использоваться каждый бит CRC до 8000-й степени mod poly. Набор матриц (один набор на байт CRC) может использоваться для работы с байтом за раз. Оба метода будут иметь постоянное время (фиксированное количество петель) O (1).

Как отмечалось выше, если 1000 не является константой, то можно использовать возведение в степень возведением в квадрат, что будет иметь временную сложность O (log2 (n)) или комбинацию предварительно вычисленных таблиц для некоторого постоянного числа нулевых битов, например 256, с последующим возведением в степень и возведением в квадрат, чтобы конечный шаг был O (log2 (n% 256)).


Оптимизация в целом: для обычных данных с нулевыми и ненулевыми элементами на современной X86 с pclmulqdq (использует регистры xmm) может быть реализован быстрый crc32 (или crc16), хотя он близок к 500 строкам ассемблерного кода. Документ Intel: crc с использованием pclmulqdq. Пример исходного кода для github fast crc16. Для 32-битной CRC требуется другой набор констант. Если интересно, я преобразовал исходный код для работы с Visual Studio ML64.EXE (64-битный MASM) и создал примеры для 32-битных CRC со сдвигом влево и вправо, каждый с двумя наборами констант для двух самых популярных 32-битных полиномов CRC. (полисы сдвига влево: crc32: 0x104C11DB7 и crc32c: 0x11EDC6F41, полисы сдвига вправо меняются местами).


Пример кода для быстрой настройки CRC с использованием программного обеспечения умножения без переноса по модулю полиномиального CRC. Это будет намного быстрее, чем использование матричного умножения 32 x 32. CRC вычисляется для ненулевых данных: crf = GenCrc (msg, ...). Константа регулировки вычисляется для n нулевых байтов: pmc = pow (2 ^ (8 * n))% poly (с использованием возведения в степень путем повторного возведения в квадрат). Затем CRC корректируется для нулевых байтов: crf = (crf * pmc)% poly.

Обратите внимание, что временная сложность может быть уменьшена до O (1) с помощью создания таблицы констант pow (2 ^ (8 * i))% poly для i = от 1 до n. Затем выполняется поиск по таблице и фиксированная итерация (32 цикла) умножения% poly.

#include <stdio.h>
#include <stdlib.h>

typedef unsigned char       uint8_t;
typedef unsigned int       uint32_t;

static uint32_t crctbl[256];

void GenTbl(void)                       /* generate crc table */
{
uint32_t crc;
uint32_t c;
uint32_t i;
    for(c = 0; c < 0x100; c++){
        crc = c<<24;
        for(i = 0; i < 8; i++)
            crc = (crc<<1)^((0-(crc>>31))&0x04c11db7);
        crctbl[c] = crc;
    }
}

uint32_t GenCrc(uint8_t * bfr, size_t size) /* generate crc */
{
uint32_t crc = 0u;
    while(size--)
        crc = (crc<<8)^crctbl[(crc>>24)^*bfr++];
    return(crc);
}

/* carryless multiply modulo crc */
uint32_t MpyModCrc(uint32_t a, uint32_t b) /* (a*b)%crc */
{
uint32_t pd = 0;
uint32_t i;
    for(i = 0; i < 32; i++){
        pd = (pd<<1)^((0-(pd>>31))&0x04c11db7u);
        pd ^= (0-(b>>31))&a;
        b <<= 1;
    }
    return pd;
}

/* exponentiate by repeated squaring modulo crc */
uint32_t PowModCrc(uint32_t p)          /* pow(2,p)%crc */
{
uint32_t prd = 0x1u;                    /* current product */
uint32_t sqr = 0x2u;                    /* current square */
    while(p){
        if (p&1)
            prd = MpyModCrc(prd, sqr);
        sqr = MpyModCrc(sqr, sqr);
        p >>= 1;
    }
    return prd;
}

/* # data bytes */
#define DAT  ( 32)
/* # zero bytes */
#define PAD  (992)
/* DATA+PAD */
#define CNT (1024)

int main()
{
uint32_t pmc;
uint32_t crc;
uint32_t crf;
uint32_t i;
uint8_t *msg = malloc(CNT);

    for(i = 0; i < DAT; i++)           /* generate msg */
        msg[i] = (uint8_t)rand();
    for( ; i < CNT; i++)
        msg[i] = 0;
    GenTbl();                           /* generate crc table */
    crc = GenCrc(msg, CNT);             /* generate crc normally */
    crf = GenCrc(msg, DAT);             /* generate crc for data */
    pmc = PowModCrc(PAD*8);             /* pmc = pow(2,PAD*8)%crc */
    crf = MpyModCrc(crf, pmc);          /* crf = (crf*pmc)%crc */
    printf("%08x %08x\n", crc, crf);    /* crf == crc */
    free(msg);
    return 0;
}

На современных процессорах быстрый crc32 (и 8, и 16, и 64) уже реализован аппаратно. Ровно 1 строка ассемблерного кода: software.intel.com/sites/landingpage/IntrinsicsGuide/…

Soonts 27.10.2018 17:06

@Soonts - эта инструкция работает только для сдвига вправо crc32c (определенного полинома).

rcgldr 27.10.2018 17:07

Может быть достаточно для операции. Инструкция очень быстрая, 1000 значений = менее 1 микросекунды, практически невозможно измерить.

Soonts 27.10.2018 17:21

@Soonts - если OP использует другой полином или использует CRC с левым смещением, то эта инструкция не поможет. Даже если OP использует crc32c с правым смещением, это не намного быстрее, в моей системе Intel 3770K 3,5 ГГц для 256 МБ, pclmulqdq => 0,0783919 сек, crc32 intrinsic => 0,0541801 сек. Хотя это 500 строк против 1 строки кода.

rcgldr 27.10.2018 17:35
Ответ принят как подходящий

Вы можете вычислить результат применения нулей п не за время O (1), а за время O (log п). Это делается в crc32_combine() zlib. Создается двоичная матрица, которая представляет операцию применения одного нулевого бита к CRC. Матрица 32x32 умножает 32-битный CRC на GF (2), где сложение заменяется на исключающее ИЛИ (^), а умножение заменяется на и (&), бит за битом.

Затем эту матрицу можно возвести в квадрат, чтобы получить оператор для двух нулей. Это возведено в квадрат, чтобы получить оператор для четырех нулей. Третье возводится в квадрат, чтобы получить оператор для восьми нулей. И так далее по мере необходимости.

Теперь этот набор операторов может быть применен к CRC на основе единичных битов в числе п нулевых битов, для которых вы хотите вычислить CRC.

Вы можете предварительно вычислить результирующий матричный оператор для любого количества нулевых битов, если вы знаете, что часто будете применять именно такое количество нулей. Тогда это всего лишь одно умножение матрицы на вектор, который на самом деле равен O (1).

Вам не нужно использовать инструкцию pclmulqdq, предложенную в другом ответе здесь, но это было бы немного быстрее, если бы она у вас была. Это не изменит O () операции.

Упоминание pclmulqdq в моем ответе касалось быстрого crc32 с ненулевыми данными. Я обновил свой ответ, чтобы прояснить это.

rcgldr 27.10.2018 23:06

Я обновил свой ответ, чтобы отметить, что временная сложность O (1) возможна с использованием поиска в таблице и программного полинома умножения без переноса по модулю crc.

rcgldr 03.04.2020 08:53

CRC32 основан на умножении в GF (2) [X] по модулю некоторого многочлена, который является мультипликативным. Сложная часть - это отделение неумножительного от мультипликативного.

Сначала определите разреженный файл со следующей структурой (в Go):

type SparseFile struct {
    FileBytes []SparseByte
    Size      uint64
}
type SparseByte struct {
    Position uint64
    Value    byte
}

В вашем случае это будет SparseFile{[]FileByte{}, 1000}

Тогда функция будет такой:

func IEEESparse (file SparseFile) uint32 {
    position2Index := map[uint64]int{}
    for i , v := range(file.FileBytes) {
        file.FileBytes[i].Value = bits.Reverse8(v.Value)
        position2Index[v.Position] = i
    }
    for i := 0; i < 4; i++ {
        index, ok := position2Index[uint64(i)]
        if !ok {
            file.FileBytes = append(file.FileBytes, SparseByte{Position: uint64(i), Value: 0xFF})
        } else {
            file.FileBytes[index].Value ^= 0xFF
        }
    }

    // Add padding
    file.Size += 4
    newReminder := bits.Reverse32(reminderIEEESparse(file))

    return newReminder ^ 0xFFFFFFFF
}

Так что обратите внимание, что:

  1. Деление выполняется по битам в обратном порядке (по байтам).
  2. Первые четыре байта имеют значение 0xFF.
  3. Файл дополняется 4 байтами.
  4. Напоминание снова отменено.
  5. Напоминание снова зафиксировано.

Внутренняя функция reminderIEEESparse является настоящим напоминанием, и ее легко реализовать в O(log n), где n - размер файла.

Вы можете найти полную реализацию здесь.

Другие вопросы по теме

Похожие вопросы