The advantage of using a length-1-vector instead of an int/float variable (if any)?

t = Vector{Float}(1) # t is a Float-vector of length 1, i.e., it's a one-element vector 
initMessage(t, msg) = doPrint && (t[1] = time(); print(msg)) # a simple function to check the progress of #running the code. Logic: if doPrint is true, assign the current CPU time to t[1] and print out the message
initTime(t) = doPrint && println(": ", round(time() - t[1], 2), " seconds") 
# function definition. Logic: if doPrint is true, print out the time since the start so far in seconds, rounding up 2 decimials

The above code is easy to understand. I was wondering, are there any specific reasons that a length-1 float vector t is used instead of an float variable instead? Would it be quicker? If so, why?

It modifies the array in-place, i.e. after calling initMessage the t passed into it will be modified. This couldn’t be done with a float as that is immutable. Also, note the convention would be to name the function with a ! at the end (and also not camel case), i.e. init_message!.

2 Likes

I tried some code as the following and I think the function parameter handling is not the same as variable assignments. That’s how I understand it.

julia> t=-1
-1

julia> initMessage!(t,msg)=(t=time(); print("laugh"))
initMessage! (generic function with 1 method)

julia> initMessage!(t,"smile")
laugh
julia> initMessage!(t,"smile")
laugh
julia> initMessage!(t,"smile")
laugh
julia> initMessage!(t,"smile")
laugh
julia> initMessage!(t,"smile")
laugh
julia> initMessage!(t,"smile")
laugh
julia> initMessage!(t,"smile")
laugh
julia> initMessage!(t,"smile")
laugh
julia> t
-1
julia> t=-1
-1
julia> t=-2
-2

Note that this is not particularly idiomatic Julia code. It would be much more natural to simply return the time, to take doprint as an argument, and to use all lowercase (CamelCaps are used in Julia for types):

initmessage(msg, doprint=true) = let t = time(); doprint && print(msg); t; end
5 Likes

This is a good read: Values vs. Bindings: The Map is Not the Territory · John Myles White

3 Likes

The idiomatic way would be to use Ref instead of one-element arrays. Use a const Ref for variables that hold type-stable mutable global state, like doPrint.

julia> begin
          timer = Ref(1.0) 
          const doPrint = Ref(true)
          initMessage(t, msg) = doPrint[] && (t[] = time(); print(msg))
          initTime(t) = doPrint[] && println(": ", round(time() - t[], 2), " seconds") 
          initMessage(timer, "m1")
          doPrint[] = false
          initMessage(timer, "m2")
       end;
m1

Arrays are slower to access, have larger memory footprint and larger mental overhead than Ref (every reader needs to go through the entire codebase in order to check whether t really has exactly one element under all circumstances).

3 Likes

@foobar_lv2, not sure what exactly is this Ref(1.0).

For most intents and purposes, it is equivalent to

mutable struct MyRef{T}
    contents::T
end
Base.getindex(x::MyRef) = x.contents
Base.setindex!(x::MyRef{T}, v) where T = (x.contents = convert(T, v); v)

It is a container that has exactly one element. Because there always is one element, you use some_ref[] instead of some_ref[1] for access.

One of the large advantages is that Array bookkeeping (allocation and free and resize) can never be elided or inlined by the compiler; these are all foreign function calls into a C library, and not native julia (array access is special cased in the JIT and hence fast, though).

Ref is pure julia, instead of julia wrapped around C.

2 Likes

Based on your sample code, it looks like you want to time something, then print out the elapsed time with a caption, is that correct? If so, I would advice a slightly different approach. First, performance is not something I would worry about at all. That print statement is likely to be thousands of times slower than accessing the time variable. Rather, the problems I see with the current approach are:

  • Method directly modifies external data.
  • Print statements separated, what if there’s other IO output in between, or you have multiple timers and happen to mix up captions and timers?
  • Variable name t is so short that comments are needed to explain what it is.
  • The single-element-vector idiom to achieve mutability, although fairly common in many languages, is often considered a hack, typically needs comments explaining what’s going on, and even then always attracts the wrong kind of attention during code reviews.

What I would suggest is a small immutable timer object holding the caption and start time:

const doPrint = true    # assuming this is a global flag in your code

struct MyTimer{T<:AbstractString}
    message::T
    start_time::Float64
end

MyTimer(message) = MyTimer(message, time())

print_timer(timer) = doPrint && @printf("%s: %.2f seconds", timer.message, time() - timer.start_time)

Testing it:

julia> timer = MyTimer("doing stuff");

julia> # do stuff

julia> print_timer(timer)
doing stuff: 1.94 seconds

It is fast, and should be fast enough under almost all circumstances, but note that it does incur an additional dereference to get from the array object to the raw data. Using a tuple can be faster.

3 Likes