#Best way to define capturing lambdas in a performance critical context

26 messages · Page 1 of 1 (latest)

zenith rain
#

Let's say for example that I have

//...
String foo = "foo";
Boolean isFoo = (String input) -> input == foo;
//...

This lambda references a non-static outer-scope variable, therefore leading to JIT compilation never caching the lambda resulting in slower execution and more churn.

My current solution has been to do:

Boolean isFoo = ((String foo)-> (String input) -> input == foo).apply(foo);

This works for reference types and will snapshot value types. But if the variable is ever reassigned - this will fall short.

Is there a better way to do this?

royal tulipBOT
#

This post has been reserved for your question.

Hey @zenith rain! Please use /close or the Close Post button above when your problem is solved. Please remember to follow the help guidelines. This post will be automatically closed after 300 minutes of inactivity.

TIP: Narrow down your issue to simple and precise questions to maximize the chance that others will reply in here.

zenith rain
#

I looked into using annotations for this, but quickly realized that wouldn't solve it and I would have to use aspect-oriented-programming. I would rather not go down that path if at all possible.

zealous ivy
#

i... dont understand your problem

#

you mentioned jit compilation... but i dont see how that is relevant here?

#

jit will always happen, and i dont think your program is performance critical and specific enough to the point where jit execution is a downside

#

and that "if the variable is reassigned"... which variable? where? fall short in what way? i dont understand the issue

zenith rain
zealous ivy
#

then the jit will happily compile that to be faster in the long run

#

jit only takes like 5 milliseconds at most and after that, the code is permanently(*) faster than the java code could ever be

#

if you want to disable the jit, -Xint for interpreted mode only

#

beware, this will slow down your program to a crawl

zealous ivy
#

people like to use one-element arrays for this, or AtomicReference/Atomic<Xyz>

zenith rain
#
InfoQ

Java 8 was released in March 2014 and introduced lambda expressions as its flagship feature. This article sheds light on how Java 8 lambda expressions and method references are implemented under the hood, and looks at the generated bytecode and performance implications.

zealous ivy
#

yeah..?

zenith rain
#

So this is the capturing approach they highlighted. Is there a simpler approach?

zealous ivy
#

the best approach if you want to have one lambda made somewhere up that is still updating with the values is:

  1. moving the variable to a field
  2. moving the variable into an one-element array
  3. moving the variable into an atomic ref
  4. not varying the variable
zenith rain
#

Best way to define capturing lambdas in a performance critical context

zenith rain
#

This' what I landed on.

class UtilityClass {
    @FunctionalInterface
    public interface QuadriFunction<A,B,C,D,R> {

        R apply(A a, B b, C c, D d);

        default <V> QuadriFunction<A, B, C, D, V> andThen(
                                    Function<? super R, ? extends V> after) {
            Objects.requireNonNull(after);
            return (A a, B b, C c, D d) -> after.apply(apply(a, b, c, d));
        }
    }

    // uncaptures lambdas with 2 captured variables, producing a bifunctor
    public static <C1,C2,I1,I2,OUT> BiFunction<I1,I2,OUT> biCapture2(QuadriFunction<C1,C2,I1,I2,OUT> lambda, C1 capture1, C2 capture2) {
        // This function uses captured variables, but it is small.
        // This makes large lambdas that would be captured performant.
        // O(n) where n is the arbitrary complexity of the captured lambda
        // becomes: O(1) where 1 is this constant, small captured lambda.
        return (I1 internal1, I2 internal2) -> lambda.apply(capture1, capture2, internal1, internal2);
    }
}
#

This solution is slightly more specific to my real use-case, where there are 2 standard parameters, and 2 captured variables. the isFoo definition from before would look like (if a version of this was made with BiFunction and Function instead):

String foo = "foo";
Function<String,Boolean> isFoo = UtilityClass.capture1((String _foo, String input) -> input == _foo, foo);
zenith rain
#

I'll let this time out if anyone has concerns with this approach or knows of something better. But I think this solution is nice.

jovial mauve
#

Just a side note: you sure you want to compare the string with ==? That’s normally a smell and for your case, isn’t String::equals the thing you search for? The „function from string to Boolean“

zenith rain
#

The insides of that lambda were just thrown together to provide a minimal viable example. Yes you're correct as far as best practice, but that's not the matter primarily at hand.