Basically I have an __m256i variable where each byte represents a position that needs to be set in a uint64_t. Note all byte values will be < 64.
I'm at somewhat of a loss for how to do this even remotely efficiently.
One option I was considering is in certain circumstances there are a lot of duplicates between the bytes so something along the lines of:
__m256i indexes = foo();
uint64_t result = 0;
uint32_t aggregate_mask = ~0;
do {
uint32_t idx = _mm256_extract_epi8(indexes, __tzcnt_u32(aggregate_mask));
uint32_t idx_mask =
_mm256_movemask_epi8(_mm256_cmpeq_epi(indexes, _mm256_set1_epi8(idx)));
aggregate_mask ^= idx_mask;
result |= ((1UL) << idx);
} while (aggregate_mask);
With enough duplicates I believe this could be somewhat efficient but I can't guarantee that there will always be sufficient duplicates to make this faster than just iterating through the bytes and setting sequentially.
My goal is to find something this will ALWAYS be faster than what feels like the worst case:
__m256i indexes = foo();
uint8_t index_arr[32];
_mm256_store_si256((__m256i *)index_arr, indexes);
uint64_t result = 0;
for (uint32_t i = 0; i < 32; ++i) {
result |= ((1UL) << index_arr[i];
}
If possible I am looking for a solution that can run on skylake (w.o AVX512). If AVX512 is necessary (I was thinking there might be something semi efficient with grouping then using _mm256_shldv_epi16) something is better than nothing :)
This is what I am thinking. Going from epi32:
// 32 bit
__m256i lo_shifts = _mm256_sllv_epi32(_mm256_set1_epi32(1), indexes);
__m256i t0 = _mm256_sub_epi32(indexes, _mm256_set1_epi32(1));
__m256i hi_shifts = _mm256_sllv_epi32(_mm256_set1_epi32(1), t0);
__m256i lo_shifts_lo = _mm256_shuffle_epi32(lo_shifts, 0x5555);
__m256i hi_shifts_lo = _mm256_shuffle_epi32(hi_shifts, 0x5555);
__m256i hi_shifts_hi0 = _mm256_slli_epi64(hi_shifts, 32);
__m256i hi_shifts_hi1 = _mm256_slli_epi64(hi_shifts_lo, 32);
__m256i all_hi_shifts = _mm256_or_epi64(hi_shifts_hi0, hi_shifts_hi1);
__m256i all_lo_shifts_garbage = _mm256_or_epi64(lo_shifts_lo, lo_shifts);
__m256i all_lo_shifts = _mm256_and_epi64(all_lo_shifts_garbage, _mm256_set1_epi64(0xffffffff));
__m256i all_shifts = _mm256_or_epi64(all_lo_shifts, all_hi_shifts);
or going from epi64 bit:
// 64 bit
__m256i indexes0 = _m256_and_epi64(indexes, _mm256_set1_epi64(0xffffffff));
__m256i indexes1 = _m256_shuffle_epi32(indexes, 0x5555);
__m256i shifts0 = _m256_sllv_epi64(_mm256_set1_epi64x(1), indexes0);
__m256i shifts1 = _m256_sllv_epi64(_mm256_set1_epi64x(1), indexes1);
__m256i all_shifts = _m256_or_epi64(shifts0, shifts1);
My guess is from epi64 is faster.