Подтвердить что ты не робот

Оптимизация кодового словаря (полиномиальное деление)

Я пытаюсь оптимизировать кодер Рида-Соломона, который на самом деле просто является операцией многочленного деления над полями Галуа 2 ^ 8 (что просто означает, что значения обтекают более 255). Код на самом деле очень похож на то, что можно найти здесь для Go: http://research.swtch.com/field

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

И вот результат на моей машине:

With PyPy:
rsenc : total time elapsed 0.108183 seconds.
rsenc_alt1 : output is incorrect!
rsenc_alt1 : total time elapsed 0.164084 seconds.
rsenc_alt2 : output is incorrect!
rsenc_alt2 : total time elapsed 0.557697 seconds.

Without PyPy:
rsenc : total time elapsed 3.518857 seconds.
rsenc_alt1 : output is incorrect!
rsenc_alt1 : total time elapsed 5.630897 seconds.
rsenc_alt2 : output is incorrect!
rsenc_alt2 : total time elapsed 6.100434 seconds.
rsenc_numpy : output is incorrect!
rsenc_numpy : total time elapsed 1.631373 seconds

(Примечание: альтернативы должны быть правильными, некоторый индекс должен быть немного выключен, но поскольку они медленнее, я все равно не пытался их исправить)

/UPDATE и цель щедрости: я нашел очень интересный трюк оптимизации, чтобы promises ускорить вычисления: to . Я обновил код выше с помощью новой функции rsenc_precomp(). Тем не менее, в моей реализации нет никакой выгоды, она даже немного медленнее:

rsenc : total time elapsed 0.107170 seconds.
rsenc_precomp : total time elapsed 0.108788 seconds.

Как может быть, что поиск массивов стоит больше, чем операции с дополнениями или xor? Почему это работает в ZFEC, а не в Python?

Я приписываю награду тому, кто может показать мне, как сделать эту оптимизацию для оптимизации умножения/добавления (быстрее, чем операции xor и добавления), или кто может объяснить мне ссылки или анализ, почему эта оптимизация не может работать здесь (используя Python/PyPy/Cython/Numpy и т.д. Я пробовал их все).

4b9b3361

Ответ 1

Ниже на 3 раза быстрее, чем pypy на моей машине (0.04s против 0.15s). Использование Cython:

ctypedef unsigned char uint8_t # does not work with Microsoft C Compiler: from libc.stdint cimport uint8_t
cimport cpython.array as array

cdef uint8_t[::1] gf_exp = bytearray([1, 3, 5, 15, 17, 51, 85, 255, 26, 46, 114, 150, 161, 248, 19,
   lots of numbers omitted for space reasons
   ...])

cdef uint8_t[::1] gf_log = bytearray([0, 0, 25, 1, 50, 2, 26, 198, 75, 199, 27, 104, 
    more numbers omitted for space reasons
    ...])

import cython

@cython.boundscheck(False)
@cython.wraparound(False)
@cython.initializedcheck(False)
def rsenc(msg_in_r, nsym, gen_t):
    '''Reed-Solomon encoding using polynomial division, better explained at http://research.swtch.com/field'''

    cdef uint8_t[::1] msg_in = bytearray(msg_in_r) # have to copy, unfortunately - can't make a memory view from a read only object
    cdef int[::1] gen = array.array('i',gen_t) # convert list to array

    cdef uint8_t[::1] msg_out = bytearray(msg_in) + bytearray(len(gen)-1)
    cdef int j
    cdef uint8_t[::1] lgen = bytearray(gen.shape[0])
    for j in xrange(gen.shape[0]):
        lgen[j] = gf_log[gen[j]]

    cdef uint8_t coef,lcoef

    cdef int i
    for i in xrange(msg_in.shape[0]):
        coef = msg_out[i]
        if coef != 0: # coef 0 is normally undefined so we manage it manually here (and it also serves as an optimization btw)
            lcoef = gf_log[coef] # precaching

            for j in xrange(1, gen.shape[0]): # optimization: can skip g0 because the first coefficient of the generator is always 1! (that why we start at position 1)
                msg_out[i + j] ^= gf_exp[lcoef + lgen[j]] # equivalent (in Galois Field 2^8) to msg_out[i+j] -= msg_out[i] * gen[j]

    # Recopy the original message bytes
    msg_out[:msg_in.shape[0]] = msg_in
    return msg_out

Это ваша самая быстрая версия со статическими типами (и проверка html от cython -a до тех пор, пока петли не будут выделены желтым цветом).

Несколько кратких заметок:

  • Cython предпочитает x.shape[0] - len(shape)

  • Определяя представления памяти как [::1] promises, они непрерывны в памяти, что помогает

  • initializedcheck(False) необходимо избегать множества проверок существования на глобально определенных gf_exp и gf_log. (Возможно, вы можете ускорить свой базовый код Python/PyPy, создав для них локальную ссылку на переменную и используя этот istead)

  • Мне пришлось скопировать пару входных аргументов. Cython не может создать представление памяти из объекта readonly (в данном случае msg_in). Я мог бы, возможно, просто сделать это char *). Также gen (список) должен быть в чем-то с быстрым доступом к элементу.

Кроме того, все это довольно прямолинейно. (Я не пробовал никаких вариантов его получения быстрее). Я действительно впечатлен тем, насколько хорошо работает PyPy.

Ответ 2

Основываясь на ответе DavidW, вот реализация, которую я сейчас использую, что примерно на 20% быстрее, используя nogil и параллельное вычисление:

from cython.parallel import parallel, prange

@cython.boundscheck(False)
@cython.wraparound(False)
@cython.initializedcheck(False)
cdef rsenc_cython(msg_in_r, nsym, gen_t) :
    '''Reed-Solomon encoding using polynomial division, better explained at http://research.swtch.com/field'''

    cdef uint8_t[::1] msg_in = bytearray(msg_in_r) # have to copy, unfortunately - can't make a memory view from a read only object
    #cdef int[::1] gen = array.array('i',gen_t) # convert list to array
    cdef uint8_t[::1] gen = gen_t

    cdef uint8_t[::1] msg_out = bytearray(msg_in) + bytearray(len(gen)-1)
    cdef int i, j
    cdef uint8_t[::1] lgen = bytearray(gen.shape[0])
    for j in xrange(gen.shape[0]):
        lgen[j] = gf_log_c[gen[j]]

    cdef uint8_t coef,lcoef
    with nogil:
        for i in xrange(msg_in.shape[0]):
            coef = msg_out[i]
            if coef != 0: # coef 0 is normally undefined so we manage it manually here (and it also serves as an optimization btw)
                lcoef = gf_log_c[coef] # precaching

                for j in prange(1, gen.shape[0]): # optimization: can skip g0 because the first coefficient of the generator is always 1! (that why we start at position 1)
                    msg_out[i + j] ^= gf_exp_c[lcoef + lgen[j]] # equivalent (in Galois Field 2^8) to msg_out[i+j] -= msg_out[i] * gen[j]

    # Recopy the original message bytes
    msg_out[:msg_in.shape[0]] = msg_in
    return msg_out

Мне бы хотелось, чтобы он был быстрее (при реальной реализации данные кодируются со скоростью около 6.4 МБ/с с n = 255, n - размером сообщения + кодового слова).

Основной причиной более быстрой реализации, которую я нашел, является использование подхода LUT (LookUp Table) путем предварительного вычисления массивов умножения и сложения. Однако в моих реализациях Python и Cython подход LUT медленнее, чем вычисление операций XOR и добавления.

Существуют и другие подходы к внедрению более быстрого кодировщика RS, но у меня нет возможностей и времени, чтобы опробовать их. Я оставлю их в качестве ссылок для других заинтересованных читателей:

  • "Быстрая реализация программного обеспечения конечных полевых операций", Чэн Хуан и Лихао Сюй, Вашингтонский университет в Сент-Луисе, Tech. Rep (2003). ссылка и правильная реализация кода здесь.
  • Ло, Цзяньцян и др. "Эффективные программные реализации больших конечных полей GF (2 n) для приложений безопасного хранения". ACM Transactions on Storage (TOS) 8.1 (2012): 2.
  • "Оценка эффективности и изучение библиотек кодирования Erasure для хранения с открытым исходным кодом", Plank, J. S. and Luo, J. and Schuman, C.D. и Xu, L. и Wilcox-O'Hearn, Z, FAST. Том 9. 2009. ссылкаИли также не расширенную версию: "Сравнение производительности библиотек кодирования стирания с открытым исходным кодом для приложений хранения", Plank и Schuman.
  • Исходный код библиотеки ZFEC с оптимизацией оптимизации LUT ссылка.
  • "Оптимизированная арифметика для кодов Рида-Соломона", Кристоф Паар (1997, июнь). В Международном симпозиуме IEEE по теории информации (стр. 250-250). ИНСТИТУТ ЭЛЕКТРОТЕХНИЧЕСКИХ ИНЖЕНЕРОВ INC (IEEE). ссылка
  • "Быстрый алгоритм кодирования (255,233) Рид-Соломонова кода над GF (2 ^ 8)", Р.Л. Миллер и Т.К. Чыонг, И.С. Рид. ссылка
  • "Оптимизация арифметики поля Галуа для различных процессорных архитектур и приложений", Гринана, Кевина и М., Этана и Л. Миллера и Томаса Дж. Э. Шварца, Моделирование, анализ и моделирование компьютеров и телекоммуникационных систем, 2008. MASCOTS 2008. IEEE Международный симпозиум. IEEE, 2008. ссылка
  • Anvin, H. Peter. "Математика RAID-6". (2007). ссылка и ссылка
  • Wirehair library, одна из немногих реализаций Коши Рида-Соломона, которая называется очень быстрой.
  • "Логарифмический логический алгоритм времени для параллельного полиномиального деления", Бини, Д. и Пан, В.Ю. (1987), Буквы обработки информации, 24 (4), 233-237. См. Также Бини, Д. и В. Пан. "Быстрые параллельные алгоритмы для полиномиального деления над произвольным полем констант". Компьютеры и математика с приложениями 12.11 (1986): 1105-1118. link
  • Kung, H.T. "Быстрая оценка и интерполяция". (1973). ссылка
  • Цао, Чжэнцзюнь и Хэньюэ Цао. "Обратите внимание на алгоритм быстрого деления для многочленов, использующих итерацию Ньютона". arXiv preprint arXiv: 1112.4014 (2011). ссылка
  • "Введение в поля Галуа и кодирование Рида-Соломона", Джеймс Уэстолл и Джеймс Мартин, 2010. ссылка
  • Мамиди, Суман и др. "Расширения набора инструкций для кодирования и декодирования Рида-Соломона". Application-Specific Systems, Архитектурные процессоры, 2005. ASAP 2005. 16-я Международная конференция IEEE. IEEE, 2005. ссылка
  • Дюма, Жан-Гийом, Лоран Фюсс и Бруно Сальви. "Одновременная модулярная редукция и замена Кронекера для малых конечных полей". Journal of Symbolic Computation 46.7 (2011): 823-840.
  • Грина, Кевин М., Этан Л. Миллер и Томас Шварц. Анализ и построение месторождений galois для повышения надежности хранения. Том 9. Технический отчет UCSC-SSRC-07, 2007. ссылка

Однако, я считаю, что лучше всего использовать эффективную многочленную модульную редукцию вместо полиномиального деления:

  • "Модульное сокращение в GF (2 n) без предварительной вычислительной фазы". Kneževic, M., et al. Арифметика конечных полей. Springer Berlin Heidelberg, 2008. 77-87.
  • "О вычислении полиномиальной модулярной редукции". Ву, Хуапенг. Технический отчет, Univ. Ватерлоо, Центр прикладных криптографических исследований, 2000 г.
  • "Быстрая реализация программного обеспечения для арифметических операций в GF (2n)". De Win, E., Bosselaers, A., Vandenberghe, S., De Gersem, P. и Vandewalle, J. (1996, январь). В Advances in Cryptology-Asiacrypt'96 (стр. 65-76). Спрингер Берлин Гейдельберг. ссылка
  • Barnett reduction

/EDIT: на самом деле кажется, что "при вычислении полиномиальной модульной редукции" просто использует тот же подход, что и я, с вариантами rsenc_alt1() и rsenc_alt2() (основная идея состоит в том, что мы предкоммутируем пары коэффициентов, которые мы будем необходимо и сократить их все сразу), и, к несчастью, это не быстрее (это на самом деле медленнее, потому что прекомпция не может быть выполнена один раз для всех, поскольку она зависит от ввода сообщения).

/EDIT: я нашел библиотеку с действительно интересными оптимизациями, много, которые даже не найдены ни в каких научных статьях (которые автор заявил, что он прочитал кстати), и которая, вероятно, является самой быстрой программной реализацией Рида-Соломона: проект wirehair и кодек Коши-Рида-Соломона под названием longhair с аналогичными трюками оптимизации.

/FINAL EDIT: кажется, что самая быстрая реализация доступна на основе этой статьи:

Планк, Джеймс С., Кевин М. Гринан и Этан Л. Миллер. "Кричащий быстрая арифметика поля Галуа с использованием инструкций Intel SIMD." FAST. 2013. ссылка

Реализация в чистом Go доступна здесь и является автором Klaus Post. Это самая быстрая реализация, о которой я когда-либо читал, как в одном потоке, так и в параллельном (он поддерживает оба). Он требует более 1 ГБ/с в одном потоке и более 4 ГБ/с с 8 потоками. Однако он опирается на оптимизированные инструкции SIMD и различные низкоуровневые оптимизации для матричных операций (потому что здесь кодек RS ориентирован матрицей вместо полиномиального подхода, который у меня есть в моем вопросе).

Итак, если вы заинтересованный читатель и хотите найти самый быстрый доступный кодек Рида-Соломона, то тот.

Ответ 3

В качестве альтернативы, если вы знаете C, я бы рекомендовал переписать эту функцию Python в plain C и вызвать ее (скажем, с CFFI). По крайней мере, вы знаете, что вы достигаете максимальной производительности во внутренних циклах ваших функций, не зная о том, что вы используете PyPy или Cython.

Смотрите: http://cffi.readthedocs.org/en/latest/overview.html#performance