Библиотека быстрой арифметики поля Галуа на C / C ++
Перевод статьи - Fast Galois Field Arithmetic Library in C/C++
Автор(ы) - James S. Plank
Источник оригинальной статьи:
[email protected]
Технический отчет UT-CS-07-593
Департамент компьютерных наук
Университета Теннесси
Если вы используете этот код
Отправьте мне электронное письмо ( [email protected] ) и дайте мне знать. Один из способов, которым меня оценивают, - это влияние моей работы, и важны достоверные данные о том, сколько людей используют этот код.
Благодарности
Этот веб-сайт основан на работе, поддержанной Национальным научным фондом в рамках грантов CNS-0615221 и CNS-0437508. Я также хотел бы поблагодарить Cheng Huang, Lihao Xu, Jin Li и Mochan Shrestha за полезные обсуждения, а также Michel Machado за полезные предложения.
Вступление
Арифметика поля Галуа является фундаментальной для многих приложений, особенно для кодирования Рида-Соломона. В поле Галуа GF (2 w ) операции сложения, вычитания, умножения и деления определяются над числами 0, 1, ..., 2 w -1. таким образом, что:
- Они замкнуты - если a и b являются элементами поля, то также являются (a + b) , (ab) , (a * b) и (a / b) .
- Они придерживаются всех известных свойств сложения / вычитания / умножения / деления. Например, сложение и умножение коммутативны; сложение, умножение и деление ассоциативны; сложение / умножение являются распределительными; и т.п.
- У каждого элемента есть мультипликативный обратный. Это самое главное свойство. Это означает, что если a является элементом поля и a ≠ 0, то существует элемент b, который также является элементом поля, такой что ab = 1 .
Когда w = 8 , Поле Галуа GF (2 8 ) содержит элементы 0, 1, ..., 255. Это важное поле, потому что оно позволяет выполнять арифметические операции, которые придерживаются вышеуказанных свойств для отдельных байтов. Опять же, это важно в кодировании Рида-Соломона и других видах кодирования. Точно так же w = 16 и w = 32 - другие важные поля.
Есть полезные приложения для полей с другими значениями w . Например, кодирование Коши Рида-Соломона использует поля Галуа с другими значениями w и преобразует их в строго двоичные (XOR) коды.
Поля Галуа описаны в стандартных текстах по кодам исправления ошибок, таких как Peterson & Weldon [PW72], MacWilliams and Sloane [MS77] и Van Lint [VL82]. Эти процедуры являются тщательными и требуют времени, чтобы понять их. Для более краткого и прагматичного рассмотрения см. Учебник Планка 1997 года по кодированию Рида-Соломона [P97] , [PD05].
Файлы библиотеки
Эта библиотека представлена в виде tar- файла: galois.tar . Файлы galois.tar :
- README.html - Этот файл.
- galois.h - Заголовочный файл библиотеки.
- galois.c - Реализация библиотеки.
- gf_mult.c - множитель Поля Галуа в командной строке.
- gf_div.c - Разделенное поле Галуа в командной строке.
- gf_xor.c - Командная строка XOR (сложение / вычитание поля Галуа).
- gf_inverse.c - Инвертор Поля Галуа из командной строки. Это то же самое, что делить единицу на число.
- gf_log.c - Дискретный логарифм для Поля Галуа.
- gf_ilog.c - Дискретный обратный логарифм для Поля Галуа.
- gf_basic_tester.c - правильности и синхронизации для арифметики Поля Галуа.
- gf_xor_tester.c - Тестер времени для XOR.
- makefile - Makefile.
- primitive-polynomial-table.txt - таблица примитивных многочленов.
Использование инструментов командной строки
Введите make, чтобы скомпилировать библиотеку и инструменты командной строки. Тестирование инструментов командной строки - хороший способ увидеть, как все работает с Полями Галуа. Во-первых, сложение и вычитание в Поле Галуа эквивалентны - они равны побитовой операции «исключающее ИЛИ». Программа gf_xor позволяет использовать побитовое исключающее или двух чисел:
Unix-Prompt> gf_xor 15 7 8 Unix-Prompt> gf_xor 8 7 15 Unix-Prompt> gf_xor 230498 2738947 2772833 Unix-Prompt> gf_xor 2772833 230498 2738947 Unix-Prompt> |
Программа gf_mult принимает три аргумента: a, b и w, и выводит произведение a и b в GF (2 w) . Программа gf_div выполняет деление, а программа gf_inverse возвращает мультипликативное обратное число в GF (2 w):
Unix-Prompt> gf_mult 3 7 4 9 Unix-Prompt> gf_div 9 3 4 7 Unix-Prompt> gf_div 9 7 4 3 Unix-Prompt> gf_mult 1234567 2345678 32 1404360778 Unix-Prompt> gf_div 1404360778 1234567 32 2345678 Unix-Prompt> gf_div 1404360778 2345678 32 1234567 Unix-Prompt> gf_inverse 1404360778 32 106460795 Unix-Prompt> gf_mult 1404360778 106460795 32 1 Unix-Prompt>
Другие инструменты командной строки обсуждаются в конце этого документа.
Использование библиотеки
Файлы galois.h и galois.c реализуют библиотеку процедур для арифметики поля Галуа в GF (2 w) для w от 1 до 32. Библиотека написана на C, но будет работать и на C ++. Он специально разработан для w, равного 8, 16 и 32, но он также применим для любого другого значения w . Для меньших значений w (где таблицы умножения или логарифма умещаются в памяти) эти процедуры должны быть очень быстрыми.
В следующих разделах мы описываем процедуры, реализованные библиотекой.
1. Умножение и деление общего назначения: galois_single_multiply() and galois_single_divide()
Синтаксис этих двух вызовов:
int galois_single_multiply(int a, int b, int w); int galois_single_divide(int a, int b, int w); |
Galois_single_multiply () возвращает произведение a и b в GF (2 w ) , а galois_single_divide () возвращает qoutient a и b в GF (2 w ) . w может иметь любое значение от 1 до 32.
Решение использовать в этой процедуре обычные целые числа со знаком вместо целых чисел без знака было принято в основном для удобства. Это имеет значение только тогда, когда w равно 32, и в этом случае может быть установлен знаковый бит a , b или возвращаемые значения. Если это важно, просто преобразуйте целые числа в целые числа без знака. Процедуры в этой библиотеке будут работать независимо - когда w равно 32, они рассматриваются как потоки битов, а не целых чисел.
Предполагается, что большинству приложений, которым необходимо выполнять одиночное умножение и деление, требуется только разумная производительность, что и дают вам эти две процедуры. Если вам нужно более быстрое умножение и деление, обратитесь к приведенным ниже процедурам, которые позволят вам добиться гораздо более высокой производительности.
2. Умножение области байтов на одно число в GF(28), GF(216) and GF(232)
Обычное использование арифметики Поля Галуа - умножение области байтов на одно число. Это основная операция кодирования и декодирования Рида-Соломона. Эта библиотека предоставляет следующие три процедуры для выполнения умножения областей:
void galois_w08_region_multiply(char *region, int multby, int nbytes, char *r2, int add); void galois_w16_region_multiply(char *region, int multby, int nbytes, char *r2, int add); void galois_w32_region_multiply(char *region, int multby, int nbytes, char *r2, int add); |
Они умножают область байтов, указанную параметром region и nbytes , на число multby в поле, указанном в имени процедуры. Region должен быть выровнен по длинному слову, иначе эти процедуры вызовут ошибку шины. У этих процедур есть три отдельные функции, обозначенные значениями r2 и add .
- Если r2 - NULL, байты в region заменяются их произведениями с multby.
- Если r2 не NULL и add равно нулю, то продукты помещаются в nbytes памяти, начиная с r2 . Эти две области не должны перекрываться, если r2 не меньше region.
- Если r2 не NULL, а add равно единице, то вычисляются продукты, а затем выполняется операция XOR с существующими байтами r2 .
Выполнение этих процедур настроено на очень быстрое. При w = 8 используется таблица умножения. Таблицы логарифма и обратного логарифма используются при w = 16, а семь таблиц умножения используются при w = 32 .
3. Выполнение операции XOR над областью байтов
Следующая процедура позволяет выполнять побитовое исключающее ИЛИ для области байтов.
void galois_region_xor(char *r1, char *r2, char *r3, int nbytes); |
R3 может быть равно r1 или r2, если вы хотите перезаписать любой из них. Опять же, все указатели должны быть выровнены по длинным словам.
Расширенное использование - быстрое единичное умножение и деление
Хотя galois_single_multiply () и galois_single_divide () являются хорошими инструментами общего назначения, их универсальность делает их медленнее, чем они должны быть. Для повышения производительности вы можете использовать их базовые реализации, описанные ниже:
4. Использование таблицы умножения: galois_multtable_multiply() and galois_multtable_divide()
Когда w мало, самый быстрый способ выполнить умножение и деление - использовать таблицы умножения и деления. Эти таблицы занимают 2 (2w + 2) байта каждая, поэтому они применимы только тогда, когда w достаточно мало. Например, при w = 8 это 256 КБ на таблицу.
Чтобы напрямую использовать таблицы умножения и деления, используйте одну или несколько из следующих подпрограмм
int galois_create_mult_tables(int w); int galois_multtable_multiply(int a, int b, int w); int galois_multtable_divide(int a, int b, int w); int *galois_get_mult_table(int w); int *galois_get_div_table(int w); |
Galois_create_mult_tables (w) создает таблицы умножения и деления для заданного значения w и сохраняет их внутри. Если вы вызовете его дважды с тем же значением w , он не создаст новые таблицы во второй раз. Вы можете вызвать его с разными значениями w, и таблицы будут храниться отдельно.
В случае успеха galois_create_mult_tables () вернет 0. В противном случае он вернет -1, и вся выделенная память будет освобождена.
Galois_multtable_multiply () и galois_multtable_divide () работают так же, как galois_single_multiply () и galois_single_divide () , за исключением того, что они предполагают, что вы вызвали galois_create_mult_tables () для соответствующего значения w и что это было успешно. Они не проверяют ошибки, поэтому, если вы не вызывали galois_create_mult_tables (), они будут сегментировать. Это решение было принято для скорости - хотя для небольших значений w (от 1 до 9) galois_single_multiply () использует galois_multtable_multiply (), это значительно медленнее из-за проверки ошибок, которую она выполняет.
Наконец, чтобы избавиться от накладных расходов на вызов процедур, подпрограммы galois_get_mult_table () и galois_get_div_table () возвращают сами таблицы. Произведение / частное a и b находится в элементе a * 2 w + b, который, конечно, можно быстро вычислить с помощью битовой арифметики как ((a << w) | b) . Вам не нужно вызывать galois_create_mult_tables () перед вызовом galois_get_mult_table () или galois_get_div_table ().
5. Использование таблиц журнала / анти-журнала: galois_logtable_multiply() and galois_logtable_divide()
Когда невозможно использовать таблицы умножения, следующий самый быстрый способ выполнить умножение и деление - это использовать журнальные и обратные журнальные таблицы, как описано в [P97] . Таблица журнала занимает 2 (w + 2) байта, а обратная таблица журнала занимает 3 * 2 (w + 2) байта, что означает, что можно обрабатывать средние значения w . Например, при w = 16 это 1 МБ таблиц.
Чтобы использовать таблицы журнала, используйте одну или несколько из следующих подпрограмм:
int galois_create_log_tables(int w); int galois_logtable_multiply(int a, int b, int w); int galois_logtable_divide(int a, int b, int w); int galois_log(int value, int w); int galois_ilog(int value, int w); int *galois_get_log_table(int w); int *galois_get_ilog_table(int w); |
Galois_create_log_tables (w) создает журнальные и обратные журнальные таблицы для заданного значения w и сохраняет их внутри. Если вы вызовете его дважды с тем же значением w , он не создаст новые таблицы во второй раз. Вы можете вызвать его с разными значениями w, и таблицы будут храниться отдельно.
В случае успеха функция galois_create_log_tables () вернет 0. В противном случае она вернет -1, и вся выделенная память будет освобождена.
Galois_logtable_multiply () и galois_logtable_divide () работают так же, как galois_single_multiply () и galois_single_divide (), за исключением того, что они предполагают, что вы вызвали galois_create_log_tables () для соответствующего значения w и что это было успешно. Они не проверяют ошибки, поэтому, если вы не вызывали galois_create_log_tables (), они будут сегментировать ошибки. Это решение было принято для скорости - хотя для средних значений w (от 10 до 22) galois_single_multiply () использует galois_logtable_multiply (), это значительно медленнее из-за проверки ошибок, которую она выполняет.
Galois_log () и galois_ilog () возвращают журнал и обратный журнал элемента GF (2 w ). Вы можете использовать их для умножения, используя следующую идентичность:
a * b = ilog[ (log[a] + log[b]) % ((1 << w)-1) ]
a / b = ilog[ (log[a] - log[b] + (1 << w)) % ((1 << w)-1) ]
Идентичность деления учитывает странное определение модульной арифметики с отрицательными числами в С.
Чтобы выполнить максимально быстрое умножение и деление с этими таблицами, вы должны получить доступ к самим таблицам с помощью galois_get_log_table (int w) и galois_get_ilog_table (int w) . Затем вы можете рассчитать произведение / частное a и b как:
a * b = ilog [ log[a] + log[b] ]
a / b = ilog [ log[a] - log[b] ]
Вам не нужно беспокоиться о модульной арифметике, потому что таблица ilog содержит три копии обратных журналов и определена для индексов от -2 w +1 до 2 2w -2 . Это экономит несколько инструкций.
6. Умножение со сдвигом и медленное деление: galois_shift_multiply() and galois_shift_divide()
Когда таблицы непригодны для использования, умножение и деление общего назначения осуществляется с помощью следующих двух процедур:
int galois_shift_multiply(int a, int b, int w); int galois_shift_divide(int a, int b, int w); |
Galois_shift_multiply () преобразует b в битовую матрицу w * w и умножает ее на битовый вектор a, чтобы создать вектор произведения. Вы можете увидеть квазиучебное описание этой техники в статье [P05]. Это значительно медленнее, чем методы, использующие таблицы. Однако он универсален и не требует предварительного выделения памяти.
Для деления Galois_shift_divide () также преобразует b в битовую матрицу, инвертирует ее, а затем умножает обратную матрицу на a. Таким образом, он * очень * медленный. Если я пойму, как реализовать это быстрее, я это сделаю.
Galois_single_multiply () использует galois_shift_multiply () для w между 23 и 31. Galois_single_divide () использует galois_shift_divide () для w между 23 и 32.
7. Частный случай w=32: galois_split_w8_multiply()
Наконец, для w = 32 определены следующие процедуры:
int galois_create_split_w8_tables(); int galois_split_w8_multiply(int a, int b); |
Galois_create_split_w8_tables () создает семь таблиц по 256 КБ каждая. Galois_split_w8_multiply () использует эти таблицы для умножения 32-битных чисел, разбивая их на четыре восьмибитовых числа каждое, а затем выполняя шестнадцать умножений и исключающих ИЛИ для вычисления произведения. Это крутая техника, предложенная мне Ченг Хуангом из Microsoft, и она в 16 раз быстрее, чем использование galois_shift_multiply () .
«Но не могли бы вы использовать эту технику для других значений w ?» Да, можно, но я не реализую это, потому что не думаю, что это так важно. Если это мнение изменится, я исправлю это.
Galois_single_multiply () использует galois_split_w8_multiply () для w = 32 .
Безопасность потоков
Единственно возможные условия гонки в этих кодах - это когда создаются различные таблицы. По этой причине galois_create_mult_tables () и galois_create_log_tables () должны быть защищены мьютексом, если безопасность потоков является проблемой.
Поскольку galois_single_multiply () и galois_single_divide () вызывают подпрограммы создания таблиц всякий раз, когда таблицы не существуют, если вы беспокоитесь о безопасности потоков, то для каждого значения w, которое вы будете использовать, вы должны убедиться, что первый вызов galois_single_multiply ( ) или galois_single_divide () защищен. После этого никакой защиты не требуется.
Тестирование приложений
Программы gf_mult, gf_div, gf_log, gf_ilog gf_inverse и gf_xor просты и позволяют тестировать различные подпрограммы для различных значений w .
Gf_basic_tester и gf_xor_tester проверяют и правильность, и скорость. Вызовите gf_xor_tester без аргументов, чтобы проверить скорость gf_region_xor () в вашей системе. Вот он, Macbook, процессор которого немного занят другими делами:
Unix-Prompt> gf_xor_tester XOR Tester Seeding random number generator with 1172533188 Passed correctness test -- doing 10-second timing 1827.79986 Megabytes of XORs per second Unix-Prompt>
Gf_basic_tester принимает следующие аргументы командной строки:
- W : от 1 до 32
- Метод : это одно из следующих слов: default , multtable , logtable , shift , splitw8 . Он определяет, как будет выполняться умножение / деление. По умолчанию используются gf_single_multiply () и gf_single_divide () .
- Ntrials : определяет количество случайных умножений / делений для проверки правильности.
После проверки на корректность gf_basic_tester проверяет скорость. Есть три особых случая. Когда method = default и w равно 8, 16 и 32, gf_basic_tester также проверяет скорость gf_w xx _region_multiply () .
Например (опять же, на моем MacBook):
Unix-Prompt> gf_basic_tester 16 default 100000 W: 16 Method: default Seeding random number generator with 1172533569 Doing 100000 trials for single-operation correctness. Passed Single-Operations Correctness Tests. Doing galois_w16_region_multiply correctness test. Passed galois_w16_region_multiply correctness test. Speed Test #1: 10 Seconds of Multiply operations Speed Test #1: 42.23862 Mega Multiplies per second Speed Test #2: 10 Seconds of Divide operations Speed Test #2: 43.42448 Mega Divides per second Doing 10 seconds worth of region_multiplies - Three tests: Test 0: Overwrite initial region Test 1: Products to new region Test 2: XOR products into second region Test 0: 253.45548 Megabytes of Multiplies per second Test 1: 238.59569 Megabytes of Multiplies per second Test 2: 167.99968 Megabytes of Multiplies per second
Reference
- [MS77] F. J. MacWilliams and N. J. A. Sloane, The Theory of Error-Correcting Codes, Part I, North-Holland Publishing Company, Amsterdam, New York, Oxford, 1977.
- [PW72] W. W. Peterson and E. J. Weldon, Jr., Error-Correcting Codes, Second Edition, The MIT Press, Cambridge, Massachusetts, 1972, ISBN: 0-262-16-039-0.
- [P97] J. S. Plank, "A Tutorial on Reed-Solomon Coding for Fault-Tolerance in RAID-like Systems," Software -- Practice & Experience, 27(9), September, 1997, pp. 995-1012. http://web.eecs.utk.edu/~jplank/plank/papers/SPE-9-97.html.
- [P05] J. S. Plank, "Optimizing Cauchy Reed-Solomon Codes for Fault-Tolerant Storage Applications", Technical Report CS-TR-05-569, University of Tennessee, November, 2005. http://web.eecs.utk.edu/~jplank/plank/papers/CS-05-569.html.
- [PD05] J. S. Plank and Y. Ding, "Note: Correction to the 1997 Tutorial on Reed-Solomon Coding", Software, Practice & Experience, Volume 35, Issue 2, February, 2005, pp. 189-194. http://web.eecs.utk.edu/~jplank/plank/papers/SPE-04.html.
- [VL82] J. H. van Lint, Introduction to Coding Theory, Springer-Verlag, New York, 1982.