Вот простые и эффективные функции, которые вычисляют минимальные и максимальные значения набора из 64 байтовых значений, закодированных как 8 uint64_t
пакетов, каждая из которых хранит 1 бит каждого из 64 значений:
#include <stdint.h>
uint8_t maxslice(const uint64_t s[8]) {
uint8_t max = 0, bit = 0x80;
uint64_t mask = ~0ULL;
for (int i = 8; i-- > 0; bit >>= 1) {
uint64_t x = s[i] & mask;
if (x) {
max |= bit;
mask &= x;
}
}
return max;
}
uint8_t minslice(const uint64_t s[8]) {
uint8_t min = 0, bit = 0x80;
uint64_t mask = ~0ULL;
for (int i = 8; i-- > 0; bit >>= 1) {
uint64_t x = ~s[i] & mask;
if (x) {
min |= bit;
mask &= x;
}
}
return ~min;
}
Как можно проверить на Godbolt's Compiler Explorer , clang
генерирует автономный код для обеих функций.
Для вашей расширенной цели вычисления минимума из большего набора значений, организованных таким образом, uint64_t slices[8][100]
, вы можете просто выполнить итерацию этого кода в массиве и постепенно вычислить минимум. Возможно, стоит проверить на каждом шаге этого l oop, был ли уже найден абсолютный минимум 0
. Сложность состоит в том, как организован массив:
uint64_t slices[8][100]
определяет массив из 8 массивов по 100 uint64_t
. Другими словами, макет в памяти состоит из 6400 младших битов, затем 6400 битов порядка 2, ..., наконец, 6400 битов веса 128.
uint8_t minarray(const uint64_t s[8][100]) {
uint8_t all_max = 0;
for (int j = 0; j < 100; j++) {
uint8_t max = 0, bit = 0x80;
uint64_t mask = ~0ULL;
for (int i = 8; i-- > 0; bit >>= 1) {
uint64_t x = ~s[i][j] & mask;
if (x) {
max |= bit;
mask &= x;
}
}
if (all_max < max) {
all_max = max;
if (all_max == 255)
break;
}
}
return ~all_max;
}
Чтобы векторизовать этот код, мы можем транспонировать циклы: вычисление с x
и mask
в виде массивов 100 uint64_t
даст тот же результат, но позволит компилятору векторизовать некоторые из внутренних циклов:
uint8_t minarray1(const uint64_t s[8][100]) {
uint8_t max = 0, bit = 0x80;
uint64_t mask[100] = {
~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL,
~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL,
~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL,
~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL,
~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL,
~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL,
~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL,
~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL,
~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL,
~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL, ~0ULL,
};
for (int i = 8; i-- > 0; bit >>= 1) {
uint64_t x[100];
uint64_t xall = 0;
for (int j = 0; j < 100; j++) {
x[j] = ~s[i][j] & mask[j];
xall |= x[j];
}
if (xall) {
max |= bit;
for (int j = 0; j < 100; j++) {
mask[j] &= x[j];
}
}
}
return ~max;
}
Снова clang генерирует развернутый векторизованный код . Бенчмаркинг покажет, дает ли этот подход лучшую производительность, чем предыдущий.