Live data from Hacker News

The NSA Instruction (2019)

vaibhavsagar.com

11–20 of 98 posts

Re: The NSA Instruction (2019)

#11
post #8

Here's a dumb question. If someone asked me to do it I'd probably write code like: while(x != 0) { c += x&1; x >>= 1; } Is this something that should be added to LLVM? Edit: flip the order

Popcount is easily recognized by llvm (and it’s actually mentioned in the article...) In the case of the code you’ve posted, you’re shifting out the LSB before you check the bit, so it’s not quite right, but (in general) popcount is recognized and used when possible.

Yep my bad! I think flipping the order should work still though.

The two links in the article:

https://lemire.me/blog/2016/05/23/the-surprising-cleverness-...

And the LLVM source indicate to me it only picks up on x&(x-1) pattern, which would miss the popcount optimization on code like mine.

Re: The NSA Instruction (2019)

#12

Another interesting application of popcount is in computer vision, namely in matching keypoints that use binary descriptors for 3D reconstruction in SLAM/TRN etc

Yep, I've used __builtin_popcountll for ORB from OpenCV (256 bit binary descriptors).

Looks like we've done similar things :)

Horror story: I was once developing a TRN system for a spacecraft instrument which uses an ancient x86 processor that does not have popcnt, ended up using a look-up table instead...

Re: The NSA Instruction (2019)

#13
post #11

Earlier quoted context omitted.

Popcount is easily recognized by llvm (and it’s actually mentioned in the article...) In the case of the code you’ve posted, you’re shifting out the LSB before you check the bit, so it’s not quite right, but (in general) popcount is recognized and used when possible.

Yep my bad! I think flipping the order should work still though. The two links in the article: https://lemire.me/blog/2016/05/23/the-surprising-cleverness-... And the LLVM source indicate to me it only picks up on x&(x-1) pattern, which would miss the popcount optimization on code like mine.

Flipping the order works, except if the LSB on x is set.

https://godbolt.org/z/qdWhxMPsf

Note the run output under clang.

edit:

> And the LLVM source indicate to me it only picks up on x&(x-1) pattern, which would miss the popcount optimization on code like mine.

Thanks for teaching me something this morning. That's annoying.

I think the portable solution is std::popcount in C++ (or equivalent in Rust).

Re: The NSA Instruction (2019)

#14
>You might be wondering, like I was, if there’s more to this instruction, but that’s all it does! This doesn’t seem very useful, right?

I have a hard time understanding how anybody who has done most any non-trivial amount of bit maniplation couldn't think of plenty of uses.

Re: The NSA Instruction (2019)

#15
post #11

Earlier quoted context omitted.

Yep my bad! I think flipping the order should work still though. The two links in the article: https://lemire.me/blog/2016/05/23/the-surprising-cleverness-... And the LLVM source indicate to me it only picks up on x&(x-1) pattern, which would miss the popcount optimization on code like mine.

Flipping the order works, except if the LSB on x is set. https://godbolt.org/z/qdWhxMPsf Note the run output under clang. edit: > And the LLVM source indicate to me it only picks up on x&(x-1) pattern, which would miss the popcount optimization on code like mine. Thanks for teaching me something this morning. That's annoying. I think the portable solution is std::popcount in C++ (or equivalent in Rust).

> or equivalent in Rust

https://doc.rust-lang.org/std/?search=count_ones

Internally Rust actually just staples LLVM's implementation into your code, via an intrinsic - but if that were ever to change the standard library count_ones() methods will do whatever happens instead so you should use that.

Re: The NSA Instruction (2019)

#16
post #8

Here's a dumb question. If someone asked me to do it I'd probably write code like: while(x != 0) { c += x&1; x >>= 1; } Is this something that should be added to LLVM? Edit: flip the order

I came across this long ago. But it shows some very nice ways to fiddle bits. It has a few different ways to do it. Which would be handy on systems that do not have a popcount.

https://graphics.stanford.edu/~seander/bithacks.html

Re: The NSA Instruction (2019)

#17

>You might be wondering, like I was, if there’s more to this instruction, but that’s all it does! This doesn’t seem very useful, right? I have a hard time understanding how anybody who has done most any non-trivial amount of bit maniplation couldn't think of plenty of uses.

Why did you write this comment?

Re: The NSA Instruction (2019)

#18
post #17

>You might be wondering, like I was, if there’s more to this instruction, but that’s all it does! This doesn’t seem very useful, right? I have a hard time understanding how anybody who has done most any non-trivial amount of bit maniplation couldn't think of plenty of uses.

Why did you write this comment?

It’s immediately relevant to the very beginning of the article.

Re: The NSA Instruction (2019)

#20
GPU-programmers use popcount-based programming all the time these days, but the abstractions are built on top and are hardware accelerated.

CUDA's __activemask(); returns the 32-bit value of your current 32-wide EXEC mask. That is to say, if your current warp is:

    int foo = 0;
    if(threadIdx.x %= 2){
      foo = __activemask(); 
    }
foo will be "0b01010101...." or 0x55555555. This __activemask() has a number of useful properties should you use __popc with it.

popcount(__activemask()); returns the number of threads executing.

lanemask_lt() returns "0b0000000000000001" for the 0th lane. 0b0000000000000011 for the 1st lane. 0b0000000000000111... for the 2nd lane... and 111111111...111 for the last 31st lane.

popcount(__activemask() & lanemask_lt()); returns the "active lane count". All together now, we can make a parallel SIMD-stack that can push/pop together in parallel.

    int head = 0;
    char buffer[0x1000];

    while(fooBar()){ // Dynamic! We don't know who is, or is not active anymore
        int localPrefix = __popc(__activemask() & __lanemask_lt());
        int totalWarpActive = __popc(__activemask()); 
        buffer[head + localPrefix] = generateValueThisThread();
        if(localPrefix == 0){
            head += totalWarpActive; // Move the head forward, much like a "push" operation in single-thread land
            // Only one thread should move the head
        }
         __syncthreads(); // Thread barrier, make sure everyone is waiting on activeThread#0 before continuing.
    }
------------

As such, you can dynamically load-balance between GPU threads (!!!) from a shared stack with minimal overheads.

If you want to extend this larger than one 32-wide CUDA-warp, you'll need to use __shared __ memory to share the prefix with the rest of the block.

It is a bad idea (too much overhead) to extend this much larger than a block, as there's no quick way to communicate outside of your block. Still though, having chunks of up to 1024 threads synchronized through a shared data-structure that only has nanoseconds of overhead is a nifty trick.

-----------

EDIT: Oh right, and this concept is now replicated very, very quickly in the dedicated __ballot_sync(...) function (which compiles down to just a few assembly instructions).

Playing with the "Exec-mask" is a hugely efficient way to synchronously, and dynamically gather information across your warp. So lots of little tricks have been built around this.

Post reply on HN