FizzBuzz

Writing a fast and efficient FizzBuzz.

Posted by dbunker on September 24, 2010

The FizzBuzz problem is as follows:

Write a program that prints the numbers from 1 to 100. But for multiples of three print “Fizz” instead of the number and for the multiples of five print “Buzz”. For numbers which are multiples of both three and five print “FizzBuzz”

Below is a simple python implementation:

for i in range(1,101):
    if i%3 == 0 and i%5 == 0:
        print 'FizzBuzz'
    elif i%3 == 0:
        print 'Fizz'
    elif i%5 == 0:
        print 'Buzz'
    else:
        print str(i)

However, this can be made more efficient, though at the expense of readability. Below it is written in C, and takes advantage of the fact that the Fizz Buzz pattern repeats every 15. This allows for the removal of the conditionals.

#include <stdio.h>

int main(int argc, char *argv[]){
    for(int i=1; i<77; i+=15)
        printf("%u\n%u\nFizz\n%u\nBuzz\nFizz\n%u\n%u\nFizz\nBuzz\n%u\nFizz\n%u\n%u\nFizzBuzz\n",
            i, i+1, i+3, i+6, i+7, i+10, i+12, i+13);
    printf("91\n92\nFizz\n94\nBuzz\nFizz\n97\n98\nFizz\Buzz\n");
    return 0;
}

This becomes the following assembly code from clang when set to maximum optimization (clang -S fizzbuzz.c -O3).

	.section	__TEXT,__text,regular,pure_instructions
	.macosx_version_min 10, 11
	.globl	_main
	.align	4, 0x90
_main:                                  ## @main
	.cfi_startproc
## BB#0:
	pushq	%rbp
Ltmp0:
	.cfi_def_cfa_offset 16
Ltmp1:
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
Ltmp2:
	.cfi_def_cfa_register %rbp
	pushq	%rbx
	subq	$24, %rsp
Ltmp3:
	.cfi_offset %rbx, -24
	movl	$14, 16(%rsp)
	movl	$13, 8(%rsp)
	movl	$11, (%rsp)
	leaq	L_.str(%rip), %rbx
	movl	$1, %esi
	movl	$2, %edx
	movl	$4, %ecx
	movl	$7, %r8d
	movl	$8, %r9d
	xorl	%eax, %eax
	movq	%rbx, %rdi
	callq	_printf
	movl	$29, 16(%rsp)
	movl	$28, 8(%rsp)
	movl	$26, (%rsp)
	movl	$16, %esi
	movl	$17, %edx
	movl	$19, %ecx
	movl	$22, %r8d
	movl	$23, %r9d
	xorl	%eax, %eax
	movq	%rbx, %rdi
	callq	_printf
	movl	$44, 16(%rsp)
	movl	$43, 8(%rsp)
	movl	$41, (%rsp)
	movl	$31, %esi
	movl	$32, %edx
	movl	$34, %ecx
	movl	$37, %r8d
	movl	$38, %r9d
	xorl	%eax, %eax
	movq	%rbx, %rdi
	callq	_printf
	movl	$59, 16(%rsp)
	movl	$58, 8(%rsp)
	movl	$56, (%rsp)
	movl	$46, %esi
	movl	$47, %edx
	movl	$49, %ecx
	movl	$52, %r8d
	movl	$53, %r9d
	xorl	%eax, %eax
	movq	%rbx, %rdi
	callq	_printf
	movl	$74, 16(%rsp)
	movl	$73, 8(%rsp)
	movl	$71, (%rsp)
	movl	$61, %esi
	movl	$62, %edx
	movl	$64, %ecx
	movl	$67, %r8d
	movl	$68, %r9d
	xorl	%eax, %eax
	movq	%rbx, %rdi
	callq	_printf
	movl	$89, 16(%rsp)
	movl	$88, 8(%rsp)
	movl	$86, (%rsp)
	movl	$76, %esi
	movl	$77, %edx
	movl	$79, %ecx
	movl	$82, %r8d
	movl	$83, %r9d
	xorl	%eax, %eax
	movq	%rbx, %rdi
	callq	_printf
	leaq	L_str(%rip), %rdi
	callq	_puts
	xorl	%eax, %eax
	addq	$24, %rsp
	popq	%rbx
	popq	%rbp
	retq
	.cfi_endproc

	.section	__TEXT,__cstring,cstring_literals
L_.str:                                 ## @.str
	.asciz	"%u\n%u\nFizz\n%u\nBuzz\nFizz\n%u\n%u\nFizz\nBuzz\n%u\nFizz\n%u\n%u\nFizzBuzz\n"

	.align	4                       ## @str
L_str:
	.asciz	"91\n92\nFizz\n94\nBuzz\nFizz\n97\n98\nFizz\nBuzz"


.subsections_via_symbols

The compiler has unrolled the loop, pulling out the 6 times the loop would run. Of course the true fastest method would be to simply print FizzBuzz directly as it is only 413 characters, 412 excluding the trailing new line.

Some simple profiling using the time command provides the following representative results.

time python fizzbuzz.py

real    0m0.026s
user    0m0.014s
sys     0m0.009s

time ./fizzbuzz

real    0m0.002s
user    0m0.000s
sys     0m0.002s

A clear speed improvement can be seen. Though this is an apples to oranges comparison as python is an interpreted and c a compiled language.