*Synopsis:*  Some, including  Evan, maintain that Elm can be "faster than 
JavaScipt".  While that may be true for some use cases including use of 
perhaps more efficient UI updates due to compartmentalized use of 
VirtualDOM, the actaul Javascript code generated by the compiler  is not 
very efficient for many/most tight code cases.  The reason is often 
unnecessary nested function calls that could easily be eliminated by making 
full use of the type information that the Elm compiler has.

Part I

*An example;*  The following tight loop doesn't really do anything, so 
should therefore compile into the very tightest of code (and I'm not 
expecting the Elm compiler to recognize that the result is actually known 
at compile time):

range : Int
range = 1000000000

testProg : Int -> Int
testProg n = -- do some work
  let lmt = min (n + 100000000) range in
  let loop i =
    if i >= lmt then i else
    loop (i + 1) in loop n

which compiles to the following JavaScript:

var _user$project$Temp1482759649866537$range = 1000000000;
var _user$project$Temp1482759649866537$testProg = function (n) {
var lmt = A2(_elm_lang$core$Basics$min, n + 10000000000, 
_user$project$Temp1482759649866537$range);
var loop = function (i) {
loop:
while (true) {
if (_elm_lang$core$Native_Utils.cmp(i, lmt) > -1) {
return i;
} else {
var _v0 = i + 1;
i = _v0;
continue loop;
}
}
};
return loop(n);
};
All right, the code looks fairly good, and we can see that for the inner 
`loop` function that the compiler used its new capability to do tail call 
optimization and turn it into a while loop.  Also, one might expect that 
any decent JIT compiler such as Chrome V8 will use constant folding and get 
rigd of the `_v0 variable.  However, the real limitation of this loop is 
the call to the `Native_Utils.cmp` function.  Function calls are expensive 
at 10's of CPU clock cycles each!

The pertinent JavaScript for `Native_Utils.cmp` is as follows:

var LT = -1, EQ = 0, GT = 1;

function cmp(x, y)
{
if (typeof x !== 'object')
{
return x === y ? EQ : x < y ? LT : GT;
}
...

Note that there are three native branches here (in addition to the 
enclosing one):  one for the check to see it the arguments are objects 
(which of course they are not in the case of Int's as here or Float's as 
the compiler well knows), one to check if they are equal. and (given that 
generally they won't be equal most of the time) one to see which is 
greater.  Now these conditions are not so bad by themselves as they are 
very predictable for modern CPU's branch prediction (i is almost always < 
lmt), so that will cost at most a few CPU cycles; However, the call to the 
function in the first place will cost 10's of CPU clock cycles!

Given that the compiler already knows and strictly enforces that the 
arguments are both Int's or Float's (which are both just Numbers in 
JavaScript), there is no reason that it cannot directly output (i >= lmt) 
instead of the function call and make the whole inner loop take only a few 
CPU clock cycles (on a JavaScript engine such as Chrome V8).  If the 
compiler were consistent in applying this specialization rule, there would 
be no need for the `Native_Utils.cmp` function to do the check if the 
arguments are objects, but for safety's sake and considering that one extra 
check in the case of objects is likely negligible compare to the object 
processing, it may as well be left in for its true best use case of 
comparing objects of the various kinds.

*The Elm Compiler only deals with two primitive types:  Int and Float 
(which are both actual Number/Float to JavaScript), which makes direct use 
of primitive operands very easy*

*Part II*

In a similar way, the definition of the Bitwise library to emulate 
Haskell's definition of the Data.Bits library was silly for only five Int 
functions, made even worse by a name collision with the Bool `xor` 
operator.  Because these are library functions, there is at least one level 
of function call for every use.

*Just as for the above function call for known primitive types (in this 
case only for Int), these functions should be added to the Basics library 
as operators with appropriate infix levels and with the names `&&&`, `|||`, 
`^^^` , `<<<`, and `>>>` just as for F# (which Elm emulates more and more), 
with the Elm compiler directly substituting the equivalent primitive 
JavaScript operators.  The Bitwise library, which should never have 
existed, should be canned.*

Note that although this will make bitwise operators much faster than 
currently, programmers should be aware that there are many operations 
taking place under the covers that may not make these operations as 
efficient as possible alternate means:  under the covers the arguments are 
and'ed to produce 32-bit values, the operation is carried out, and then the 
result is sign extended to convert back to a Number/Float, although for a 
sequence of bitwise operations, the JavaScript engines may only do the 
conversions at the beginning and the end of the sequence.  Also, using bits 
in place of Bools does save space but doesn't save as much as perhaps 
thought:  32 bits are stored in a 64-bit Float, which may not save much 
space as compared to Bool's, especially as some JavaScript engines by 
automatically do the specialization.  In other words, potential time and 
space savings must be tested.  But in any case,these operations may as well 
be as efficient as possible and this above changes should be made.

-- 
You received this message because you are subscribed to the Google Groups "Elm 
Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to elm-discuss+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to