XORs, swizzling, and reductions
One way to think about XOR is as a bit flip: XORing a bit with is a no-op, while XORing it with flips it. The same idea extends to bitwise XOR: to compute , flip each bit of wherever the corresponding bit of is .
The rule is straightforward, but I never had a good intuition for what bitwise XOR does to a number. That changed when I visualized what XOR does to a sequence.
XORs and sequences
Consider the sequence from to . Apply each mask independently to the original sequence, using masks with exactly one set bit:

XOR with flips the lowest bit, so each even number trades places with the odd number next to it. XOR with flips the next bit, swapping adjacent blocks of two. The masks and swap blocks of four and eight. Applying the same mask twice restores the original order: .
What happens when a mask has more than one set bit? The result is the same as applying the corresponding single-bit masks in succession. The order does not matter: XOR is associative and commutative.
For the sequence , each -bit mask produces a distinct permutation, built from swaps of adjacent blocks whose sizes are powers of two. There are possible masks, giving distinct XOR permutations.
A mask with all bits set reverses the sequence by successively swapping adjacent blocks of increasing size. We can reach the same conclusion algebraically: the original number and its bitwise complement add up to , so .
We can apply these permutations to indices, too. XOR can then select which worker to exchange values with, or where to store an element in memory.
Reductions
Suppose four workers, numbered through , hold values . We want to compute their sum and leave the result in every worker. This is an all-reduce.
At each stage, worker combines its current value with the previous-stage value from worker , where is the XOR mask. The illustration shows two stages, using masks and . Multiple letters inside a tile represent the sum of those values:

The first stage pairs workers and , and workers and . The first pair now holds ; the second holds . The second stage pairs workers and , and workers and , leaving all four with .
At each stage, the XOR mask pairs workers whose partial sums contain disjoint sets of inputs. Combining those sums doubles the number of original values included in each worker’s sum without counting anything twice. With this butterfly pattern, a power-of-two group of workers needs stages, using masks .
Swizzling
Swizzling changes the mapping from logical array indices to physical memory locations. An XOR swizzle can avoid bank conflicts in GPU shared memory. We’ll use NVIDIA’s CUDA programming model as a concrete example.
For the scalar 32-bit accesses considered here, shared memory has 32 banks, each serving 4-byte words. A word at index maps to bank . Threads in a warp can access different banks in parallel. When multiple threads access different words in the same bank, however, those accesses must be serialized, reducing throughput. This is called a bank conflict.
Suppose we have a matrix of 32-bit floating-point values. The matrix occupies 4 KiB of shared memory. We want the 32 threads in a warp to read either a whole row or a whole column, with each thread reading one element.
Assume we store the matrix in row-major order, using zero-based indices. The element at row , column has word offset . Taking this offset modulo keeps only its lowest five bits, which come entirely from .
When reading a row, is fixed and runs from to , so every thread accesses a different bank. When reading a column, is fixed and varies, but . Every thread therefore accesses a different word in the same bank.
XOR lets us mix the row index into those bits: store each element in column instead. For a fixed logical column , the bank indices cover all 32 banks exactly once as runs from to . Row accesses remain conflict-free because XOR with a fixed row index simply permutes the columns.
The following example illustrates the idea. Each entry labels an element by its original column index. The bank-conflict argument above applies to the layout:

The swizzled word offset for logical element is . Both reads and writes use this mapping; the stored values themselves are unchanged. Practical GPU layouts choose which address bits to XOR according to the element size and access pattern.