How to write a fast loop through structure fields

#1

Hi all,
Novice question. I am looping through structure fields and would like to know how to keep this fast. Is there a way to work on these that is faster?

#Function that works on values
function test(x,y,z)
	x=x.+1
	y=y.+1
	z=z.+1
	return x,y,z
end

#Function that loops thorugh structure entries and does the same
function test2(Structure)
	FieldsInStruct=fieldnames(typeof(Structure));
	for i=1:length(FieldsInStruct)
		#Check field i
		Value=getfield(Structure, FieldsInStruct[i])
		Value=Value.+1;
		setfield!(Structure,FieldsInStruct[i],Value)
	end
end

x=zeros(10);
y=ones(10);
z=ones(10).+1;

mutable struct points; x;y;z; end
PntLst=points(x,y,z)

#Run two times...
@time test(x,y,z)
 #0.000005 seconds (7 allocations: 640 bytes)
@time test2(PntLst)
 #0.000036 seconds (14 allocations: 816 bytes)

Is there a way for me to get this second function faster, still without explictly typing out the structures field names?

#2

mutable struct points; x;y;z; end

Since you care about speed, you may want to add types here, e.g.

mutable struct points{T}; x::T; y::T; z::T; end

As for your question, you’ll get something much more readable and speedy using metaprogramming. For example:

@generated function test4(p::P) where P
    assignments = [
        :( p.$name += 1 ) for name in fieldnames(P)
    ]
    quote $(assignments...) end
end
#3

If you find yourself wanting to loop through struct fields, it’s quite likely you are using the wrong data structure.

For example, if you want an array of (x,y,z) coordinates, then rather than a struct with (x,y,z) fields I would just use an Array of SVector from StaticArrays.jl:

using StaticArrays
pointlist = fill(SVector(0.0, 1.0, 2.0), 10)

By using the StaticArrays package, you get extremely fast and convenient geometric operations on the points. e.g. to shift all of the coordinates by (1,1,1) you would just do:

pointlist .+= Ref(SVector(1,1,1))

or even just

pointlist .+= 1
5 Likes
#4

Great, will try both. Thanks.

#5

Occasionally yes. But it would still be interesting to have a programming technique where one could write code that is equivalent to manually unrolled versions without using a generated function. Eg, for an MWE, consider

struct HetPoint{T,S}
    x::T
    y::S
end

plus_manual(a::HetPoint, b::HetPoint) = HetPoint(a.x + b.x, a.y + b.y)

plus_loop(a::HetPoint, b::HetPoint) =
    HetPoint(ntuple(i -> getfield(a, i) + getfield(b, i), Val(fieldcount(typeof(a))))...)

where

julia> using BenchmarkTools

julia> a = HetPoint(1.0, 2)
HetPoint{Float64,Int64}(1.0, 2)

julia> b = HetPoint(1f0, Int8(2))
HetPoint{Float32,Int8}(1.0f0, 2)

julia> @btime plus_manual($a, $b)
  0.032 ns (0 allocations: 0 bytes)
HetPoint{Float64,Int64}(2.0, 4)

julia> @btime plus_loop($a, $b)
  2.853 ns (0 allocations: 0 bytes)
HetPoint{Float64,Int64}(2.0, 4)

I can occasionally optimize these things, but have not found a general way so far.