Q:

Why 'IF' made this code 2x faster as the output is the same and 'IF' make an extra check?

I was studying this fast Sieve of Eratosthenes generator and just wondered why, with the 'If check' of every item index, the code run faster than without this check. Who would think that!? Is it a trick to use in general?
import time    

def first(n):
    s=time.time()
    full_range = list(range(n))
    zeroes = [0] * n
    '''multiply n until sqrt(n)'''
    for r in range(2, int(n**.5) + 1):
        if full_range[r]:  ##HERE##
            full_range[2 * r:n:r] = zeroes[2 * r:n:r]   
    return list(filter(None, full_range)), print (time.time()-s)

def second(n):
    s=time.time()
    full_range = list(range(n))
    zeroes = [0] * n
    '''multiply n until sqrt(n)'''
    for r in range(2, int(n**.5) + 1):
        #if full_range[r]:   ##HERE##
        full_range[2 * r:n:r] = zeroes[2 * r:n:r]
    return list(filter(None, full_range)), print (time.time()-s)

print(first(100000) == second(100000))

First  0.3749978542327881 seconds
Second 0.9687492847442627 seconds
True

A:

In words, the process you're doing says "zero out the flags for all multiples of this number."  The if statement says "do the next step only if the current number is a prime."
The second sieve has to zero out all multiples of 2, then 3, then 4, ... for every number in the sieve.  The first one zeroes out only multiples of primes: 2, then 3, then ...
When it reaches 4, it sees that 4 is already zeroed.  Any multiple of 4 is also a multiple of 2, so they're already zeroed.  Marking them again is wasted work.
Now we do 5, skip 6, do 7, skip 8, 9, and 10, ...
When we do that work only for prime divisors, we save a lot of work.

