It would seem that loop bookkeeping dominates execution time here. Without it, I get quite different results:
>>> timeit.timeit("'%s' % i", setup="i = 42", number=1000000)
0.31799793243408203
>>> timeit.timeit("str(i)", setup="i = 42", number=1000000)
0.4146881103515625
This is another argument for profiling everything.