Побитовая перестановка нескольких 64-битных значений в параллельной / комбинированной - PullRequest
7 голосов
/ 20 августа 2011

Этот вопрос НЕ о том, «Как я делаю битовую перестановку». Теперь мы узнаем, как это сделать, что мы ищем, это более быстрый путь с меньшим количеством инструкций процессора, вдохновленный реализацией бит-слиса sbox в DES * 1002. *

Чтобы ускорить некоторый код шифра, мы хотим уменьшить количество вызовов перестановки. Основные функции шифрования выполняют несколько побитовых перестановок на основе массивов поиска. Поскольку операции перестановки являются только битовыми сдвигами,

Наша основная идея состоит в том, чтобы взять несколько входных значений, для которых требуется одна и та же перестановка, и сместить их параллельно. Например, если входной бит 1 необходимо переместить в выходной бит 6.

Есть ли способ сделать это? У нас нет примера кода прямо сейчас, потому что нет абсолютно никакой идеи, как сделать это эффективным способом.

Максимальный размер значения, который мы имеем на наших платформах, составляет 128 бит, самое длинное входное значение - 64 бита. Поэтому код должен быть быстрее, а затем выполнять всю перестановку 128 раз.

EDIT

Вот простой 8-битный пример перестановки

+---+---+---+---+---+---+---+---+
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <= Bits
+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <= Input
+---+---+---+---+---+---+---+---+
| 3 | 8 | 6 | 2 | 5 | 1 | 4 | 7 | <= Output
+---+---+---+---+---+---+---+---+

Шифр ​​использует несколько клавиш ввода. Это блочный шифр, поэтому один и тот же шаблон должен применяться ко всем 64-битным блокам ввода.

Поскольку перестановки одинаковы для каждого входного блока, мы хотим обработать несколько входных блоков за один шаг /, чтобы объединить операции для нескольких входных последовательностей. Вместо перемещения 128 раз по одному биту на вызов, перемещение по 1 разу 128 бит за раз.

EDIT2

Мы НЕ можем использовать потоки, так как мы должны запускать код во встроенных системах без поддержки потоков. Поэтому у нас также нет доступа к внешним библиотекам, и мы должны держать это открытым C.

РЕШЕНИЕ

После тестирования и игры с данными ответами мы сделали это следующим образом:

  • Мы помещаем одиночные биты 128 64-битных значений в массив uint128_t [64] *.
  • Для перестановки нужно просто скопировать указатели
  • После того, как все сделано, мы возвращаем первую операцию и получаем 128 перестановочных значений обратно

Да, это действительно так просто. Мы тестировали этот способ в начале проекта, но он был слишком медленным. Кажется, у нас была ошибка в тест-коде.

Спасибо всем за подсказки и терпение.

Ответы [ 4 ]

4 голосов
/ 21 августа 2011

Вы можете ускорить побитовый код Стэна, используя восемь справочных таблиц, отображающих байты в 64-битные слова.Чтобы обработать 64-битное слово из ввода, разделите его на восемь байтов и найдите каждое из другой справочной таблицы, а затем ИЛИ результаты.На моем компьютере последний в 10 раз быстрее, чем побитовый подход для 32-битных перестановок.Очевидно, что если ваша встроенная система имеет мало кеша, то 32 кБ 16 кБ справочных таблиц может стать проблемой.Если вы обрабатываете 4 бита за раз, вам нужно только 16 справочных таблиц по 16 * 8 = 128 байт каждая, то есть 2 кБ справочных таблиц.

РЕДАКТИРОВАТЬ: внутренний цикл может выглядеть примерно такэто:

void permute(uint64_t* input, uint64_t* output, size_t n, uint64_t map[8][256])
{
    for (size_t i = 0; i < n; ++i) {
        uint8_t* p = (uint8_t*)(input+i);
        output[i] = map[0][p[0]] | map[1][p[1]] | map[2][p[2]] | map[3][p[3]]
            | map[4][p[4]] | map[5][p[5]] | map[6][p[6]] | map[7][p[7]];
    }
}
2 голосов
/ 21 августа 2011

Я думаю, что вы, возможно, ищете реализацию нарезки битов .Вот как работают самые быстрые имплантации DES-крекинга.(Во всяком случае, это было до того, как появились инструкции SSE.)

Идея состоит в том, чтобы написать вашу функцию "побитовым" способом, представляя каждый выходной бит как логическое выражение над входными битами.Поскольку каждый выходной бит зависит только от входных битов, любая функция может быть представлена ​​таким образом, даже такие вещи, как сложение, умножение или поиск в S-блоках.

Хитрость заключается в том, чтобы использовать фактические биты одного регистрадля представления одного бита из нескольких входных слов .

Я проиллюстрирую это простой четырехбитной функцией.

Предположим, например, что вы хотите взять четыребитовые входы вида:

x3 x2 x1 x0

... и для каждого входа рассчитайте четырехбитный выход:

x2 x3 x2^x3 x1^x2

И вы хотите сделать это, скажем, для восьмивходы.(Хорошо, для четырех битов таблица поиска будет самой быстрой. Но это просто для иллюстрации принципа.)

Предположим, ваши восемь входных данных:

A = a3 a2 a1 a0
B = b3 b2 b1 b0
...
H = h3 h2 h1 h0

Здесь a3 a2 a1 a0 представляетчетыре бита входа A и т. д.

Сначала закодируйте все восемь входов в четыре байта, где каждый байт содержит один бит от каждого из восьми входов:

X3 =  a3 b3 c3 d3 e3 f3 g3 h3
X2 =  a2 b2 c2 d2 e2 f2 g2 h2
X1 =  a1 b1 c1 d1 e1 f1 g1 h1
X0 =  a0 b0 c0 d0 e0 f0 g0 h0

Здесьa3 b3 c3 ... h3 - это восемь бит X3.Он состоит из старших бит всех восьми входов.X2 - следующий бит из всех восьми входов.И так далее.

Теперь, чтобы вычислить функцию восемь раз параллельно, вы просто делаете:

Y3 = X2;
Y2 = X3;
Y1 = X2 ^ X3;
Y0 = X1 ^ X2;

Теперь Y3 содержит старшие биты из всех восьми выходов, Y2 содержит следующий бит извсе восемь выходов и тд.Мы только что вычислили эту функцию на восьми разных входах, используя только четыре машинные инструкции!

Еще лучше, если наш ЦП 32-битный (или 64-битный), мы могли бы вычислить эту функцию на 32 (или 64) входах, все еще используя только четыре инструкции.

Кодирование ввода и декодирование вывода в / из представления "битовой части", конечно, занимает некоторое время.Но для правильного рода функций этот подход предлагает огромный параллелизм на уровне битов и, следовательно, значительное ускорение.

Основное предположение состоит в том, что у вас есть много входов (например, 32 или 64), на которых вы хотите вычислитьта же функция, и что функция не является ни слишком сложной, ни слишком простой для представления в виде набора логических операций.(Слишком сложно делает необработанные вычисления медленными; слишком легко делает время доминирующим за счет самого кодирования / декодирования битовых фрагментов.) В частности, для криптографии, где (a) данные должны пройти множество «циклов» обработки, (b) алгоритм часто уже использует биты, и (c) вы, например, пробуете множество ключей для одних и тех же данных ... Он часто работает довольно хорошо.

1 голос
/ 20 августа 2011

Кажется, трудно сделать перестановку всего за один вызов. В особом случае вашей проблемы, обращая биты в целое число, требуется более одного «вызова» (что вы подразумеваете под call ?). См. Bit Twiddling Hacks by Sean для информации этого примера.

Если ваш шаблон сопоставления не сложен, возможно, вы сможете найти быстрый способ подсчета ответа :) Однако я не знаю, нравится ли вам этот прямой путь:

#include <stdio.h>

unsigned char mask[8];

//map bit to position
//0 -> 2
//1 -> 7
//2 -> 5
//...
//7 -> 6
unsigned char map[8] = {
    2,7,5,1,4,0,3,6
};


int main()
{
    int i;

    //input:
    //--------------------
    //bit 7 6 5 4 3 2 1 0
    //--------------------
    //val 0 0 1 0 0 1 1 0
    //--------------------
    unsigned char input = 0x26;

    //so the output should be 0xA1:
    //    1 0 1 0 0 0 0 1
    unsigned char output;

    for(i=0; i<8; i++){ //initialize mask once
        mask[i] = 1<<i;
    }

    //do permutation
    output = 0;
    for(i=0; i<8; i++){
        output |= (input&mask[i])?mask[map[i]]:0;
    }

    printf("output=%x\n", output);
    return 0;
}
0 голосов
/ 20 августа 2011

Лучше всего было бы рассмотреть схему потоков определенного типа ... либо вы можете использовать систему передачи сообщений, где вы отправляете каждый блок фиксированному набору рабочих потоков, либо вы можете настроить конвейер с не- блокировка одиночных очередей производителя / потребителя, которые выполняют несколько смен "синхронно".Я говорю «синхронный», потому что конвейер на ЦП общего назначения не будет по-настоящему синхронной конвейерной операцией, как это было бы на устройстве с фиксированной функцией, но в основном в течение определенного «отрезка» времени каждый поток работал бы надодин этап многоэтапной проблемы в одно и то же время, и вы будете «перетекать» исходные данные в конвейер и из него.

...