Какой эффективный по времени алгоритм для копирования невыровненных битовых массивов? - PullRequest
6 голосов
/ 21 августа 2010

В прошлом мне приходилось делать это много раз, и я никогда не был доволен результатами.

Может кто-нибудь предложить быстрый способ копирования непрерывного массива битов из источникак месту назначения, где и источник, и место назначения не могут быть выровнены (смещены вправо) на удобных границах процессора?

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

В качестве отправной точки мой код неизбежно будет выглядеть примерно так (непроверенный, игнорируйте побочные эффекты, это просто не работает)пример):

const char mask[8] = { 1, 3, 7, 15, 31, 63, 127, 255 };
/* Assume:
 * - destination is already zeroed,
 * - offsets are right shifts
 * - bits to copy is big (> 32 say)
 */
int bitarray_copy(char * src, int src_bit_offset, int src_bit_len,
                  char * dst, int dst_bit_offset) {
    if (src_bit_offset == dst_bit_offset) { /* Not very interesting */ 
    } else {
        int bit_diff_offset = src_bit_offset - dst_bit_offset; /* assume positive */
        int loop_count;
        char c;
        char mask_val = mask[bit_diff_offset];

        /* Get started, line up the destination. */
        c  = (*src++ << bit_diff_offset) | ((*src >> (8 - bit_diff_offset)) & mask_val);
        c &= mask[8-dst_bit_offset];

        *dst++ |= c;

        src_bit_len -= 8 - dst_bit_offset;
        loop_count = src_bit_len >> 3;

        while (--loop_count >= 0) 
            * dst ++ = (*src++ << bit_diff_offset) | ((*src >> (8 - bit_diff_offset)) & mask_val);

        /* Trailing tail copy etc ... */
        if (src_bit_len % 8) /* ... */
    }
}

(на самом деле это лучше, чем я делал раньше. Это выглядит не так уж плохо)

Ответы [ 4 ]

7 голосов
/ 21 августа 2010

Я так и сделал. ( РЕДАКТИРОВАТЬ Изменено 21.08.2014 для ошибки при копировании одного бита.)

#include <limits.h>
#include <string.h>
#include <stddef.h>

#define PREPARE_FIRST_COPY()                                      \
    do {                                                          \
    if (src_len >= (CHAR_BIT - dst_offset_modulo)) {              \
        *dst     &= reverse_mask[dst_offset_modulo];              \
        src_len -= CHAR_BIT - dst_offset_modulo;                  \
    } else {                                                      \
        *dst     &= reverse_mask[dst_offset_modulo]               \
              | reverse_mask_xor[dst_offset_modulo + src_len];    \
         c       &= reverse_mask[dst_offset_modulo + src_len];    \
        src_len = 0;                                              \
    } } while (0)


static void
bitarray_copy(const unsigned char *src_org, int src_offset, int src_len,
                    unsigned char *dst_org, int dst_offset)
{
    static const unsigned char mask[] =
        { 0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff };
    static const unsigned char reverse_mask[] =
        { 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff };
    static const unsigned char reverse_mask_xor[] =
        { 0xff, 0x7f, 0x3f, 0x1f, 0x0f, 0x07, 0x03, 0x01, 0x00 };

    if (src_len) {
        const unsigned char *src;
              unsigned char *dst;
        int                  src_offset_modulo,
                             dst_offset_modulo;

        src = src_org + (src_offset / CHAR_BIT);
        dst = dst_org + (dst_offset / CHAR_BIT);

        src_offset_modulo = src_offset % CHAR_BIT;
        dst_offset_modulo = dst_offset % CHAR_BIT;

        if (src_offset_modulo == dst_offset_modulo) {
            int              byte_len;
            int              src_len_modulo;
            if (src_offset_modulo) {
                unsigned char   c;

                c = reverse_mask_xor[dst_offset_modulo]     & *src++;

                PREPARE_FIRST_COPY();
                *dst++ |= c;
            }

            byte_len = src_len / CHAR_BIT;
            src_len_modulo = src_len % CHAR_BIT;

            if (byte_len) {
                memcpy(dst, src, byte_len);
                src += byte_len;
                dst += byte_len;
            }
            if (src_len_modulo) {
                *dst     &= reverse_mask_xor[src_len_modulo];
                *dst |= reverse_mask[src_len_modulo]     & *src;
            }
        } else {
            int             bit_diff_ls,
                            bit_diff_rs;
            int             byte_len;
            int             src_len_modulo;
            unsigned char   c;
            /*
             * Begin: Line things up on destination. 
             */
            if (src_offset_modulo > dst_offset_modulo) {
                bit_diff_ls = src_offset_modulo - dst_offset_modulo;
                bit_diff_rs = CHAR_BIT - bit_diff_ls;

                c = *src++ << bit_diff_ls;
                c |= *src >> bit_diff_rs;
                c     &= reverse_mask_xor[dst_offset_modulo];
            } else {
                bit_diff_rs = dst_offset_modulo - src_offset_modulo;
                bit_diff_ls = CHAR_BIT - bit_diff_rs;

                c = *src >> bit_diff_rs     &
                    reverse_mask_xor[dst_offset_modulo];
            }
            PREPARE_FIRST_COPY();
            *dst++ |= c;

            /*
             * Middle: copy with only shifting the source. 
             */
            byte_len = src_len / CHAR_BIT;

            while (--byte_len >= 0) {
                c = *src++ << bit_diff_ls;
                c |= *src >> bit_diff_rs;
                *dst++ = c;
            }

            /*
             * End: copy the remaing bits; 
             */
            src_len_modulo = src_len % CHAR_BIT;
            if (src_len_modulo) {
                c = *src++ << bit_diff_ls;
                c |= *src >> bit_diff_rs;
                c     &= reverse_mask[src_len_modulo];

                *dst     &= reverse_mask_xor[src_len_modulo];
                *dst |= c;
            }
        }
    }
}
1 голос
/ 21 августа 2010

Что будет оптимальным, будет зависеть от целевой платформы.На некоторых платформах без бочкообразных переключателей наиболее быстрым подходом будет смещение всего вектора влево или вправо на один бит, n раз, для n <3 (на платформе PIC18 цикл с 8-кратным развертыванием байтов для сдвига влево на один бит будет стоить 11количество циклов команд на восемь байт).В противном случае мне нравится шаблон (заметьте, что src2 придется инициализировать в зависимости от того, что вы хотите сделать с концом вашего буфера) </p>

  src1 = *src++;
  src2 = (src1 shl shiftamount1) | (src2 shr shiftamount2);
  *dest++ = src2;
  src2 = *src++;
  src1 = (src2 shl shiftamount1) | (src1 shr shiftamount2);
  *dest++ = src1;

Это должно обеспечить очень эффективную реализацию на ARM (восемь инструкцийкаждые два слова, если регистры доступны для src, dest, src1, src2, shifttamount1 и shifttamount 2. Использование большего количества регистров позволило бы более быструю работу с помощью инструкций загрузки / сохранения из нескольких слов. Обработка четырех слов была бы примерно такой (одна машинная инструкция настрока, за исключением того, что первые четыре строки вместе будут одной инструкцией, как и последние четыре строки):

  src0 = *src++;
  src1 = *src++;
  src2 = *src++;
  src3 = *src++;
  tmp  = src0;
  src0 = src0 shr shiftamount1
  src0 = src0 | src1 shl shiftamount2
  src1 = src1 shr shiftamount1
  src1 = src1 | src2 shl shiftamount2
  src2 = src2 shr shiftamount1
  src2 = src2 | src3 shl shiftamount2
  src3 = src3 shr shiftamount1
  src3 = src3 | tmp shl shiftamount2
  *dest++ = src0;
  *dest++ = src1;
  *dest++ = src2;
  *dest++ = src3;

Одиннадцать инструкций на 16 повернутых байтов.

1 голос
/ 21 августа 2010

Ваш внутренний цикл берет куски по два байта и перемещает их в целевой байт.Это почти оптимально.Вот еще несколько подсказок в произвольном порядке:

  • Нет необходимости ограничивать себя байтом за раз.Используйте самый большой целочисленный размер, с которым ваша платформа позволит вам избежать неприятностей.Это, конечно, усложнит вашу начальную и конечную логику.
  • Если вы используете беззнаковые символы или целые числа, вам может не потребоваться маскировать второй фрагмент источника после его смещения вправо.Это будет зависеть от вашего компилятора.
  • Если вам действительно нужна маска, убедитесь, что ваш компилятор перемещает поиск таблицы за пределы цикла.Если это не так, скопируйте его во временную переменную и используйте его.
0 голосов
/ 21 августа 2010

Ваше решение выглядит аналогично большинству из тех, что я видел: в основном выполняйте некоторую работу без выравнивания в начале и в конце, с основным циклом в середине, используя выровненный доступ.Если вам действительно нужна эффективность и вы делаете это на очень длинных битовых потоках, я бы предложил использовать в основном цикле что-то специфичное для архитектуры, например SSE2.

...