question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Optimize generated code to reduce method count.

See original GitHub issue

Android’s file format, dex, is highly sensitive to the number of methods in the contained class files. There’s a method table with a 16-bit index which thus restricts the number of methods to 65536. “65536 methods?” you might say, “that should be enough for anyone!” Unfortunately these add up quick for all kinds of reasons.

As I’m sure you are aware, Retrolambda has a big userbase in the Android community due to the restriction of the toolchain only understanding Java 6/7 classfiles. Minimizing the number of methods generated per lambda and method reference would go a long way to help keep applications under this limit.

There’s currently four cases whose generated code can be altered to reduce the number of required methods required for each lambda and method reference. By my estimation this will remove between 2000 and 2500 method references from our app! For our current usage count, that’s approximately 5%!

Instance factory method

All $$Lambda$ classes have a lambdaFactory$ method which either returns a new instance for capturing lambdas or a cached instance for non-capturing lambdas. The implementation either delegates to the private constructor or a private static field. Removing the private modifier and making these package scoped would allow the constructor or field to be reference directly from the original call site avoiding the need for this method altogether.

Non-capturing example:

class Test {
  public static void main(String... args) {
    run(() -> System.out.println("Hey!"));
  }
  private static void run(Runnable run) {
    run.run();
  }
}

Generates:

final class Test$$Lambda$1 implements java.lang.Runnable {
  private static final Test$$Lambda$1 instance;

  // <omitted>

  public static java.lang.Runnable lambdaFactory$();
    Code:
       0: getstatic     #22                 // Field instance:LTest$$Lambda$1;
       3: areturn
}
class Test {
  public static void main(java.lang.String...);
    Code:
       0: invokestatic  #22                 // Method Test$$Lambda$1.lambdaFactory$:()Ljava/lang/Runnable;
       3: invokestatic  #26                 // Method run:(Ljava/lang/Runnable;)V
       6: return

  // <omitted>
}

Capturing example:

class Test {
  public static void main(String... args) {
    run(() -> System.out.println("Hey!" + args));
  }
  private static void run(Runnable run) {
    run.run();
  }
}
final class Test$$Lambda$1 implements java.lang.Runnable {
  private Test$$Lambda$1(java.lang.String[]);
    Code:
       0: aload_0
       1: invokespecial #13                 // Method java/lang/Object."<init>":()V
       4: aload_0
       5: aload_1
       6: putfield      #15                 // Field arg$1:[Ljava/lang/String;
       9: return

  // <omitted>

  public static java.lang.Runnable lambdaFactory$(java.lang.String[]);
    Code:
       0: new           #2                  // class Test$$Lambda$1
       3: dup
       4: aload_0
       5: invokespecial #19                 // Method "<init>":([Ljava/lang/String;)V
       8: areturn
}
class Test {
  public static void main(java.lang.String...);
    Code:
       0: aload_0
       1: invokestatic  #22                 // Method Test$$Lambda$1.lambdaFactory$:([Ljava/lang/String;)Ljava/lang/Runnable;
       4: invokestatic  #26                 // Method run:(Ljava/lang/Runnable;)V
       7: return

 // <omitted>
}

Completed Parts 🎉

Non-private method references (#84)

Method references on non-private methods (both static and instance) generate a needless package-scoped accessor method. This only needs generated for private methods. All other versions can effectively “inline” the method call directly to the generated $$Lambda$ class.

Example:

class Test {
  public static void main(String... args) {
    run(Test::sayHi);
  }
  private static void run(Runnable run) {
    run.run();
  }
  static void sayHi() {
    System.out.println("Hey!");
  }
}

Generates:

final class Test$$Lambda$1 implements java.lang.Runnable {
  // <omitted>

  public void run();
    Code:
       0: aload_0
       1: getfield      #15                 // Field arg$1:LTest;
       4: invokestatic  #25                 // Method Test.access$lambda$0:(LTest;)V
       7: return
}
class Test {
  // <omitted>

  void sayHi();
    Code:
       0: getstatic     #41                 // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #43                 // String Hey!
       5: invokevirtual #49                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return

  static void access$lambda$0(Test);
    Code:
       0: aload_0
       1: invokevirtual #52                 // Method sayHi:()V
       4: return
}

Lambda body methods (#86)

The body of a lambda is copied into a private static method on the defining class with any captured references hoisted into parameters. Being a private method, though, an additional accessor method has to be created so that the $$Lambda$ class can call back into it. Promoting the lambda body method itself to package scoped eliminates the need for this extra indirection.

Example:

class Test {
  public static void main(String... args) {
    run(() -> System.out.println("Hey!" + args));
  }
  private static void run(Runnable run) {
    run.run();
  }
}

Generates:

class Test {
  // <omitted>

  private static void lambda$main$0(java.lang.String[]);
    Code:
       0: getstatic     #37                 // Field java/lang/System.out:Ljava/io/PrintStream;
       3: new           #39                 // class java/lang/StringBuilder
       6: dup
       7: invokespecial #40                 // Method java/lang/StringBuilder."<init>":()V
      10: ldc           #42                 // String Hey!
      12: invokevirtual #46                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      15: aload_0
      16: invokevirtual #49                 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
      19: invokevirtual #53                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      22: invokevirtual #59                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      25: return

  static void access$lambda$0(java.lang.String[]);
    Code:
       0: aload_0
       1: invokestatic  #62                 // Method lambda$main$0:([Ljava/lang/String;)V
       4: return
}

Unused factory method (#82)

A factory method named get$Lambda shows up on the $$Lamda$ classes for capturing lambdas. This method duplicates the implementation of the lambdaFactory$ on the same class but is completely unused.

class Test {
  public static void main(String... args) {
    run(() -> System.out.println("Hey!" + args));
  }
  private static void run(Runnable run) {
    run.run();
  }
}
final class Test$$Lambda$1 implements java.lang.Runnable {
  // <omitted>

  private static java.lang.Runnable get$Lambda(java.lang.String[]);
    Code:
       0: new           #2                  // class Test$$Lambda$1
       3: dup
       4: aload_0
       5: invokespecial #19                 // Method "<init>":([Ljava/lang/String;)V
       8: areturn

  // <omitted>
}

Issue Analytics

  • State:closed
  • Created 8 years ago
  • Reactions:19
  • Comments:8 (8 by maintainers)

github_iconTop GitHub Comments

18reactions
JakeWhartoncommented, Apr 30, 2016

Thank you!

16reactions
luontolacommented, Apr 30, 2016

Your pull requests are now included in Retrolambda 2.3.0. Sorry for taking so long. 😓

Read more comments on GitHub >

github_iconTop Results From Across the Web

Efficiently reducing your method count - Jeroen Mols
Normally I recommend never to optimize unless you have a problem. But with method counts, I really advice you to consider the method...
Read more >
How to properly reduce an app method count (below dex-limit)
First things first, you don't need all those exclude s. If two dependencies use com.android.support:support-v4:26.1.0 , it is only included ...
Read more >
Python's reduce(): From Functional to Pythonic Style
The idea behind Python's reduce() is to take an existing function, apply it cumulatively to all the items in an iterable, and generate...
Read more >
Optimize Options (Using the GNU Compiler Collection (GCC))
Optimizing compilation takes somewhat more time, and a lot more memory for a large function. With -O , the compiler tries to reduce...
Read more >
Optimize java byte code - ProAndroidDev
Reduce total method count, and make optimized use of 65k method count limit imposed by dex . · Save space and time by...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found