Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it good practice to use counters?

Here are two alternative codes (coded in Julia) that do essentially the same thing.

counter = 0
for i = myArray
    counter = counter + 1
    Do1(i)
    Do2(counter)
end

and

for counter = 1:length(myArray)
    i = myArray[counter]
    Do1(i)
    Do2(j)
end

What is the good practice? Which code is faster? Which code consumes less memory? Which code is less error prone? Why?

like image 291
Remi.b Avatar asked Dec 14 '22 17:12

Remi.b


2 Answers

In julia, you can test this very easily:

function indexing(A)
    si = 0
    sA = zero(eltype(A))
    for i = 1:length(A)
        sA += A[i]
        si += i
    end
    si, sA
end

function counter(A)
    si = 0
    sA = zero(eltype(A))
    i = 0
    for a in A
        sA += a
        si += (i += 1)
    end
    si, sA
end

function enumrt(A)
    si = 0
    sA = zero(eltype(A))
    for (i, a) in enumerate(A)
        sA += a
        si += i
    end
    si, sA
end

A = rand(Float32, 10^8)
# Compile all the functions, including those needed to time things
indexing(A)
counter(A)
enumrt(A)
@time 1+1

# Test the timing
@time indexing(A)
@time counter(A)
@time enumrt(A)

Output:

elapsed time: 4.61e-6 seconds (80 bytes allocated)
elapsed time: 0.12948093 seconds (144 bytes allocated)
elapsed time: 0.191082557 seconds (144 bytes allocated)
elapsed time: 0.331076493 seconds (160 bytes allocated)

If you add @inbounds annotations before every loop, then you get this:

elapsed time: 4.632e-6 seconds (80 bytes allocated)
elapsed time: 0.12512546 seconds (144 bytes allocated)
elapsed time: 0.12340103 seconds (144 bytes allocated)
elapsed time: 0.323285599 seconds (160 bytes allocated)

So the difference between the first two is really just the effectiveness of automatic bounds-checking removal. Finally, if you really want to dive into the details, you can inspect the generated machine code using @code_native indexing(A), or use @code_llvm to inspect the LLVM IR (internal representation).

However, in some cases readability may be more important, and so the enumerate approach is one I use often (but not in truly performance-critical code).

like image 198
tholy Avatar answered Feb 01 '23 13:02

tholy


I don't actually know much about Julia, but the pattern is common enough that most (sane) languages have a built-in function to help you deal with such an indecision. In both Python and Julia, it's called enumerate:

Return an iterator that yields (i, x) where i is an index starting at 1, and x is the ith value from the given iterator. It’s useful when you need not only the values x over which you are iterating, but also the index i of the iterations.

For your example, it would look like:

for (j, i) in enumerate(myArray)
    Do1(i)
    Do2(j)
end
like image 24
Rufflewind Avatar answered Feb 01 '23 14:02

Rufflewind