How to use hyperblocks with conditional functions

In some cases instead of using map of a conditional function, e.g.
image
One can accomplish the same thing with hyperblocks
image

If the list is one million numbers then the hyperblock version is 22 times faster.

But note that the hyperblock version computes the false branch of the conditional so there will be many situations where it is slower.

Here is an illustrative project:

Jens and I spent a couple of days trying to work out a way to hyperize reporter-if that wouldn't have that problem, and was both intuitively meaningful and actually implementable, and we failed.

Your post has inspired me to think about it again, and I wonder if it could be done by using explicit functions and array input data instead of expressions in which the computational part and the data givens have to be teased out:


The algorithm would be something like this:

for each atomic value in the first list
   Remember its position in the input (in the form of a list that could be used as the first input to ITEM).
   Call the predicate on it.
   If the result is true, call the second function on the chosen item of the second list.
   Otherwise, call the third function on the chosen item of the third list.

The three lists would be required to have the same shape. (In some cases they'd be the same list.) The expectation is that those lists have already been computed, or are trivial to compute. The computationally expensive part of the work would be in the three functions. The predicate function would be called for every atom in the input, unavoidably. But only one of the other two functions would be called for each input atom (maybe called on different data).

What do you think, @jens? Would that work? Would users understand it? It certainly isn't as elegant as the existing reporter-if magically just working for arrays, but that's not possible.

That's nice!
Incidentally I've been thinking in a slightly different direction these past days and implemented something much easier and way faster, but, alas!, also hardly useful, as it turns out :slight_smile:
I've been playing with a very fast reporter-if version (because it hardly does anything, that's why it's fast, haha) that simply in the case of a list of Booleans substitutes every item in the "tests" list with the according value from the case-slots. So, in the case of passing in a list of Booleans this block ceases to be a control structure and immediately evaluates both cases just once. I guess you're not excited about that, but it's actually really fun to use.

What does your proposal return? The then and else results appended or interleaved appropriately?

When would you want to have 3 different lists?

While my scheme performs unneeded computations since it evaluates both branches it can be orders of magnitude faster in some circumstances. And if in the future hyperblocks takes advantage of a GPU even faster.

As my example illustrated if the then and else functions are simple enough and the list is very long it can be much much faster.

Your hyper reporter-if should be even faster than what I did.

Yes, a list the same shape as the first list input but with elements computed from the others.

The example Jens and I were considering was


in which factorial isn't primitive but is defined as if num=0 then 1 else ...
So this would be something like

The answer to your question is the base case, in which we apply the identity function to 1. But I suppose it could instead be done as

so I'm not sure I have a good example.

But the real problem with this proposal is that it's not very data-parallel. The recursive calls to factorial are made with a single atomic input, not a list of 10 numbers. It couldn't be the latter, because different input numbers give rise to different numbers of recursive calls. The whole point of IF is to do recursive calls only when necessary, not to try to compute !(-1) etc.

By the way, I think what you've done is wonderful! I'm not trying to argue against it.

P.S. Oh, wait, if we say that it takes just one input list, maybe we can data-parallelize it. There would be recursive calls like this:
! [1 .. 10]
! [0 .. 9]
! [0 0 .. 8]
! [0 0 0 .. 7]
etc. This would "compute" the constant 1 more often than necessary, but that's not a big deal. The big deal is that I no longer know how to tell when to stop. When all the results are constants?

How about

! [1 .. 10]
! [0 .. 9]
! [- 0 .. 8]
! [- - 0 .. 7]

where '-' is a token meaning no input since those positions didn't make a recursive call. Terminates when all are '-'. My point is that it is not as if in the base case it recurs with 0 so 0 shouldn't be in the input list. Also this way it doesn't need to call the function repeatedly with the base case.

Interesting. Let me play with that idea a bit.

Comparing performance of this scripts ...
hypertest script pic (1)
hypertest script pic (2)
...a single "map" with empty action is nearly 50 times slower than the hyper "+" operator.
Is this caused by the different "map" iterator or overhead of the ring evaluation?
How this will perform
hypertest script pic (3)
if "hyperDyadic" will be able to evaluate ringed input (with parameters substitution)?

here's what I found


according to this, hyperblocks are the fastest. I did make sure the timer value is retrieved after the functions are done.

I'm curious why you use timer as you do. Would not the following be simpler and as good?

image

I use the IGNORE block for that purpose, rather than (confusingly, I think) setting A twice.

I use something similar:

benchmark reporter script pic

Sure, that's fine for timing command blocks. It's if you're timing a reporter that the issue arises of what to do with the value that the reporter reports.

using run just ignores it :slight_smile:
the block input is a reporter ring, so it now also accepts commands

Ah, something else I didn't know that belongs in the manual... I'll never catch up.

Any important difference between using reset timer and current time in milliseconds?

well, not really, except that if something else is already using timer it doesn't interfere. Oh, and also timer is rounded to 1/10 secs, whereas milliseconds lets you measure smaller differences..

ok, here's the improved code

note: I have run it multiple times and I got different results, but item 1 is always the biggest, 2 is next, and 3 is usually in the single digits, but the others are always in the hundreds.

All this has gotten me thinking that I don't actually understand why the hyperblock version is faster than the compiled-map version. They both have to call the underlying scalar function the same number of times, right? Is it that in the hyperblock case the looping all happens in the one call, so it's the same amount of arithmetic but not the same pushing and popping of contexts?