-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cartesian Product iteration #1917
Comments
In fact, I've been thinking of adding this to the language as follows:
where |
I like both the idea and the syntax. People are inevitably going to ask for support for X being other kinds of collections. Is that feasible or can we not infer their size well enough? |
It is feasible, since after all we can't infer the size of every tuple anyway. You just want to avoid spurious use of non-tuples, like |
I like it a lot. Maybe an interesting use case would be to consider what it would look like to compute the discrete Laplacian of a multidimensional array this way. Presumably you'd have to do iteration over the components of |
+1 |
Would you be able to do math on the index tuples? For example, suppose I wanted to implement
|
It would be something like |
We could define things like |
Stefan, you're right of course. Another thing to think about is that there may be powerful performance arguments to be able to reduce most array accesses to linear indexing. I seem to remember that 4d indexing was something like 20x slower than linear, when the accesses were in a pattern that allowed you to precalculate the outer dimensions. The imfilter example would probably be best implemented in terms of a list of coefficients and stride offsets. But for boundary conditions you also have to keep track of the actual coordinates, and feed that into your linear indexing, so you do need cartesian iteration in parallel with linear indexing. If you imagine implementing reflecting, periodic, and NA boundary conditions (the latter meaning "just pretend any undefined elements don't exist, normalizing by the ones you have"), it seems like the |
For the record there's an imfilter implementation for arbitrary dimensions at You definitely want to do the inner loop over the kernel as a linear loop and in many cases handle boundary conditions with pre-padding. |
A bit off-topic, but @GunnarFarneback, your imfilter is certainly better than the one in Images.jl. I'd welcome a pull request, if you're willing to share. While linear indexing when you can do it will always be faster, I suspect my 20x number needs revision in light of e550406. Nice work, Jeff. |
Sharing is no problem, deciding how to fit it in is more of one. But maybe it's time to get that implementation replaced. Going back to topic, the bilateral filter may be a good concrete example of how to stress a Cartesian product language feature. In 2D a somewhat naive implementation looks like function bilateral(in::Matrix{Float64}, kernel_size::Int,
sigma_s::Float64, sigma_i::Float64)
k_s = -0.5 / sigma_s^2
k_i = -0.5 / sigma_i^2
kernel_radius = fld(kernel_size - 1, 2)
out = similar(in)
height = size(in, 1)
width = size(in, 2)
for x = 1:width
for y = 1:height
center_value = in[y, x]
sum_w = 0.0
sum = 0.0
for dx = -kernel_radius:kernel_radius
xi = x + dx
if xi < 1 || xi > width
continue
end
for dy = -kernel_radius:kernel_radius
yi = y + dy
if yi < 1 || yi > height
continue
end
value = in[yi, xi]
diff = value - center_value
wi = exp(k_s * (dx^2 + dy^2) + k_i * diff^2)
sum_w += wi
sum += wi * value
end
end
out[y, x] = sum / sum_w
end
end
return out
end It would be lovely if this could be written compactly for arbitrary dimensionality. |
Just wanted to give another example here, showing how useful/necessary this will be for switching over to returning views. It would be nice to write a sum function something like this:
I suspect we won't be happy with the performance until this type of syntax generates code something like this:
Here I've not required that |
Since some discussions have started focusing on post-0.2, let me chime in with an update on this issue (which I think is an important 0.3 milestone). First, let me correct something I said immediately above: in recent julias, cartesian indexing of multidimensional arrays is completely viable. (I suspect it's been this way ever since Jeff's "hoist array size" fix for #3712.) So a lot of the "convert multidimensional indices into a linear index" gymnastics we've done in the past simply aren't necessary, in my view. That simplifies many things, and in particular makes a tuple-of-indexes (as Jeff proposes above) the obvious foundation for Cartesian iteration. Second, my One of the main surprises in developing and using
using the following code (here
Those anonymous function-expressions get "inlined" at parsing time, so this is literally translated into the efficient version above. Such tricks allow you to implement reductions, broadcasting, nearest-neighbor searches, etc almost trivially. (In particular, every example that's been mentioned in this issue so far is quite easy to implement.) Moreover, most of your code naturally ends up being cache-efficient, which as @lindahua has pointed out is the most performant way to perform reductions. Finally, the resulting algorithms are performant for both Without the anonymous function-expression inlining, I found that I was much more limited in terms of what I could do---in terms of my own algorithms, developing this very simple functionality was a big breakthrough. So I'd argue that a facility like this will be an important consideration for whatever ends up in base. If you want to play with |
I should also add that I'm not advertising for users of |
Mentioned on the mailing list, but posting here so it doesn't get lost between now and when people have time to start thinking about this again: a more flexible syntax is here. It allows the iterator ranges to be specified and introduces a new keyword |
If we implement APL indexing (#5949), we'll need either this or #5395. That is, unless we restrict the indexes to something for which linear indexing is efficient. I think it's simply unfeasible otherwise. A brief update on where we stand on this issue, relative to this gist (I'm excising JIT warmups, etc.):
Pretty impressive, although I suspect some would be loath to give up a factor of 2. But the more relevant case reveals we're not there yet:
More than 500-fold slower (note it's doing 30-fold less work). |
This is a follow-up to the discussion on the julia-users mailing list here.
There seemed to be good progress but I didn't see any code committed.
The text was updated successfully, but these errors were encountered: