"how to benchmark the efficiencies?"
measure empirical orders of growth, that's how! :)
e.g. with data from the accepted answer, log_10(50.41/0.9) = 1.75
, and log_10(2.31/0.21) = 1.04
, so it's ~ n^1.75
empirical order of growth for your TD (trial division) code (in the range 1,000 ... 10,000) vs. ~ n^1.04
for the sieve of Eratosthenes.
Which, the latter, is consistent with n log log n
, and the former, with n^2 / log(n)^2
, as it should.
With g(n) = n^2 / log(n)^2
, we have g(10000)/g(1000) = 56.25
, which is just 0.4% off the empirical value 50.41/0.9 = 56.01
. But with g2(n) = n^2 / log(n)
, we have g2(10000)/g2(1000) = 75
, which is way off from the evidence.
About time complexity: actually, most of composites fail early (are multiples of small primes). To produce k=n/log(n)
primes takes O(k^2)
time here, testing each prime number by all its preceding primes, a.o.t. just those not exceeding its square root.
Composites don't add to the complexity (exact analysis in M. ONeill's JFP article (pg 4) gives the same complexity for composites as for primes when testing up to sqrt
- each composite is guaranteed to have a prime factor not greater than its sqrt
- so complexity for composites is even less than the complexity for primes here).
So overall it's O(k^2)
, which is to say, O(n^2/log(n)^2)
.
By adding just two lines you can improve your code's speed drastically, from O(n^2/log(n)^2)
to O(n^1.5/log(n)^2)
time complexity:
for j in ans:
if j*j > i:
ans.append(i); break;
if i % j == 0:
break
# else:
# ans.append(i)
The improvement in time complexity means the run time ratio of 17.8x
for 10,000 over 1,000, instead of 56.25x
as before. This translates to ~ n^1.25
empirical order of growth in this range (instead of ~ n^1.75
). The absolute run time for 10,000 call will be much closer to the 2.31 seconds than the old 50.41 seconds.
BTW your original code is equivalent to the famous code by David Turner,
primes = sieve [2..]
sieve (x:xs) = x : sieve [y | y <- xs, rem y x /= 0]
and the improved code, to this:
primes = 2 : sieve [3..] primes
sieve xs (p:ps) | (h,t) <- span (< p*p) xs =
h ++ sieve [y | y <- t, rem y p /= 0] ps
(the code is in Haskell by I think it's readable enough. x:xs
stands for a list with x
the head element and xs
the rest of list).