From Performance Tips:
When working with parameterized types, including arrays, it is best to
avoid parameterizing with abstract types where possible.
Suppose you're writing a function that requires a Vector, and with each use of the function, the Vector can contain different types.
function selection_sort!(unsorted_vect::AbstractVector)
# Code to sort.
end
names = String["Sarah","Kathy","Amber"]
unsorted_fibonacci = Int32[8, 1, 34, 21, 3, 5, 0, 13, 2, 1]
selection_sort!(names)
selection_sort!(unsorted_fibonacci)
When using selection_sort!() the first time, the Vector contains String, the second time Int32.
The function knows it's getting an AbstractVector, but it doesn't know the type of elements.
Will performance be inefficient? Would it be the same as writing selection_sort!(unsorted_vect::AbstractVector{Real})?
The same section also says:
If you cannot avoid containers with abstract value types, it is
sometimes better to parametrize with Any to avoid runtime type
checking. E.g. IdDict{Any, Any} performs better than IdDict{Type,
Vector}
Would it be better to write the function this way?
function selection_sort!(unsorted_vect::AbstractVector{Any})
If so, why do the sorting algorithms just use AbstractVector?
function sort!(v::AbstractVector, # left out other parameters)
What you need is:
function selection_sort!(unsorted_vect::AbstractVector{T}) where T
# Code to sort.
end
In this way you have an abstract type for the container (and hence any container will get accepted).
Yet those containers con be non-abstract - because they can have a concrete type for their elements - T. As noted by Bogumil - if in the body code you do not need the type T you could do function selection_sort!(unsorted_vect::AbstractVector) instead.
However, this is just a limit for argument types. Julia would be as happy as to have function selection_sort!(unsorted_vect) and the code would be as efficient.
What will really matter are the types of arguments. Consider the following 3 variables:
a = Vector{Float64}(rand(10))
b = Vector{Union{Float64,Int}}(rand(10))
c = Vector{Any}(rand(10))
a is a typed container, b is a small-union type container and c is an abstract container. Let us have a look on what is happening with the performance:
julia> #btime minimum($a);
18.530 ns (0 allocations: 0 bytes)
julia> #btime minimum($b);
28.600 ns (0 allocations: 0 bytes)
julia> #btime minimum($c);
241.071 ns (9 allocations: 144 bytes)
The variable c requires value unboxing (due to unknown element type) and hence the performance is degraded by an order of magnitude.
In conclusion, it is the parameter type what actually matters.
Related
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 1 year ago.
Improve this question
I am learning the Julia language and would like to know which implementation is likely to have better performance.
In answer to question How to check if a string is numeric Julia last answer is
isintstring(str) = all(isdigit(c) for c in str)
This code works well, but it could be rewritten
isintstring = str -> mapreduce(isnumeric, &, collect(str))
Is the rewrite better or worse? or just different? The Julia Style Guide does not seem to provide guidance.
Edit: As originally expressed, this question was blocked for seeking opinion not facts. I have rephrased it in gratitude for the three outstanding and helpful answers the original question received.
If you are using collect there is probably something wrong with your code, especially if it's a reduction. So your second method needlessly allocates, and, furthermore, it does not bail out early, so it will keep going to the end of the string, even if the first character fails the test.
If you benchmark the performance, you will also find that mapreduce(isnumeric, &, collect(str)) is an order of magnitude slower, and that is without considering early bailout.
In general: Don't use collect(!), and bail out early if you can.
The idiomatic solution in this case is
all(isdigit, str)
Edit: Here are some benchmarks:
jl> using BenchmarkTools, Random
jl> str1 = randstring('0':'9', 100)
"7588022864395669501639871395935665186290847293742941917566300220720077480508740079115268449616551087"
jl> str2 = randstring('a':'z', 100)
"xnmiemobkalwiiamiiynzxxosqoavwgqbnxhzaazouzbfgfbiodsmhxonwkeyhxmysyfojpdjtepbzqngmfarhqzasppdmvatjsz"
jl> #btime mapreduce(isnumeric, &, collect($str1))
702.797 ns (1 allocation: 496 bytes)
true
jl> #btime all(isdigit, $str1)
82.035 ns (0 allocations: 0 bytes)
true
jl> #btime mapreduce(isnumeric, &, collect($str2))
702.222 ns (1 allocation: 496 bytes) # processes the whole string
false
jl> #btime all(isdigit, $str2)
3.500 ns (0 allocations: 0 bytes) # bails out early
false
The rewrite is definitely worse. Slower, less elegant and more verbose.
Another edit: I only noticed now that you are using isnumeric with mapreduce, but isdigit with all. isnumeric is more general and much slower than isdigit so that also makes a big difference. If you use isdigit instead, and remove collect, the speed difference isn't so big for numeric strings, but it still does not bail out early for non-numeric strings, so the best solution is still clearly all(isdigit, str).
Part of your question is about named vs anonymous functions. In the first case, you created a function via its first method and assigned it to an implicitly const variable isintstring. The function object itself also gets the name isintstring, as you can see in its type. You can't reassign the variable isintstring:
julia> isintstring(str) = all(isdigit(c) for c in str)
isintstring (generic function with 1 method)
julia> typeof(isintstring)
typeof(isintstring)
julia> isintstring = str -> mapreduce(isnumeric, &, collect(str))
ERROR: invalid redefinition of constant isintstring
julia> isintstring = 1
ERROR: invalid redefinition of constant isintstring
Now let's restart the REPL and switch the order to start at the second case. The second case creates an anonymous function, then assigns it to a variable isintstring. The anonymous function gets a generated name that can't be a variable. You can reassign isintstring as long as you're not trying to declare it const, which includes method definitions.
julia> isintstring = str -> mapreduce(isnumeric, &, collect(str))
#5 (generic function with 1 method)
julia> typeof(isintstring)
var"#5#6"
julia> isintstring(str) = all(isdigit(c) for c in str)
ERROR: cannot define function isintstring; it already has a value
julia> isintstring = 1
1
It's far more readable to add methods to a named function, all you have to do is define another method using the const name, like isintstring(int, str) = blahblah().
It's actually possible to add methods to an anonymous function, but you have to do something like this: (::typeof(isintstring))(int, str) = blahblah(). The variable isintstring may not always exist, and the anonymous function can have other references such as func_array[3], in which case you'll have to write (::typeof(func_array[3]))(int, str) = blahblah(). I think you'll agree that a const name is far clearer.
Anonymous functions tend to be written as arguments in method calls like filter(x -> x%3==0, A) where the anonymous function only needs 1 method. In such a case, creating a const-named function would only bloat the function namespace and force a reader to jump around the code. In fact, do-blocks exist to allow people to write a multiple-line anonymous function as a first argument without bloating the method call.
Just like Gandhi said "My life is my message", Julia says "My code is my guide". Julia makes it very easy to inspect and explore standard and external library code, with #less, #edit, methods, etc. Guides for semantic style are rather hard to pin down (as opposed to those for syntactic style), and Python is rather the exception than the rule when it comes to the amount of documentation and emphasis surrounding this. However, reading through existing widely used code is a good way to get a feel for what the common style is.
Also, the Julialang Discourse is a more useful resource than search engines seem to give it credit for.
Now, for the question in your title, "using functional idioms" is a broad and vague descriptor - Julian style doesn't generally place high emphasis on avoiding mutations (except for performance reasons), for eg., and side effects aren't something rare and smelly. Higher order functions are pretty common, though explicit map/reduce are only one part of the arsenal of tools that includes broadcasting, generators, comprehensions, functions that implicitly do the mapping for you (sum(f, A::AbstractArray; dims): "Sum the results of calling function f on each element of an array over the given dimensions"), etc.
There's also another factor to consider: performance trumps (almost) anything else. As the other answers have hinted at, which style you go for can be a matter of optimizing for performance. Code that starts out reading functional can have parts of it start mutating its inputs, parts of it become for loops, etc., as and when necessary for performance. So it's not uncommon to see a mixture of these different style in the same package or even the same file.
they are the same. and if you just look at it...clearly the first one is cleaner even shorter
This is a constructor for arrays:
Array{T}(undef, dims)
I am new to Julia, and don't have a good background in programming. In this syntax, why is undef used for creating the array?
What is a constructor in Julia, in what situation do we use a constructor?
If we don't type constructor, Julia will automatically create a constructor. Then, Why we use constructor?
First, you want to understand what is a constructor:
For that, I suggest you the Julia doc: Constructors in Julia
Now that you have the theory, let's break apart this expression:
a = Array{Int}(undef, (2, 2))
What this expression is saying is "I want a to be an Array of dimension (2, 2)". So Julia will ask for some memory space. When I write it on the Julia REPL:
julia> a = Array{Int}(undef, (2, 2))
2×2 Array{Int64,2}:
0 0
0 0
Now Array{T}(undef, dims) is the generalization of that. "Construct an array of a specific type T with a specific number of dimensions dims"
So far, I didn't explain what is undef. undef is a shortcut for UndefInitializer(). In this example, we wanted an uninitialized array. What does it mean? For that, you have to understand that variables are not created ex nihilo on your terminal. They are occupying a specific place in the memory of your computer. And sometimes, the same memory space was occupied by another variable. So the space my new variable can take might not be empty:
julia> a = Array{Float64}(undef, (2, 2))
2×2 Array{Float64,2}:
6.94339e-310 6.94339e-310
6.94339e-310 0.0
Here, I never asked for these values to be there. I could erase it to work with a clean variable. But that would mean to erase the value for each cell, and it's much more expensive for the computer to replace each value rather than declaring "here is the new variable".
So basically, undef and uninitialized arrays are used for performance purposes. If you want an array well initialized, you can use fill.
I am looking for different ways of writing for loops in Julia! I know this is a basic question but I'm wondering what some of the different options are and if there are advantages/disadvantages with respect to performance.
For loop
Pro: fully flexible has break and continue
Con: no return, must specify iterator at start
While loop
Pro: fully flexible has break and continue
Con: no return, if iterator must be handled manually
Label+goto
Please don't use this for loops
Generator comprehension/Vector comprehension
Pro: Has return value, continue is expressed with filter clause, comes in lazy (generator) and eager forms (vector), can create multidimensional return vale
Con: really ugly for anything long, no break
Broadcast
Pro: express transform of multiple input susictly, has return value with output structure matching what it should be. Can be expressed with just a dot and supports loop fusion.
Con: no break no contine. Writing body means writing a function. Wrapping things you want to broadcast as scalar in Ref is a bit ugly
Map/pmap/asyncmap
Written in do-block form
Pro: can easily change to run distributed or asynchronously, had a return value
Con: no break, no continue
foreach function
It is a lot like map but no return value. So save on allocating that.
Other than that same pros and cons
This is straight from the Julia docs:
The for loop makes common repeated evaluation idioms easier to write. Since counting up and down like the above while loop does is so common, it can be expressed more concisely with a for loop:
julia> for i = 1:5
println(i)
end
1
2
3
4
5
Here the 1:5 is a range object, representing the sequence of numbers 1, 2, 3, 4, 5. The for loop iterates through these values, assigning each one in turn to the variable i. One rather important distinction between the previous while loop form and the for loop form is the scope during which the variable is visible. If the variable i has not been introduced in another scope, in the for loop form, it is visible only inside of the for loop, and not outside/afterward. You'll either need a new interactive session instance or a different variable name to test this:
julia> for j = 1:5
println(j)
end
1
2
3
4
5
julia> j
ERROR: UndefVarError: j not defined
See Scope of Variables for a detailed explanation of the variable scope and how it works in Julia.
In general, the for loop construct can iterate over any container. In these cases, the alternative (but fully equivalent) keyword in or ∈ is typically used instead of =, since it makes the code read more clearly:
julia> for i in [1,4,0]
println(i)
end
1
4
0
julia> for s ∈ ["foo","bar","baz"]
println(s)
end
foo
bar
baz
Various types of iterable containers will be introduced and discussed in later sections of the manual (see, e.g., Multi-dimensional Arrays).
I'm wondering about immutable types and performances in Julia.
In which case does making a composite type immutable improve perfomances? The documentation says
They are more efficient in some cases. Types like the Complex example
above can be packed efficiently into arrays, and in some cases the
compiler is able to avoid allocating immutable objects entirely.
I don't really understand the second part.
Are there cases where making a composite type immutable reduce performance (beyond the case where a field needs to be changed by reference)? I thought one example could be when an object of an immutable type is used repeatedly as an argument, since
An object with an immutable type is passed around (both in assignment statements and in function calls) by copying, whereas a mutable type is passed around by reference.
However, I can't find any difference in a simple example:
abstract MyType
type MyType1 <: MyType
v::Vector{Int}
end
immutable MyType2 <: MyType
v::Vector{Int}
end
g(x::MyType) = sum(x.v)
function f(x::MyType)
a = zero(Int)
for i in 1:10_000
a += g(x)
end
return a
end
x = fill(one(Int), 10_000)
x1 = MyType1(x)
#time f(x1)
# elapsed time: 0.030698826 seconds (96 bytes allocated)
x2 = MyType2(x)
#time f(x2)
# elapsed time: 0.031835494 seconds (96 bytes allocated)
So why isn't f slower with an immutable type? Are there cases where using immutable types makes a code slower?
Immutable types are especially fast when they are small and consist entirely of immediate data, with no references (pointers) to heap-allocated objects. For example, an immutable type that consists of two Ints can potentially be stored in registers and never exist in memory at all.
Knowing that a value won't change also helps us optimize code. For example you access x.v inside a loop, and since x.v will always refer to the same vector we can hoist the load for it outside the loop instead of re-loading on every iteration. However whether you get any benefit from that depends on whether that load was taking a significant fraction of the time in the loop.
It is rare in practice for immutables to slow down code, but there are two cases where it might happen. First, if you have a large immutable type (say 100 Ints) and do something like sorting an array of them where you need to move them around a lot, the extra copying might be slower than pointing to objects with references. Second, immutable objects are usually not allocated on the heap initially. If you need to store a heap reference to one (e.g. in an Any array), we need to move the object to the heap. From there the compiler is often not smart enough to re-use the heap-allocated version of the object, and so might copy it repeatedly. In such a case it would have been faster to just heap-allocate a single mutable object up front.
This test includes a special cases, so is not extendable and could not reject better performance of immutable types.
check following test and look at different allocation times,when create a vector of immutables compare to a vector of mutables
abstract MyType
type MyType1 <: MyType
i::Int
b::Bool
f::Float64
end
immutable MyType2 <: MyType
i::Int
b::Bool
f::Float64
end
#time x=[MyType2(i,1,1) for i=1:100_000];
# => 0.001396 seconds (2 allocations: 1.526 MB)
#time x=[MyType1(i,1,1) for i=1:100_000];
# => 0.003683 seconds (100.00 k allocations: 3.433 MB)
I am using this piece of code and a stackoverflow will be triggered, if I use Extlib's Hashtbl the error does not occur. Any hints to use specialized Hashtbl without stackoverflow?
module ColorIdxHash = Hashtbl.Make(
struct
type t = Img_types.rgb_t
let equal = (==)
let hash = Hashtbl.hash
end
)
(* .. *)
let (ctable: int ColorIdxHash.t) = ColorIdxHash.create 256 in
for x = 0 to width -1 do
for y = 0 to height -1 do
let c = Img.get img x y in
let rgb = Color.rgb_of_color c in
if not (ColorIdxHash.mem ctable rgb) then ColorIdxHash.add ctable rgb (ColorIdxHash.length ctable)
done
done;
(* .. *)
The backtrace points to hashtbl.ml:
Fatal error: exception Stack_overflow Raised at file "hashtbl.ml",
line 54, characters 16-40 Called from file "img/write_bmp.ml", line
150, characters 52-108 ...
Any hints?
Well, you're using physical equality (==) to compare the colors in your hash table. If the colors are structured values (I can't tell from this code), none of them will be physically equal to each other. If all the colors are distinct objects, they will all go into the table, which could really be quite a large number of objects. On the other hand, the hash function is going to be based on the actual color R,G,B values, so there may well be a large number of duplicates. This will mean that your hash buckets will have very long chains. Perhaps some internal function isn't tail recursive, and so is overflowing the stack.
Normally the length of the longest chain will be 2 or 3, so it wouldn't be surprising that this error doesn't come up often.
Looking at my copy of hashtbl.ml (OCaml 3.12.1), I don't see anything non-tail-recursive on line 54. So my guess might be wrong. On line 54 a new internal array is allocated for the hash table. So another idea is just that your hashtable is just getting too big (perhaps due to the unwanted duplicates).
One thing to try is to use structural equality (=) and see if the problem goes away.
One reason you may have non-termination or stack overflows is if your type contains cyclic values. (==) will terminates on cyclic values (while (=) may not), but Hash.hash is probably not cycle-safe. So if you manipulate cyclic values of type Img_types.rgb_t, you have to devise your one cycle-safe hash function -- typically, calling Hash.hash on only one of the non-cyclic subfields/subcomponents of your values.
I've already been bitten by precisely this issue in the past. Not a fun bug to track down.