Мне нужен быстрый способ получить позицию всех одного бита в 64-битном целое. Например, учитывая x = 123703
, я хотел бы заполнить массив idx[] = {0, 1, 2, 4, 5, 8, 9, 13, 14, 15, 16}
. Мы можем предположить, что мы знаем число бит априори. Это будет называться 10 ^ 12 - 10 ^ 15 раз, поэтому скорость имеет смысл. Самый быстрый ответ, который я получил до сих пор, - это следующий монстр, который использует каждый бит 64-битного целого в качестве индекса в таблицах, которые дают количество бит, установленных в этом байте, и их позиции:
int64_t x; // this is the input
unsigned char idx[K]; // this is the array of K bits that are set
unsigned char *dst=idx, *src;
unsigned char zero, one, two, three, four, five; // these hold the 0th-5th bytes
zero = x & 0x0000000000FFUL;
one = (x & 0x00000000FF00UL) >> 8;
two = (x & 0x000000FF0000UL) >> 16;
three = (x & 0x0000FF000000UL) >> 24;
four = (x & 0x00FF00000000UL) >> 32;
five = (x & 0xFF0000000000UL) >> 40;
src=tab0+tabofs[zero ]; COPY(dst, src, n[zero ]);
src=tab1+tabofs[one ]; COPY(dst, src, n[one ]);
src=tab2+tabofs[two ]; COPY(dst, src, n[two ]);
src=tab3+tabofs[three]; COPY(dst, src, n[three]);
src=tab4+tabofs[four ]; COPY(dst, src, n[four ]);
src=tab5+tabofs[five ]; COPY(dst, src, n[five ]);
где COPY
- оператор switch для копирования до 8 байтов, n
- это массив числа бит, заданного в байте, и tabofs
дает смещение в tabX
, которое удерживает позиции установить бит в X-й байт. Это примерно в 3 раза быстрее, чем разворачиваемые методы на основе цикла с помощью (см. ниже). Я в настоящее время повторяю __builtin_ctz()
на моем Xeon E5-2609.x
в лексикографическом порядке для заданного количества бит набор.
Есть ли лучший способ?
EDIT: добавлен пример (который я впоследствии исправил). Полный код доступен здесь: http://pastebin.com/79X8XL2P. Примечание: GCC с -O2, похоже, оптимизирует его, но компилятор Intel (который я использовал для его создания) не...
Кроме того, позвольте мне дать дополнительную информацию, чтобы рассмотреть некоторые из комментариев ниже. Цель состоит в том, чтобы выполнить статистический тест на каждом возможном подмножестве K переменных из универсума N возможных объясняющих переменных; конкретная цель прямо сейчас равна N = 41, но я вижу некоторые проекты, требующие N до 45-50. Тест в основном включает факторизацию соответствующей подматрицы данных. В псевдокоде что-то вроде этого:
double doTest(double *data, int64_t model) {
int nidx, idx[];
double submatrix[][];
nidx = getIndices(model, idx); // get the locations of ones in model
// copy data into submatrix
for(int i=0; i<nidx; i++) {
for(int j=0; j<nidx; j++) {
submatrix[i][j] = data[idx[i]][idx[j]];
}
}
factorize(submatrix, nidx);
return the_answer;
}
Я закодировал версию этого для платы Intel Phi, которая должна завершить N = 41 случай примерно через 15 дней, из которых ~ 5-10% времени тратится на наивный getIndices()
, поэтому сразу после bat более быстрая версия могла бы сэкономить день или больше. Я тоже работаю над реализацией для NVidia Kepler, но, к сожалению, проблема, которую я имею (смехотворные числа операций с малой матрицей), не идеально подходит для аппаратного обеспечения (смехотворно большие матричные операции). Тем не менее, этот документ представляет собой решение, которое, по-видимому, достигает сотен GFLOPS/s на матрицах моего размера, агрессивно разворачивая петли и выполняя всю факторизацию в регистрах, что размеры матрицы определяются во время компиляции. (Это разворачивание цикла должно помочь уменьшить накладные расходы и улучшить векторизация в версии Phi, поэтому getIndices()
станет более важным!) Итак, теперь я думаю, что мое ядро должно выглядеть больше:
double *data; // move data to GPU/Phi once into shared memory
template<unsigned int K> double doTestUnrolled(int *idx) {
double submatrix[K][K];
// copy data into submatrix
#pragma unroll
for(int i=0; i<K; i++) {
#pragma unroll
for(int j=0; j<K; j++) {
submatrix[i][j] = data[idx[i]][idx[j]];
}
}
factorizeUnrolled<K>(submatrix);
return the_answer;
}
Версия Phi решает каждую модель в цикле `cilk_for 'от модели = 0 до 2 ^ N (или, скорее, подмножество для тестирования), но теперь для пакетной работы для графического процессора и амортизации начальных издержек запуска ядра Я должен повторять номера моделей в лексикографическом порядке для каждого из K = от 1 до 41 бит (как отмечал doynax).
EDIT 2: Теперь, когда отпуск закончился, вот некоторые результаты на моем Xeon E5-2602 с помощью icc версии 15. Код, который я использовал для сравнения: http://pastebin.com/XvrGQUat. Я выполняю извлечение бит для целых чисел, у которых задано ровно K бит, поэтому есть некоторые накладные расходы для лексикографической итерации, измеренные в столбце "База" в таблице ниже. Они выполняются 2 ^ 30 раз с N = 48 (повторяя при необходимости).
"CTZ" - это цикл, который использует gcc intrinsic __builtin_ctzll
для получения бит младшего разряда:
for(int i=0; i<K; i++) {
idx[i] = __builtin_ctzll(tmp);
lb = tmp & -tmp; // get lowest bit
tmp ^= lb; // remove lowest bit from tmp
}
Отметка Mark не привязана к циклу:
for(int i=0; i<K; i++) {
*dst = i;
dst += x & 1;
x >>= 1;
}
Tab1 - мой исходный код на основе таблиц со следующим макросом копирования:
#define COPY(d, s, n) \
switch(n) { \
case 8: *(d++) = *(s++); \
case 7: *(d++) = *(s++); \
case 6: *(d++) = *(s++); \
case 5: *(d++) = *(s++); \
case 4: *(d++) = *(s++); \
case 3: *(d++) = *(s++); \
case 2: *(d++) = *(s++); \
case 1: *(d++) = *(s++); \
case 0: break; \
}
Tab2 - это тот же код, что и Tab1, но макрос копирования просто перемещает 8 байтов в виде одной копии (принимая идеи от doynax и Lưu Vĩnh Phúc... но обратите внимание, что это не обеспечивает выравнивание):
#define COPY2(d, s, n) { *((uint64_t *)d) = *((uint64_t *)s); d+=n; }
Вот результаты. Я предполагаю, что мое первоначальное утверждение о том, что Tab1 в 3 раза быстрее, чем CTZ, выполняется только для больших K (где я тестировал). Марк цикл быстрее, чем мой исходный код, но избавление от ветки в макросе COPY2
принимает торт для K > 8.
K Base CTZ Mark Tab1 Tab2
001 4.97s 6.42s 6.66s 18.23s 12.77s
002 4.95s 8.49s 7.28s 19.50s 12.33s
004 4.95s 9.83s 8.68s 19.74s 11.92s
006 4.95s 16.86s 9.53s 20.48s 11.66s
008 4.95s 19.21s 13.87s 20.77s 11.92s
010 4.95s 21.53s 13.09s 21.02s 11.28s
015 4.95s 32.64s 17.75s 23.30s 10.98s
020 4.99s 42.00s 21.75s 27.15s 10.96s
030 5.00s 100.64s 35.48s 35.84s 11.07s
040 5.01s 131.96s 44.55s 44.51s 11.58s