3

I am pretty new to java coming from a c++ world. I am running some server code that runs a method a method foo() that is called a few million times every second. This is latency sensitive code and the method also shows up in profiler as consuming 20% of overall cpu usage by process.

int foo_old() {
     if (Float.isNan(this.x)) { // shows up in profiling
        res = do some computation; // some floating point comparison, doesn't show up in profiling;
        return res;
     } else {
        // Happens 99% of the time;
        res = do something else; // some floating point comparison, doesn't show up in profiling;
        return res;
     }
}

Is there a easy way for me to test whether my method foo will be inlined or not ? can i know that from profiler stack trace information in a running server ?

I tried some optimization by trying to simplify method foo(). Basically there is a float.isNan check within foo , which also shows up on profiler , surprised to see nan checks are slower. compared to some other boolean operations (less than, greater than floating point comparison) faster.

One method i tried was to remove nan check , i.e because i know during compile time if an object needs nan check or not , i tried storing a functional interface (member variable) and assign this functional interface foo_old (which has nan check) OR foo_optimized (that doesn't do nan check) based on the object property known at the time of creation of object (In the constructor of object i assign this interface the correct method reference .

class A {
  final FuncIf test; // Functional interface with same signature as foo_old, foo_new

  public A(bool optimize) {
      test = optimize ? this::foo_optimized : this::foo_old;
  }

  // same as the original foo mentioned above
  int foo_old() {
    ...
   }

  // No nan check
  int foo_optimized() {
        res = do some computation; 
        return res;
  }
}

Now when i create objects i know during compile time/object construction time which version of foo to use. so i assign interface variable to the correct version of foo. After deployment i observe latency infact increased by < 10% . Even though many of the objects now will actually use optimized version of foo.

IS it because foo before was a direct method call and as soon as i use a interface reference the extra indirection of dispatching virtual foo is the overhead i see in latency (The overhead of interface method call being much greater than the Nan check itself ?? ) ? Can't jvm compiler inline this interface method ?

user179156
  • 841
  • 9
  • 31
  • https://stackoverflow.com/questions/2096361/are-there-inline-functions-in-java – hunter Sep 05 '18 at 05:46
  • 2
    the solution above is not useful at all. Any insights as to why latency increase or what other methods i can try to get further insights to what jvm/jit are doing would be more beneficial. – user179156 Sep 05 '18 at 06:03
  • given topic is not a solution, it describes how it behaves. so you may understand the limitations. at least it says no compiler or runtime will make a non final method as inline. and it says all optimizations will be done by compiler and runtime (depends on the compiler and the JRE we use). use final methods , static method will speedup your app. – hunter Sep 05 '18 at 07:35
  • Extracting `res = do some computation;` into a method may decrease the size of `foo_old` and make inlining of `foo_old` possible. Then the JVM might be able to eliminate the `isNaN` check itself. +++ I can't see how using an interface may help (but I may be wrong). – maaartinus Sep 05 '18 at 12:58
  • @maaartinus : do some computation is very trivial 1-2 floating point comparison. so i dont think extracting makes any difference in regards to inlining due to code being smaller. The code is already too small. Second foo_old doesnt get smaller , it is the same , foo_optimized doesnt have nan check. Can you elaborate on why jvm can get rid of nan check by itself ? I don't see any way of jvm being able to do this as it depends on runtime data ( i don't see a way that jvm runtime can detect or be sure about this value just through code analysis even using runtime information). – user179156 Sep 05 '18 at 16:00
  • @user179156 Inlining into an expression like `return x > 0 ? foo_old() : 0` allows to eliminate the `IsNaN` check as `x` can't be NaN. I'm not claiming the JVM knows it, but it does similar stuff. +++ Anyway, I wouldn't trust the profile much. Most profilers suffer a bias as they can only sample on safe-points. – maaartinus Sep 05 '18 at 16:42

1 Answers1

1

The only educated guess would be to measure it's bytecode. Use javap for that. Basically the JVM has two compilers C1 and C2; both can inline that method.

There are three parameters that the JVM cares about when inlining (well, these are the ones I know of, I also know there are a lot more too):

-XX:MaxInlineSize (35 by default)
-XX:FreqInlineSize (325 by default)
-XX:MinInliningThreshold (250 by default)

If your method is called less than MinInliningThreshold (250), it obeys the MaxInlineSize rule, meaning that if it is smaller than 35 bytes, it will be inlined. If it is called more that that, than it obeys to FreqInlineSize, which is 325 bytes (a lot more).

What you can also do is print what is being inlined or not via some parameters:

-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining

As a result of running those you can see messages like:

  callee is too large

this is printed by C1 and it tells you that MaxInlineSize is exceeded for that compiled method. Or:

  too big

printed by C2 compiler when MaxInlineSize exceeded. Or :

  hot method too big

printed by C2 when FreqInlineSize exceeded.

Eugene
  • 117,005
  • 15
  • 201
  • 306
  • can an interface method be inlined ? Or does it require extra indirection ? i am holding a reference to functional interface which seems to add to latency – user179156 Sep 05 '18 at 16:02
  • @user179156 does your `foo_optimized` captures anything? – Eugene Sep 06 '18 at 10:55
  • nope , it works on some member variables. since it is a class method , i assume implicitly it captures "this" refernce ? – user179156 Sep 06 '18 at 20:58
  • @user179156 it does not, unless needed – Eugene Sep 06 '18 at 20:59
  • @user179156 when u say member variables... It means it does work on "something" outside lambda, right? For example `x->x +y.getData()` where `y` is outside lamba would mean that is captures it – Eugene Sep 06 '18 at 21:01
  • what i mean is since foo is class method , it has implicit argument this and all things within that method use class member variables , so going by that it does ot capture anything , foo() { return this.x > this.y : this.y - this.x; } – user179156 Sep 07 '18 at 01:30
  • @user179156 problem is that in this case you are capturing `this` and every time, and a capturing lambda creates a new instance all the time for each separate call. Easy to prove : `public void testMe() { Supplier s = this::test; System.out.println(s.hashCode()); } int test() { return 12; }` Running this in a loop: `for (int i = 0; i < 10; ++i) { d.testMe(); }` where d is an actual instance will produce 10 different values, thus 10 different instances – Eugene Sep 07 '18 at 08:32
  • @user179156 as soon as you make that `test` for example `static` and change the method reference to `Class::test` that loop will return the same values 10 times. This is probably why you see that 10% difference – Eugene Sep 07 '18 at 08:33
  • not exactly true. There is not much of difference if i make it static or not, if i make it static i would have to change the signature of static method , since the computation static method does depends on class member variable , so instead this.foo() , it would be Class::foo(float x, float y) , and called as foo(this.x, foo.y). So doesn't really help at all since we are moving the the referencing of member variables just before method call rather than calling the interface method. – user179156 Sep 12 '18 at 05:55
  • this is gettin interesting for me somehow, can you post a minimal example of what you are doing, so that I could import it and have a closer look? – Eugene Sep 12 '18 at 10:02