*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 [email protected].
For more options, visit https://groups.google.com/d/optout.