Java map(), filter() and lambda output seems less optimised than JS and PHP

55 views
Skip to first unread message

Graham Pople

unread,
Sep 25, 2016, 11:31:11 PM9/25/16
to Haxe
Hey,

I'm evaluating Haxe for use in a project using JS, PHP and Java, and been very impressed so far.  But I've been running some tests and the generated Java code for some stuff looks way less efficient and optimised than the JS and PHP targets.  


E.g. this simple chunk of Haxe:

[1, 2, 3].map(function (v) return v * 4).filter(function(v) return v > 1);


produces this perfect JS:

[1,2,3].map(function(v) {
  return v * 4;
}).filter(function(v1) {
  return v1 > 1;
});


... and this pretty efficient PHP (I dug through the _hx functions and they all look nimble enough, though I was surprised that native PHP array_filter and array_map aren't used):

_hx_deref((new _hx_array(array(1, 2, 3))))->map(array(new _hx_lambda(array(), "ScreenTest_0"), 'execute'))->filter(array(new _hx_lambda(array(), "ScreenTest_1"), 'execute'));

function ScreenTest_0($v) {
  {
  return $v * 4;
  }
}
function ScreenTest_1($v1) {
  {
  return $v1 > 1;
  }
}



But then there's this for Java:

((haxe.root.Array<java.lang.Object>) (((haxe.root.Array) (new haxe.root.Array<java.lang.Object>(new java.lang.Object[]{1, 2, 3}).map(((haxe.lang.Function) (( (( haxe.root.ScreenTest_render_58__Fun.__hx_current != null )) ? (haxe.root.ScreenTest_render_58__Fun.__hx_current) : (haxe.root.ScreenTest_render_58__Fun.__hx_current = ((haxe.root.ScreenTest_render_58__Fun) (new haxe.root.ScreenTest_render_58__Fun()) )) )) ))) )) ).filter(( (( haxe.root.ScreenTest_render_58__Fun_0.__hx_current != null )) ? (haxe.root.ScreenTest_render_58__Fun_0.__hx_current) : (haxe.root.ScreenTest_render_58__Fun_0.__hx_current = ((haxe.root.ScreenTest_render_58__Fun_0) (new haxe.root.ScreenTest_render_58__Fun_0()) )) ));

with a class generated for the lambda:`

@SuppressWarnings(value={"rawtypes", "unchecked"})
public class ScreenTest_render_58__Fun extends haxe.lang.Function
{
  public ScreenTest_render_58__Fun()
  {
  //line 58 "e:\\dev\\all3\\RankPress SEO\\ScreenTest.hx"
  super(1, 1);
  }


  public static haxe.root.ScreenTest_render_58__Fun __hx_current;

  @Override public double __hx_invoke1_f(double __fn_float1, java.lang.Object __fn_dyn1)
  {
  //line 58 "e:\\dev\\all3\\RankPress SEO\\ScreenTest.hx"
  int v = ( (( __fn_dyn1 == haxe.lang.Runtime.undefined )) ? (((int) (__fn_float1) )) : (((int) (haxe.lang.Runtime.toInt(__fn_dyn1)) )) );
  //line 58 "e:\\dev\\all3\\RankPress SEO\\ScreenTest.hx"
  return ((double) (( v * 4 )) );
  }


}

(with another class for the second lambda).

Apart from the sheer relative size and unreadability of the Java code, there look to be a few performance nasties here.  This "haxe.lang.Runtime.toInt(__fn_dyn1))" doesn't look wonderful, there's a lot of new-ing, and when I have a look at map() in Array.java, it seems to be an unoptimised straight conversion of the Haxe code.

I don't mean this to be critical, I know someone's spent a lot of time on the Java target.  It's just that I rely heavily on map/filter/lambda and this unoptimised output is kinda offputting and makes me wary about adopting Haxe on a large scale.

Incidentally, I had a look at the other major targets, and the C# output is very similar to the Java, but the C++ target looks optimised and fast (though it doesn't use native C++11 lambdas).

Is the Java target still actively worked on?  New Java 8 features (stream, map, filter, lambda) could make this code a lot faster - perhaps a new java8 target could be added?

Hugh

unread,
Sep 27, 2016, 12:59:11 AM9/27/16
to Haxe
C++ - and Java and c#, have to deal with arbitrary functions, and the fact that they may capture variables from anywhere, and some background thread may be updating values etc. so it is quite a complex situation.

However, the truth is that the map and filter *member* functions are not the best way to do this - you need an 'inline' function,  where haxe can merge the map and mapping functions.  eg:


class Test
{
   
inline static function myMap<T,R>(array:Array<T>, f:T->R) : Array<R>
   
{
     
var result = new Array<R>();
     
for(a in array)
         result
.push( f(a) );
     
return result;


     
// or, if you prefer, return [ for(a in array) f(a) ];
   
}

   
public static function main()
   
{
      trace
( myMap([1,2,3], function(i:Int) return i*2 ) );
   
}
}

This inlines everything beautifully as you would hope for.

You can use this with a 'using' command to allow the syntax:  [1,2,3].myMap(  ).

IMHO, haxe should remove the map and filter functions is favour of some kind of "ArrayTools" class.

Hugh
Reply all
Reply to author
Forward
0 new messages