Confusion about "false sharing"
The false sharing is happening in the totals memory block.

The smallest unit of cache is a cache line. On standard x86 and ARM systems, a cache line is 64 bytes. On some PowerPC archs, the cache line is 128 or even 256 bytes. Reading from main memory into the cache is done one cache line at a time, and cache invalidation is done one cache line at a time.

Imagine each element of totals is a uint8. 64 elements of totals fit in one cache line. Every time += is called, the 64 byte cache line containing totals[tid] is read from and written to. That means every core is constantly invalidating that cache line, and the ensuing coordination, “false sharing”, is costly and unnecessary.

You could fix this while maintaining similar code structure by


Padding each element of totals so that it takes up a whole cache line
If totals has enough elements, offsetting your access so that multiple threads tend not to manipulate the same cache line. Ex: index by (tid * 64) % n_elements