Noah Kochavi's blog

Computers Counting Part 4: Handwritten Assembly

How fast can computers count?

This is the 4th 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

Hardware specs:

Software specs:

Do we even need this?

If you go back to the last part, you’ll notice that the -O1 compiled option is already very well optimized. Here it is for your convienence:

        call    gettimeofday
        movl    $100000000, %eax
        .p2align 3
.L2:
        subq    $1, %rax
        jne     .L2
        movq    %rsp, %rdi
        movl    $0, %esi
        call    gettimeofday

There is only 1 instruction between the start of the loop and branch instruction, and that instruction is the counter instruction. The count instruction is technically a decrement instruction, so I didn’t count this, as I want to count upwards. So yes, I do need this, but only on technicality.

The hand-optimized assembly

I am no expert at writing assembly code, so I just took the -O1 version and made some tweaks:

  1. Count up from 0 to 100,000,000 rather than count down. This does require an extra instruction (cmpl) to compare to 100,000,000.
  2. Used 32-bit registers, since I am counting within the signed 32-bit range. This range ends at a bit over 2 billion. You can see this with the l at the end of the assembly instructions, as the l stands for “long”, or 32-bit. You can also see this with the e at the beginning of the registers, as the e stands for “extended”, or 32-bit.
  3. Put the counter and comparison number into registers eax and ebx, respectively, for quick and easy access.

Here it is in all its glory:

	call	gettimeofday
	movl	$0, %eax
	movl	$100000000, %ebx
	.p2align 3
.L2:
	addl	$1, %eax
	cmpl	%ebx, %eax
	jb	.L2
	movq	%rsp, %rdi
	movl	$0, %esi
	call	gettimeofday

To compile and run this assembly, I just need to invoke gcc as if the assembly is C code: gcc hand-optimized.s -o hand-optimized. Running the executable gives me this output:

Counted to 100 million in 26496 microseconds.

Success! This is faster than anything that has been ran before on this blog. It’s also remarkably consistent, as I ran the program 10 times and the slowest run took 26884 microseconds. Note that I’ve been using the same OS and compiler version throughout my blogs, so I’m not sure why it’s so much more consistent all of the sudden.

The final form?

So far, I have gone from writing naive count programs to hand-writing assembly. Surely this is the final step, we can’t go faster than assembly. Yes, if we don’t utilize a nifty thing called parallelism. This tool will allow drastically faster counting if used well. Stay tuned for the next part!

Summary table of 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