Noah Kochavi's blog

Computers Counting Part 5: Introduction to Parallelism

How fast can computers count?

This is the fifth post in a series of writing programs to see how fast computers can count. The series will start with naive methods, then will make use of optimization and later parallelism within the CPU, then will utilize a GPU, and the finale will use a dedicated FPGA for counting.

Previous posts:

  1. Naive counting
  2. Digging into the assembly
  3. Compiler Optimizations
  4. Handwritten Assembly

Hardware specs:

Software specs:

What is parallelism?

So far, all of my counting programs have set some variable to 0, incremented it by 1 many times, and then stopped counting once the variable has reached a certain large number. I am going to make a fundamental change that will allow for much faster counting, but will also slightly change the definition of “counting”.

My programs will now start multiple count variables at 0, have them increment by 1 simultaneously, and then all be summed up at some desired point. There might be multiple summations within the program where the counting is paused to accumulate a stored number, and then resumed later. The final sum of all counts is divided by the execution time from the first increment to the final accumulation to determine how “fast” the computer can count.

Here is a tabular example of how this might work in action:

Serial: 3.00B per second
Time        | 0ms | 100ms | 200ms |
-----------------------------------
Counter 1   | 0   | 300M  | 600M  |


Parallel: 12.0B per second
Time        | 0ms | 100ms | 200ms | 200.1ms
-------------------------------------------
Counter 1   | 0   | 300M  | 600M  | 0
Counter 2   | 0   | 300M  | 600M  | 0
Counter 3   | 0   | 300M  | 600M  | 0
Counter 4   | 0   | 300M  | 600M  | 0
Accumulator | 0   | 0     | 0     | 2.40B

We can even do this multiple times, such that we accumulate one value, and then keep counting, and then add the sum of those counts to the accumulator.

Isn’t this cheating?

If you think that this is cheating because I am not determining how quickly any one individual count module can count, that’s okay. Feel free to stop reading the blog here, as I have already achieved the peak performance that I know that I can muster from my hardware. Perhaps with a higher clock speed, dedicated system, or a counter ASIC, I could achieve a faster serial count speed.

But I think that counting in parallel is much more interesting. It allows me to really utilize the last 20 years of advancements in computer hardware, as the breakdown of Dennard Scaling made simply increasing the clock speed of a CPU next to impossible. Instead, CPUs have became multi-core, with added intra-core parallelism on top. Additionally, the rise of GPUs and general-purpose computing on GPUs has been meteoric, especially in this decade, since they are able to achieve massive computation throughput by computing thousands of things simultaneously. FPGAs are also useful in the parallel counting arena since I can “wire” it to produce a massively parallel counting array.

From here on out, the journey begins to see what is best at counting in parallel. I will map out the major paradigm shifts here:

  1. Using SIMD instructions on vector registers within a Zen 2 CPU core
  2. Utilizing all 12 of the cores of the CPU, eventually in combination with the 1st step
  3. Programming the RTX 5060 GPU with CUDA to utilize its 3840 individual cores
  4. Programming the PYNQ-Z2 FPGA to be a lean, mean counting machine

Summary table of serial counting speed

Method Increments / second
Very naive JavaScript 12.6M
Naive JavaScript 369M
Naive C 1.55B
Optimized C (-O0) 2.02B
Optimized C (-O3) 3.00B
Hand-optimized assembly 3.77B
Parallel counting ???