Live data from Hacker News

What I Learned Making My Own JIT Language

mikedrivendevelopment.com

51–60 of 120 posts

Re: What I Learned Making My Own JIT Language

#51
post #40

Earlier quoted context omitted.

[1]In clojure the array type is basically a hashmap to enable high perf/low memory immutability. So I would say "Yes[1]" ^^

I would certainly say "no" to having your basic array type be a hashmap as a hashmap is several orders of magnitude slower than an array for all operations on small arrays and for access for all sizes, although there is nothing wrong with also having hashmap and linked list collections if you need O(1) inserts. However, what I was referring to is much worse than that: JavaScript arrays are defined as a normal hashmap…

I thought dense arrays used linear storage and only fell back to a hashmap when they got sufficiently hole-y.

Re: What I Learned Making My Own JIT Language

#52

Earlier quoted context omitted.

Is there a list of common language poison pills? I'd like to learn more.

a JIT is one! time is wasted doing compiling while running, the compiler has strict time constraints so can't do as many optimizations, claims about theoretical benefits from runtime data collection and-recompilation for optimization are rarely reazlied in the actual world and profile guided optimizations can achieve similar things for AOT compiled programs. soooooooooooo AOT master race

What about WebAssembly? How would you provide the same guarantees and benefits without JITing or interpreting?

Re: What I Learned Making My Own JIT Language

#53

Sure, there are things that can be "poison pills" for performance, even in JITs. For example, another reason why my fib benchmark beats V8 is that V8 has to continually check if the fib function was redefined as a deoptimization check. I don't allow that in Vaiven, so I can produce faster code. Dart was designed to have fewer of these poison pills, for instance. Note to language designers: Reduce your language's pois…

Note to commenter: you can design a language to never change a function's signature, in which case that language loses the "dynamic" and "scripting" part (okay, maybe just the dynamic part).

Nothing wrong with having ultra-dynamic languages, but you will inevitably trade speed for quirky code in cases like that.

Re: What I Learned Making My Own JIT Language

#54
post #21
post #11

Earlier quoted context omitted.

I guess you mean Julia, or to pick three from the past Common Lisp, Dylan and Strongtalk.

Is Julia really a Python competitor, I mean, for the same sort of niches? I had the impression that it was more specialized than that. (Bearing in mind of course that once you're Turing Complete and have a C binding you can technically do anything. Not that either of those two is necessary, but they are sufficient.) But as my phrasing implies, it was just an impression, and two people have mentioned it now. Common Li…

Common Lisp systems have supported two ways to develop software:

a) fast incremental development of Lisp code. For that one got Lisp interpreters (executing the code on the s-expression level) and quick incremental compilers. Code then tends to be more on the dynamic side with full debug information.

b) delivery of optimized applications for deployment. We got optimizing native-code AOT compilers (using type hints and type inference), block compilers (optimizing larger batches of code), whole-program Lisp-to-C compilers and application delivery tools with tree shakers. Code then tends to be more on the static side with little or not debug/development information.

Between a) and b) there is a continuum of different approaches - often in the same program in different code sections - where compilers may support different code generation strategies.

Thus one can develop using an interpreter or fast incremental compiler, and then deliver with an optimizing compiler.

Re: What I Learned Making My Own JIT Language

#55
post #40

Earlier quoted context omitted.

[1]In clojure the array type is basically a hashmap to enable high perf/low memory immutability. So I would say "Yes[1]" ^^

I would certainly say "no" to having your basic array type be a hashmap as a hashmap is several orders of magnitude slower than an array for all operations on small arrays and for access for all sizes, although there is nothing wrong with also having hashmap and linked list collections if you need O(1) inserts. However, what I was referring to is much worse than that: JavaScript arrays are defined as a normal hashmap…

You say all this as if JS's jit engines (V8, chakra, spidermokey, etc) did not destroy "better designed" languages when it comes to speed and efficiency. The only dynamic language that does not pale beyond what is achieved with V8 is LuaJIT.

Re: What I Learned Making My Own JIT Language

#56

I wish http://terralang.org got more attention. It’s a great way to write a jitting library. Add in LuaPEG and it’s a great way to write a jitting language.

Doesn’t it rely on llvm internally? That means that the jit path is slow to start and that your have a huge dependency.

Re: What I Learned Making My Own JIT Language

#57
post #4

Sure, there are things that can be "poison pills" for performance, even in JITs. For example, another reason why my fib benchmark beats V8 is that V8 has to continually check if the fib function was redefined as a deoptimization check. I don't allow that in Vaiven, so I can produce faster code. Dart was designed to have fewer of these poison pills, for instance. Note to language designers: Reduce your language's pois…

I would be intrigued to see a modern attempt at a dynamic language like Python or Ruby, but with attention paid from day one to ensure that only very JIT-able constructs were used, and with an eye towards the language helping the user stick to those and be aware of when they deviate, rather than designing a language, setting "runs fast" somewhere around the third or fourth priority, then trying to JIT it after 10-15…

Dart seemed to be designed with the idea of learning from other dynamic JITed language's mistakes.

Re: What I Learned Making My Own JIT Language

#58

Sure, there are things that can be "poison pills" for performance, even in JITs. For example, another reason why my fib benchmark beats V8 is that V8 has to continually check if the fib function was redefined as a deoptimization check. I don't allow that in Vaiven, so I can produce faster code. Dart was designed to have fewer of these poison pills, for instance. Note to language designers: Reduce your language's pois…

Note to commenter: you can design a language to never change a function's signature, in which case that language loses the "dynamic" and "scripting" part (okay, maybe just the dynamic part). Nothing wrong with having ultra-dynamic languages, but you will inevitably trade speed for quirky code in cases like that.

So the restrictions needed for performance also eliminate many causes of unmaintainable messy code and runtime errors?

This is an easy decision.

Re: What I Learned Making My Own JIT Language

#59

Earlier quoted context omitted.

I say screw deoptimization checks, and put the burden in the redefinition processing. When you redefine something, you stop the world at safepoints and update prior baked assumptions that are registered on the old definition. These redefinitions usually happen during development and initialization, not during performance-sensitive runtime. It would add more to the memory footprint, but that information is not accesse…

Then you either need to have "redefinition" checks, or an actual, full stop the world, which is also a performance nightmare and also affects unrelated code. The proper solution is to not have unnecessary dynamics. There is no need for the ability to redefine methods on an object. If you have functions as first-class objects, you can just let users store functions and call them at the cost of indirection if they wish…

> There is no need for the ability to redefine methods on an object

In prototype-based languages like JavaScript and Lua redefining methods on an object is common at runtime, which makes them quite challenging to JIT. It may be most common early in execution, but there's no way to semantically define that boundary.

Since Lua 5.2 (IIRC) certain metamethods are locked & loaded when you assign a metatable (a prototype definition) to an object. Thus, the __gc finalizer is only obeyed if it existed when the metatable was set on the object. (I don't think this was a performance improvement--more about tradeoffs in GC complexity--but it's an example of side-effect semantics that could be leveraged for JIT optimization.) But this isn't the case for OOP-like methods (which in Lua are normally defined indirectly through the __index metamethod field), as those are ad hoc and would be expected to change during runtime.

A similar issue occurs in any dynamic language (JavaScript, Lua, Perl, etc) that uses generic dictionaries for assigning and loading functions. Ideally a JIT would know that invoking a function like "a.b.foo()" would always load the same function and could elide the dictionary lookup for "b" and "foo". But keeping track of whether the dictionary a or b was modified is costly. Theoretically you could, e.g., add callback hooks to dictionary entries that, when invoked, invalidate some JIT'd block; but such conditional checks and operations cause a huge amount of code bloat and slow down the fast path. Adding more logic in an attempt to minimize unnecessary work causes the same problems.

The lesson from languages like Forth, K, and Lua is that the most important thing to optimize isn't JITing, but the software VM itself, including the bytecode and dispatch tables. (Mike Pall of LuaJIT fame makes this point.) The deep pipelines and huge caches (e.g. for branch speculation) in modern processors means that the abstraction of a bytecode and dispatch table can often be subsumed into the pipeline, resulting in a fixed but relatively small overheard as compared to native code. This is especially true for the bulk of the application code, where you may only see a [hand waving] 1.5x or 2x overhead in a language like Lua or especially LuaJIT. And you can do even better by moving specific hot spots into C code. Languages like Python and Ruby don't have nearly as lean a VM as Lua so the overhead is greater and more variable, but the idea is similar.

If WebAssembly catches on, I think we'll begin to see some regressions in JavaScript JITs because the marginal cost and complexity won't be as worthwhile when people begin moving compute-heavy code into WebAssembly. Simplicity might even bring some performance improvements for code that was never susceptible to JITing because there'll be less baggage.

[1] Largely a result of the lean and clean semantics. Which is not the same as power--Lua has fully lexical scoping with proper closures, and asymmetric stackful coroutines. Asymmetric, stackful coroutines are exceedingly powerful abstractions, but also rid Lua of the colored functions[2] problem that languages that adopt explicit async/await semantics have, which means function invocation semantics are unified making the implementation simpler and leaner and in turn making it more likely VM dispatch is cleanly pipelined in hardware. Less is more. Which is a similar lesson Linux taught the world--Windows never had fork() as it was considered too heavyweight and complex for the common case, and instead focused on multiple different interfaces, one for creating threads and one for invoking new programs. But Linux optimized the heck out of fork, so even creating a thread is faster on Linux than on Windows, and the semantic power of fork makes it easier to implement complex resource sharing schemes between processes than on Windows (i.e. rather having an extremely complex data structure and flags for telling the OS what resources to pass or share between processes, you just use other common APIs--e.g. dup(), etc--before exec()).

[2] http://journal.stuffwithstuff.com/2015/02/01/what-color-is-y...

Re: What I Learned Making My Own JIT Language

#60
post #9

Earlier quoted context omitted.

Well, I can't do a full list, but: Memory non-locality. On modern systems, thrashing through RAM just kills you on performance. I don't know that you have to hand full control over to the programmer, but if you're randomly flinging values on the heap everywhere (a.k.a. "hash tables"), you're gonna hit a wall long before you get to "C performance". For JITs, any time the JIT has the ability to make an assumption, but…

I'd like to add: Don't define your array as nothing but a hashmap with strings as array indexes , with two assignment hooks: One that on assignment updates that sets length = max(length, ToUint32(assigned key)), and one on "length" assignment that iterates over all keys in the hashmap, and for those that can be parsed as uint32's, delete those that have a numerical value larger than the value assigned to length. That…

> That is how JavaScript "Array" objects work. Array indexes are coerced to a strings, as they are for all objects.

Well, according to the spec. All modern VMs make optimisations for small integers, which is allowed as long as the behaviour is in accordance to the spec.

Post reply on HN