Java
Lombok
Thread

lombok のインスタンス生成周りの検証にゃ

これは

  • lombok 使うと便利なので、実際のインスタンス生成周りのパフォーマンスを調べてみた
  • 結果としては、Setter感でした

※ Javaについては、7まで嫌いだったが8から普通な感じ

検査内容

jvm version 8,9 (2つ) x builder/コンストラクタ/setter(3つ)
x ThreadPool, ForkJoinPool, ParallelStream, yield (4つ) の計24パターン
について、fib計算をLongStream.range(0,30)を実行してインスタンス生成の時間計測

計測結果

8の方が処理速度早めで、Setterが最速で ThreadPool が早そう

jvm インスタンス作成 実行パターン 時間(mill sec) 3回計測 Avg
8 Builder ThreadPool 19,21,20 20.00
Parallel 54,20,21 31.67
ForkJoinPool 94,31,20 48.33
yield 288,92,103 161
Setter ThreadPool 24,21,23 22.67
Parallel 32,25,21 26.00
ForkJoinPool 25,24,24 24.33
yield 104,93,94 97.00
Constructor ThreadPool 34,38,24 32.00
Parallel 72,46,52 56.67
ForkJoinPool 38,31,21 30.00
yield 114,84,88 95.33
9 Builder ThreadPool 50,24,24 32.67
Parallel 80,29,29 46.00
ForkJoinPool 23,27,26 25.33
yield 126,103,98 109.00
Setter ThreadPool 23,25,26 24.67
Parallel 34,27,27 29.33
ForkJoinPool 27,26,28 27.00
yield 114,98,107 106.33
Constructor ThreadPool 24,25,24 24.33
Parallel 70,66,27 54.33
ForkJoinPool 42,30,26 32.67
yield 116,112,108 112.00

グラフは、各平均値の棒グラフ

jlombok.png

ソースの話(目デバック用)

インスタンス生成を多く発生させる目的で、計算結果を表すものをLombokで定義

import lombok.ToString;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Data {
  // 何番目
  private long nth;
  // Fn-1
  private long n1;
  // Fn-2
  private long n2;
  // Fn
  long num() { return n1 + n2; }
}

実行用クラス、ParallelStream,ThreadPool,yield周りは最適化が必要な状態

@Functionalinterface
interface Constructor {
    Data instance(long nth, long n1, long n2);
}

public class App {

    public static void main(String[] args) {
        System.out.println("[Start] java version::" + System.getProperty("java.version"));
        //Map<String,Constructor> cm = new HashMap<String,Constructor>(){{
        Map<String,Constructor> cm = new HashMap<>(){{
              // Builder でインスタンス生成
                put("Builder", (nth, n1, n2) -> {
                        return Data.builder()
                        .nth(nth).n1(n1).n2(n2).build();
                    });
                // Constructor でインスタンス生成
                put("Constructor", (nth, n1, n2) -> {
                        return new Data(nth, n1, n2);
                    });
                // Setter でインスタンス生成
                put("Setter", (nth, n1, n2) -> {
                        Data d = new Data();
                        d.setNth(nth);
                        d.setN1(n1);
                        d.setN2(n2);
                        return d;
                    });
            }};

        for (Map.Entry<String,Constructor> ct : cm.entrySet()) {
            LongStream.range(1, 4).forEach(n -> {check(n, ct);});
        }
    }

    static void check(long n, Map.Entry<String,Constructor> ct) {
        final App app = new App(ct.getValue());
        //List<Function<LongStream,List<Data>>> logics = new ArrayList<Function<LongStream,List<Data>>>(){{
        // Parallel, ForkJoin, yield, ThreadPool の4ケースの処理
        List<Function<LongStream,List<Data>>> logics = new ArrayList<>(){{
                add(app::runStream);
                add(app::runForkJoinStream);
                add(app::runYield);
                add(app::runThreadPool);
            }};
        System.out.println("[" + n + "] <" + ct.getKey() + ">");
        logics.forEach(l -> app.calc(l));
    }

    Constructor cs;

    App(Constructor c) {
        this.cs = c;
    }

    // 1回実行
    void calc(Function<LongStream,List<Data>> logic) {
        long start = System.currentTimeMillis();
        List<Data> datas = logic.apply(LongStream.range(0, 30));
        long end = System.currentTimeMillis();
        // 全結果表示 String result = datas.stream().map(d -> { return d.getNth() + "=" + d.num(); }).collect(Collectors.joining(","));
        String result = datas.get(datas.size()-1).toString();
        System.out.println(" exe time:" + (end - start) + " mill sec [" + result + "]");
    }

   // 検証したいところ
    Data fib(long n) {
        if (n == 0) {
            return cs.instance(n, 0, 0);
        } else if (n == 1) {
            return cs.instance(n, n, 0);
        }
        return cs.instance(n, fib(n-1).num(), fib(n-2).num());
    }

    // for yield
    class Fib implements Callable<Data> {
        private long n;
        private long counter;

        Fib(long n) {
            this.counter = 0;
            this.n = n;
        }

        Data fib(long n) {
            this.counter++;
            if (this.counter % 8 == 0) {
                Thread.yield();
            }
            if (n == 0) {
                return cs.instance(n, 0, 0);
            } else if (n == 1) {
                return cs.instance(n, n, 0);
            }
            return cs.instance(n, fib(n-1).num(), fib(n-2).num());
        }

        public Data call() {
            return fib(this.n);
        }
    }

    List<Data> runStream(LongStream st) {
        System.out.print("Parallel>");
        return st.parallel().mapToObj(this::fib).collect(Collectors.toList());
    }

    List<Data> runForkJoinStream(LongStream st) {
        System.out.print("ForkJoin>");
        try { return new ForkJoinPool(4).submit(() -> {return runStream(st);}).get(); }
        catch (Throwable e) { return null; }
    }

    List<Data> runThreadPool(LongStream st) {
        return runThreadPool("Thradpool>", st, 4, (n) -> { return () -> fib(n); });
    }

    List<Data> runYield(LongStream st) {
        return runThreadPool("Yield>", st, 3, (n) -> { return new Fib(n); });
    }

    List<Data> runThreadPool(String name, LongStream st, int size, LongFunction<Callable<Data>> map) {
        System.out.print(name);
        List<Callable<Data>> tasks = st.mapToObj(map).collect(Collectors.toList());
        ExecutorService exe = Executors.newFixedThreadPool(size);
        try {
            List<Future<Data>> result = exe.invokeAll(tasks);
            return result.stream().map(this::conv).collect(Collectors.toList());
        } catch (Throwable e) {
            e.printStackTrace();
            return null;
        } finally {
            exe.shutdown();
        }
    }

    Data conv(Future<Data> f) {
        try {return f.get();} catch (Exception e) {return null;}
    }

}

余談

lombok と Java9

java9 は未対応なので、ソースを生成して実行するしかいない。
アノテーションは使えない。

$ java -jar lombok.jar delombok -f pretty -p src/main/lombok/Data.java
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by lombok.javac.JavacTreeMaker$TypeTag to method com.sun.tools.javac.code.Type.getTag()
WARNING: Please consider reporting this to the maintainers of lombok.javac.JavacTreeMaker$TypeTag
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
Exception in thread "main" java.lang.NoSuchFieldError: text
 at lombok.javac.Javac8BasedLombokOptions.putJavacOption(Javac8BasedLombokOptions.java:42)
 at lombok.delombok.Delombok.delombok(Delombok.java:472)
 at lombok.delombok.Delombok.main(Delombok.java:256)
 at lombok.delombok.DelombokApp.runDirectly(DelombokApp.java:176)
 at lombok.delombok.DelombokApp.runApp(DelombokApp.java:47)
 at lombok.core.Main.go(Main.java:128)
 at lombok.core.Main.main(Main.java:43)
 at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
 at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.base/java.lang.reflect.Method.invoke(Method.java:564)
 at lombok.launch.Main.main(Main.java:36)

Java8 と 匿名クラス

Java8 では匿名内部クラスの型推論出来ない。

エラー: HashMap<K,V>の型引数を推論できません
        Map<String,Constructor> cm = new HashMap<>(){{
                                                ^
  理由: 匿名内部クラスでは'<>'を使用できません
  K,Vが型変数の場合:
    クラス HashMapで宣言されているKはObjectを拡張します
    クラス HashMapで宣言されているVはObjectを拡張します
エラー: ArrayList<E>の型引数を推論できません
List<Function<LongStream,List<Data>>> logics = new ArrayList<>(){{
                                                              ^
  理由: 匿名内部クラスでは'<>'を使用できません
  Eが型変数の場合:
    クラス ArrayListで宣言されているEはObjectを拡張します

Reference