628 karma · joined November 13, 2021
HotSpot also uses an interpreter, but its performance comes from the C2 compiler, for example. Quoth Cliff Click [1]:
> if you are spending any amount of time beyond e.g. 5% in the interpreter / stage-0 JIT you need to adjust your JIT'ing strategy. Not saying "no-gain" in a stage-0-only, but definitely should not be a super high payoff if there's a stage-1 following you.
[1] https://old.reddit.com/r/Compilers/comments/sae1iy/compiler_...
Edit: perhaps this is about missing the word "primarily", that performance in such an implementation is bound by interpretation speed.
Indeed ZF relies on stack allocation, but this is done "optimistically" and for all code.
Nonetheless I'll provide the same answer; the compiler technology should be orthogonal to how it is implemented, so it should absolutely be possible. The proposal of using (monomorphic) inline caches is only the bare minimum to avoid crashing.
[1] https://old.reddit.com/r/lisp/comments/wlafgn/zero_feet_a_pr...
I would agree that CLISP bytecode would be heavier; this approach ("template compiler"?) has been used as a baseline JIT in e.g. Jalepeño for the JVM with some success, but being a baseline JIT it is not intended to be that fast either.
Atoms are linked lists?
Wouldn't hold my breath; I recall a tangent on programmers being "afraid of pointers". Depending on your favourite not-C-pointer way of managing memory, that's saying that use-after-free bugs are a sign of bad programmers, or shunning substructural/region/etc types for verifying that explicit memory management is correct.
Lazily compute derivatives? From memory Rust regex computes and caches a DFA lazily from the NFA, for comparison.
If you do use limited counts, you will need a backup tracing collector; and limited counts are appealing because they save space (and most objects tend to only have a few references [1]).
> In fact! I just realized we do have data for which is more performant: this article describes how Instagram turned of GC for Python and just relied on reference counting. They gained 10% increase in performance:
...because generations in the GC are represented as linked lists, and modifying those linked lists reduces the number of page faults, in turn because pages are shared by copy-on-write between process. That representation isn't the best idea already, and shared structure should be in the oldest generation anyway, as you don't want to collect it (as SBCL does for images, which are loaded with copy-on-write).
[1] http://users.cecs.anu.edu.au/~steveb/pubs/papers/rc-ismm-201... page 4
Indeed the issue with measuring barriers is that measuring the barrier doesn't suffice; one wants to measure how the barrier affects the rest of execution. This entails coming up with programs that are much less trivial than repeatedly incrementing a counter.
[0] https://lamport.azurewebsites.net/pubs/garbage.pdf
[1] https://plover.com/~mjd/misc/hbaker-archive/RealTimeGC.html
[1] https://plover.com/~mjd/misc/hbaker-archive/LRefCounts.html
VisualVM <https://visualvm.github.io/> is quite good for Java/JVM profiling.
Safepoints for garbage collection are somewhat similar, but for preemption one wants to interrupt threads on a timer, rather than before the collector takes over. Despite occurring very frequently (at around 100 _million_ checks per second), the time overhead is only about 2.5% or so, according to a study by Blackburn et al [0]. It appears, I think, that as long as the fast not-interrupting path is fast enough, eliminating safepoints isn't too important.
[0] Stop and Go: Understanding Yieldpoint Behaviour <https://users.cecs.anu.edu.au/~steveb/pubs/papers/yieldpoint...>
> But the main argument is that Lisp is hyperproductive if you’re an individual brain genius who’s memorized every emacs key combo.
I certainly haven't, though I suspect this is a bit of an exaggeration.
The example also indicates how the book conflates language and implementation. It's perhaps not the most important thing to know if one is learning how to design and implement programming languages, but unfortunate as CL and Scheme have independent standards; and separating semantics and implementation makes it possible to discuss how differing implementation techniques can be used for the same language. The language implemented also uses dynamic scoping, which is plain weird nowadays.
The C code is of questionable quality, and the author doesn't cover how to improve your chances of writing working C (e.g. using a debugger, testing with sanitizers, etc). This is more of an issue, because the interpreter copies and mutates in a weird way which appears quite easy to mess up, rather than using a garbage collector. The book uses a parser library by the author, so using e.g. the Boehm GC library doesn't seem too bad, but arguably doing your own memory management is more representative of C code that isn't a language implementation. The approach also makes mutation hard to reason about; it's hard to say what a particular update is actually going to affect.
This is legitimately painful to discuss, as this information is readily available in the paper, and seemingly we are getting nowhere by picking at how I made my observations; so I am going to do something more enjoyable, somewhere very far away from you.