用于调试目的的命名(toString)Lambda 表达式

2022-01-16 00:00:00 debugging lambda jvm tostring java

有时命名 lambdas 很有用.特别是当您将它们作为参数传递时.

Sometimes it is usefull to name lambdas. Especially when you pass them around as parameter.

一个非常简单的例子是

public class Main {
    public static void main(String[] args) {
        Predicate<String> p = nameIt("isNotEmpty", (s) ->  !s.trim().isEmpty());
        maybePrint("Hello", p);
        maybePrint("    ", p);
    }

    static <T> void maybePrint(T s, Predicate<T> pred) {
        if (pred.test(s)) {
            System.out.println(s.toString());
        } else {
            System.err.println(pred + " says no to "" + s + """);
        }
    }
}

如果 jvm 有一些功能来命名 lambdas 而不会失去幕后出色的性能优化,那就太好了.

It would be nice to have some functionality by the jvm to name lambdas without loosing the great performance optimizations behind the scenes.

这样的想法对我来说很好:

Somethink like this would be fine for me:

Predicate<String> p = nameIt("isNotEmpty", (s) -> !s.trim().isEmpty());

推荐答案

这是我的解决方案(灵感来自 https 上的 andersschuller 的解决方案://stackoverflow.com/a/23705160/1325574) 的问题.可能有一些极端情况(类加载),这个实现不起作用,但对于最简单的情况,它可以工作.

This is my solution(inspired from the solution of andersschuller at https://stackoverflow.com/a/23705160/1325574) for the problem. There maybe some corner cases(Classloading) where this implementation does not work, but for the most simple cases it works.

我用我有限的 jmh 知识为此创建了一个小型性能测试:https://gist.github.com/picpromusic/4b19c718bec5a652731a65c7720ac5f8

I have created a small performance test of this with my limited jmh knowledge: https://gist.github.com/picpromusic/4b19c718bec5a652731a65c7720ac5f8

命名"结果是针对@stuartmarks 用于调试目的的命名(toString)Lambda 表达式

The "Named"-results are measured for the implementation of the answer of @stuartmarks Naming(toString) Lambda-Expressions for Debugging purpose

# Run complete. Total time: 00:40:31

Benchmark                        Mode  Cnt          Score         Error  Units
MyBenchmark.testNamedPredicate  thrpt  200   45938970,625 ±  615390,483  ops/s
MyBenchmark.testPredicate       thrpt  200   23062083,641 ±  154933,675  ops/s
MyBenchmark.testPredicateReal   thrpt  200   48308347,165 ±  395810,356  ops/s
MyBenchmark.testToString        thrpt  200  138366708,182 ± 1177786,195  ops/s
MyBenchmark.testToStringNamed   thrpt  200  252872229,907 ± 8044289,516  ops/s
MyBenchmark.testToStringReal    thrpt  200    6670148,202 ±   40200,984  ops/s

如您所见,它比使用未命名的 lambda 慢大约 2 倍.所以在设置 -DnamedLambdasEnabled=true 时要小心.对我来说有趣的是,在 Real-lambda 上调用 toString 非常昂贵.也许有人可以解释一下,或者我的 jmh-test 很愚蠢.

As you can see it is roughly 2 times slower than using an unnamed lambda. So be carefull in setting -DnamedLambdasEnabled=true. Interessting for me is that it is surprisingly expensive to call toString on an Real-lambda. Maybe someone can explain that, or my jmh-test is stupid.

代码如下:

/**
 * Helper Class to give lambda a name ("toString") for debugging purpose
 *
 */
public class LambdaNamer {

    private static Method TO_STRING;

    static {
        try {
            TO_STRING = Object.class.getMethod("toString");
        } catch (NoSuchMethodException | SecurityException e) {
            throw new RuntimeException("There is something rotten in state of denmark!");
        }
    }

    /**
     * Overrides toString "Method" for a given lambda.
     * 
     * @param name toString result of lambda
     * @param obj the lambda to encapsulate
     * @return the named lambda
     */
    public static <T> T nameIt(String name, T obj) {
        if (Boolean.getBoolean("namedLambdasEnabled")) {
            Class<T> clazz = (Class<T>) obj.getClass();
            Class<?>[] interfaces = clazz.getInterfaces();

            return (T) Proxy.newProxyInstance(//
                    obj.getClass().getClassLoader(),//
                    interfaces, //
                    (Object proxy, Method method, Object[] args) -> {
                        if (TO_STRING.equals(method)) {
                            return name;
                        } else {
                            return method.invoke(obj, args);
                        }
                    });
        } else {
            return obj;
        }
    }
}

您还有其他解决方案吗?也许一些对性能没有影响的东西?

Do you have other solutions? Maybe something that does not have performance implications?

相关文章