Быстрый алгоритм по модулю 12 для 4 uint16_t упакован в uint64_t - PullRequest
0 голосов
/ 16 февраля 2019

Рассмотрим следующее объединение:

union Uint16Vect {
    uint16_t _comps[4];
    uint64_t _all;
};

Существует ли быстрый алгоритм для определения, равен ли каждый компонент 1 по модулю 12 или нет?

Наивная последовательность кода:

Uint16Vect F(const Uint16Vect a) {
    Uint16Vect r;
    for (int8_t k = 0; k < 4; k++) {
        r._comps[k] = (a._comps[k] % 12 == 1) ? 1 : 0;
    }
    return r;
}

Ответы [ 5 ]

0 голосов
/ 17 февраля 2019

Деление на константы, подобные этому, должно быть выполнено с умножением на мультипликативное обратное .Как вы можете видеть, компиляторы оптимизируют x/12 до x*43691 >> 19

bool h(uint16_t x)
{
    return x % 12 == 1;
}
h(unsigned short):
        movzx   eax, di
        imul    eax, eax, 43691 ; = 0xFFFF*8/12 + 1
        shr     eax, 19
        lea     eax, [rax+rax*2]
        sal     eax, 2
        sub     edi, eax
        cmp     di, 1
        sete    al
        ret

Поскольку в SSE / AVX есть команды умножения, это можно легко векторизовать.Кроме того, x = (x % 12 == 1) ? 1 : 0; можно упростить до x = (x % 12 == 1) и затем преобразовать в x = (x - 1) % 12 == 0, что позволяет избежать таблицы констант для сравнения значения 1.Вы можете использовать векторное расширение , чтобы gcc автоматически генерировал для вас код

typedef uint16_t ymm __attribute__((vector_size(32)));
ymm mod12(ymm x)
{
    return !!((x - 1) % 12);
}

Ниже приведен вывод из gcc

mod12(unsigned short __vector(16)):
        vpcmpeqd    ymm3, ymm3, ymm3  ; ymm3 = -1
        vpaddw      ymm0, ymm0, ymm3
        vpmulhuw    ymm1, ymm0, YMMWORD PTR .LC0[rip] ; multiply with 43691
        vpsrlw      ymm2, ymm1, 3
        vpsllw      ymm1, ymm2, 1
        vpaddw      ymm1, ymm1, ymm2
        vpsllw      ymm1, ymm1, 2
        vpcmpeqw    ymm0, ymm0, ymm1
        vpandn      ymm0, ymm0, ymm3
        ret

Clang и ICC не поддерживают !! для векторных типов, поэтому вам нужно изменить на (x - 1) % 12 == 0.К сожалению, похоже, что компиляторы не поддерживают __attribute__((vector_size(8)) для выдачи инструкций MMX.Но в настоящее время вы все равно должны использовать SSE или AVX

Вывод для x % 12 == 1 короче, как вы можете видеть в той же ссылке Godbolt выше, но вам нужна таблица, содержащая 1 для сравнения, которая может быть лучше или нет,Проверьте, какой из них работает быстрее в вашем случае


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

#define S1(x) ((x) + 0) % 12 == 1, ((x) + 1) % 12 == 1, ((x) + 2) % 12 == 1, ((x) + 3) % 12 == 1, \
              ((x) + 4) % 12 == 1, ((x) + 4) % 12 == 1, ((x) + 6) % 12 == 1, ((x) + 7) % 12 == 1
#define S2(x) S1((x + 0)*8), S1((x + 1)*8), S1((x + 2)*8), S1((x + 3)*8), \
              S1((x + 4)*8), S1((x + 4)*8), S1((x + 6)*8), S1((x + 7)*8)
#define S3(x) S2((x + 0)*8), S2((x + 1)*8), S2((x + 2)*8), S2((x + 3)*8), \
              S2((x + 4)*8), S2((x + 4)*8), S2((x + 6)*8), S2((x + 7)*8)
#define S4(x) S3((x + 0)*8), S3((x + 1)*8), S3((x + 2)*8), S3((x + 3)*8), \
              S3((x + 4)*8), S3((x + 4)*8), S3((x + 6)*8), S3((x + 7)*8)

bool mod12e1[65536] = {
    S4(0U), S4(8U), S4(16U), S4(24U), S4(32U), S4(40U), S4(48U), S4(56U)
}

. Для использования просто замените x % 12 == 1 на mod12e1[x].Это, конечно, может быть векторизовано

Но поскольку результат равен только 1 или 0, вы также можете использовать 65536-битный массив , чтобы уменьшить размер только до 8 КБ


Вы также можете проверить делимость на 12 делением на 4 и 3. Делимость на 4 очевидно тривиальна.Делимость на 3 можно рассчитать несколькими способами:

  • . Вычисляется разница между суммой нечетных цифр и суммой четные цифры как в ответе גלעד ברקן и проверьте, делится ли оно на 3 или нет
  • Или вы можете проверить, равна ли сумма цифр в базе 2 2k (например, основание 4, 16, 64 ...), чтобы увидеть, делится ли оно на 3 или нет.

    Это работает, потому что в базе b проверяется делимостьлюбые делители n из b - 1, просто проверьте, делится ли сумма цифр на n или нет.Вот его реализация

    void modulo12equals1(uint16_t d[], uint32_t size) {
        for (uint32_t i = 0; i < size; i++)
        {
            uint16_t x = d[i] - 1;
            bool divisibleBy4 = x % 4 == 0;
            x = (x >> 8) + (x & 0x00ff); // max 1FE
            x = (x >> 4) + (x & 0x000f); // max 2D
            bool divisibleBy3 = !!((01111111111111111111111ULL >> x) & 1);
            d[i] = divisibleBy3 && divisibleBy4;
        }
    }
    

Кредиты на делимость на 3 - Роланд Иллиг

Поскольку вывод автовекторизованной сборки слишком длинный,Вы можете проверить это по ссылке Godbolt

См. также

0 голосов
/ 17 февраля 2019

Если это поможет ограничить операции битовыми операциями и popcount, мы можем заметить, что действительный кандидат должен пройти два теста, поскольку вычитание 1 должно означать делимость на 4 и 3. Во-первых, последние два бита должны быть * 1002.*.Затем делим на 3, что мы можем найти, вычитая попконт с нечетным положением из попконта с четным позицией.

const evenMask = parseInt('1010101010101010', 2);
// Leave out first bit, we know it will be zero
// after subtracting 1
const oddMask = parseInt('101010101010100', 2);

console.log('n , Test 1: (n & 3)^3, Test 2: popcount diff:\n\n');

for (let n=0; n<500; n++){
  if (n % 12 == 1)
    console.log(
      n,
      (n & 3)^3,
      popcount(n & evenMask) - popcount(n & oddMask))
}

// https://stackoverflow.com/questions/43122082/efficiently-count-the-number-of-bits-in-an-integer-in-javascript
function popcount(n) {
  var tmp = n;
  var count = 0;
  while (tmp > 0) {
    tmp = tmp & (tmp - 1);
    count++;
  }
  return count;
}
0 голосов
/ 17 февраля 2019

Это лучшее, что я мог придумать

uint64_t F(uint64_t vec) {
    //512 = 4 mod 12  -> max val 0x3FB
    vec = ((vec & 0xFE00FE00FE00FE00L) >> 7) + (vec & 0x01FF01FF01FF01FFL);
    //64 = 4 mod 12 -> max val 0x77
    vec = ((vec & 0x03C003C003C003C0L) >> 4) + (vec & 0x003F003F003F003FL);
    //16 = 4 mod 12 -> max val 0x27
    vec = ((vec & 0x0070007000700070L) >> 2) + (vec & 0x000F000F000F000FL);
    //16 = 4 mod 12 -> max val 0x13
    vec = ((vec & 0x0030003000300030L) >> 2) + (vec & 0x000F000F000F000FL);
    //16 = 4 mod 12 -> max val 0x0f
    vec = ((vec & 0x0030003000300030L) >> 2) + (vec & 0x000F000F000F000FL);

    //Each field is now 4 bits, and only 1101 and 0001 are 1 mod 12.
    //The top 2 bits must be equal and the other2 must be 0 and 1

    return vec & ~(vec>>1) & ~((vec>>2)^(vec>>3)) & 0x0001000100010001L;
}
0 голосов
/ 16 февраля 2019

Обратите внимание, что x mod 12 == 1 подразумевает, что x mod 4 == 1, а последний является чрезвычайно дешевым.

Так:

is_mod_12 = ((input & 3) == 1) && ((input % 12) == 1);

будет, в случае, когда input mod 4 часто не1, сэкономьте вам много операций по модулю.Однако, если input mod 4 обычно равен 1, производительность будет немного хуже.

...