I'm mostly trying to understand why the simpler char
array mask
below (to track which digits have been already used) is much faster than the one 16 bit uint mask
along with intrinsic ops. On Ryzen 3700x, compiled with "-O3 -march=native", this version takes only 1.02 secs for 10 million calls, whereas the commented out uint16
mask
approach takes 2.37 secs.
#include <iostream>
#include <cstdint>
#include <chrono>
#include <x86intrin.h>
unsigned int get_digit(unsigned int i, char *mask){
unsigned int j = 0;
while(1){
while(mask[j] == 0) j++;
if(i == 0) return j;
i--;
j++;
}
}
inline unsigned nthset(uint16_t x, unsigned n) {
return _tzcnt_u64(_pdep_u64(1U << n, x));
}
uint64_t foo(int i){
constexpr uint64_t factorials[16] = {1,1,2,6,24,120,720,5040,40320,362880, 3628800, 39916800, 479001600, 6227020800, 87178291200, 1307674368000};
uint64_t base = (1ULL<<60);
// NOTE: Not sure why, but the simpler char array mask below is much faster than the
// clever 16 bit mask with intrinsic ops on Ryzen 3700x.
//uint16_t mask = 0b1111'1111'1111'1111;
char mask[16] = {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1};
uint64_t res = 0;
int n = 15;
while(n){
const unsigned int v = factorials[n];
const unsigned int d = get_digit(i / v, mask);
//const unsigned int d = nthset(mask, i / v);
mask[d] = 0;
//mask = mask & ~(1<<d);
i = i%v;
res += d * base;
base = base >> 4;
n--;
}
//unsigned int d = nthset(mask, 0);
unsigned int d = get_digit(0, mask);
res += d;
return res;
}