I'm tinkering around with AVX-2 instructions and I'm looking for a fast way to count the number of leading zeros in a __m256i
word (which has 256 bits).
So far, I have figured out the following way:
// Computes the number of leading zero bits.
// Here, avx_word is of type _m256i.
if (!_mm256_testz_si256(avx_word, avx_word)) {
uint64_t word = _mm256_extract_epi64(avx_word, 0);
if (word > 0)
return (__builtin_clzll(word));
word = _mm256_extract_epi64(avx_word, 1);
if (word > 0)
return (__builtin_clzll(word) + 64);
word = _mm256_extract_epi64(avx_word, 2);
if (word > 0)
return (__builtin_clzll(word) + 128);
word = _mm256_extract_epi64(avx_word, 3);
return (__builtin_clzll(word) + 192);
} else
return 256; // word is entirely zero
However, I find it rather clumsy to figure out the exact non-zero word within the 256 bit register.
Does anybody know if there is a more elegant (or faster) way to do this?
Just as an additional information: I actually want to compute the index of the first set bit for arbitrarily long vectors created by logical ANDs, and I am comparing the performance of standard 64 bit operations with SSE and AVX-2 code. Here is my entire test code:
#include <stdio.h>
#include <stdlib.h>
#include <immintrin.h>
#include <stdint.h>
#include <assert.h>
#include <time.h>
#include <sys/time.h>
#include <stdalign.h>
#define ALL 0xFFFFFFFF
#define NONE 0x0
#define BV_SHIFTBITS ((size_t) 6)
#define BV_MOD_WORD ((size_t) 63)
#define BV_ONE ((uint64_t) 1)
#define BV_ZERO ((uint64_t) 0)
#define BV_WORDSIZE ((uint64_t) 64)
uint64_t*
Vector_new(
size_t num_bits) {
assert ((num_bits % 256) == 0);
size_t num_words = num_bits >> BV_SHIFTBITS;
size_t mod = num_bits & BV_MOD_WORD;
if (mod > 0)
assert (0);
uint64_t* words;
posix_memalign((void**) &(words), 32, sizeof(uint64_t) * num_words);
for (size_t i = 0; i < num_words; ++i)
words[i] = 0;
return words;
}
void
Vector_set(
uint64_t* vector,
size_t pos) {
const size_t word_index = pos >> BV_SHIFTBITS;
const size_t offset = pos & BV_MOD_WORD;
vector[word_index] |= (BV_ONE << (BV_MOD_WORD - offset));
}
size_t
Vector_and_first_bit(
uint64_t** vectors,
const size_t num_vectors,
const size_t num_words) {
for (size_t i = 0; i < num_words; ++i) {
uint64_t word = vectors[0][i];
for (size_t j = 1; j < num_vectors; ++j)
word &= vectors[j][i];
if (word > 0)
return (1 + i * BV_WORDSIZE + __builtin_clzll(word));
}
return 0;
}
size_t
Vector_and_first_bit_256(
uint64_t** vectors,
const size_t num_vectors,
const size_t num_avx_words) {
for (size_t i = 0; i < num_avx_words; ++i) {
const size_t addr_offset = i << 2;
__m256i avx_word = _mm256_load_si256(
(__m256i const*) (vectors[0] + addr_offset));
// AND the AVX words
for (size_t j = 1; j < num_vectors; ++j) {
avx_word = _mm256_and_si256(
avx_word,
_mm256_load_si256((__m256i const*) (vectors[j] + addr_offset))
);
}
// test whether resulting AVX word is not zero
if (!_mm256_testz_si256(avx_word, avx_word)) {
uint64_t word = _mm256_extract_epi64(avx_word, 0);
const size_t shift = i << 8;
if (word > 0)
return (1 + shift + __builtin_clzll(word));
word = _mm256_extract_epi64(avx_word, 1);
if (word > 0)
return (1 + shift + __builtin_clzll(word) + 64);
word = _mm256_extract_epi64(avx_word, 2);
if (word > 0)
return (1 + shift + __builtin_clzll(word) + 128);
word = _mm256_extract_epi64(avx_word, 3);
return (1 + shift + __builtin_clzll(word) + 192);
}
}
return 0;
}
size_t
Vector_and_first_bit_128(
uint64_t** vectors,
const size_t num_vectors,
const size_t num_avx_words) {
for (size_t i = 0; i < num_avx_words; ++i) {
const size_t addr_offset = i << 1;
__m128i avx_word = _mm_load_si128(
(__m128i const*) (vectors[0] + addr_offset));
// AND the AVX words
for (size_t j = 1; j < num_vectors; ++j) {
avx_word = _mm_and_si128(
avx_word,
_mm_load_si128((__m128i const*) (vectors[j] + addr_offset))
);
}
// test whether resulting AVX word is not zero
if (!_mm_test_all_zeros(avx_word, avx_word)) {
uint64_t word = _mm_extract_epi64(avx_word, 0);
if (word > 0)
return (1 + (i << 7) + __builtin_clzll(word));
word = _mm_extract_epi64(avx_word, 1);
return (1 + (i << 7) + __builtin_clzll(word) + 64);
}
}
return 0;
}
uint64_t*
make_random_vector(
const size_t num_bits,
const size_t propability) {
uint64_t* vector = Vector_new(num_bits);
for (size_t i = 0; i < num_bits; ++i) {
const int x = rand() % 10;
if (x >= (int) propability)
Vector_set(vector, i);
}
return vector;
}
size_t
millis(
const struct timeval* end,
const struct timeval* start) {
struct timeval e = *end;
struct timeval s = *start;
return (1000 * (e.tv_sec - s.tv_sec) + (e.tv_usec - s.tv_usec) / 1000);
}
int
main(
int argc,
char** argv) {
if (argc != 6)
printf("fuck %s\n", argv[0]);
srand(time(NULL));
const size_t num_vectors = atoi(argv[1]);
const size_t size = atoi(argv[2]);
const size_t num_iterations = atoi(argv[3]);
const size_t num_dimensions = atoi(argv[4]);
const size_t propability = atoi(argv[5]);
const size_t num_words = size / 64;
const size_t num_sse_words = num_words / 2;
const size_t num_avx_words = num_words / 4;
assert(num_vectors > 0);
assert(size > 0);
assert(num_iterations > 0);
assert(num_dimensions > 0);
struct timeval t1;
gettimeofday(&t1, NULL);
uint64_t*** vectors = (uint64_t***) malloc(sizeof(uint64_t**) * num_vectors);
for (size_t j = 0; j < num_vectors; ++j) {
vectors[j] = (uint64_t**) malloc(sizeof(uint64_t*) * num_dimensions);
for (size_t i = 0; i < num_dimensions; ++i)
vectors[j][i] = make_random_vector(size, propability);
}
struct timeval t2;
gettimeofday(&t2, NULL);
printf("Creation: %zu ms\n", millis(&t2, &t1));
size_t* results_64 = (size_t*) malloc(sizeof(size_t) * num_vectors);
size_t* results_128 = (size_t*) malloc(sizeof(size_t) * num_vectors);
size_t* results_256 = (size_t*) malloc(sizeof(size_t) * num_vectors);
gettimeofday(&t1, NULL);
for (size_t j = 0; j < num_iterations; ++j)
for (size_t i = 0; i < num_vectors; ++i)
results_64[i] = Vector_and_first_bit(vectors[i], num_dimensions,
num_words);
gettimeofday(&t2, NULL);
const size_t millis_64 = millis(&t2, &t1);
printf("64 : %zu ms\n", millis_64);
gettimeofday(&t1, NULL);
for (size_t j = 0; j < num_iterations; ++j)
for (size_t i = 0; i < num_vectors; ++i)
results_128[i] = Vector_and_first_bit_128(vectors[i],
num_dimensions, num_sse_words);
gettimeofday(&t2, NULL);
const size_t millis_128 = millis(&t2, &t1);
const double factor_128 = (double) millis_64 / (double) millis_128;
printf("128 : %zu ms (factor: %.2f)\n", millis_128, factor_128);
gettimeofday(&t1, NULL);
for (size_t j = 0; j < num_iterations; ++j)
for (size_t i = 0; i < num_vectors; ++i)
results_256[i] = Vector_and_first_bit_256(vectors[i],
num_dimensions, num_avx_words);
gettimeofday(&t2, NULL);
const size_t millis_256 = millis(&t2, &t1);
const double factor_256 = (double) millis_64 / (double) millis_256;
printf("256 : %zu ms (factor: %.2f)\n", millis_256, factor_256);
for (size_t i = 0; i < num_vectors; ++i) {
if (results_64[i] != results_256[i])
printf("ERROR: %zu (64) != %zu (256) with i = %zu\n", results_64[i],
results_256[i], i);
if (results_64[i] != results_128[i])
printf("ERROR: %zu (64) != %zu (128) with i = %zu\n", results_64[i],
results_128[i], i);
}
free(results_64);
free(results_128);
free(results_256);
for (size_t j = 0; j < num_vectors; ++j) {
for (size_t i = 0; i < num_dimensions; ++i)
free(vectors[j][i]);
free(vectors[j]);
}
free(vectors);
return 0;
}
To compile:
gcc -o main main.c -O3 -Wall -Wextra -pedantic-errors -Werror -march=native -std=c99 -fno-tree-vectorize
To execute:
./main 1000 8192 50000 5 9
The parameters mean: 1000 testcases, vectors of length 8192 bits, 50000, test repetitions (last two parameters are minor tweaks).
Sample output for the above call on my machine:
Creation: 363 ms
64 : 15000 ms
128 : 10070 ms (factor: 1.49)
256 : 6784 ms (factor: 2.21)