Let me introduce you to a 42-year-old software architecture.
“A New Architecture for Large Scientific Simulations”,
P. F. Dubois, 1984, LLNL report 197756.
(A PDF is or was available on-line but the holding site is not up at the moment.)
The architecture consists of a large simulation system (think of an
application like a galaxy formation simulator or a general circulation
model or a computational fluid dynamics model of some sort) with the
basic physics and aspects common to many simulations of this kind coded
in a programming languaged esigned for high numeric calculation efficiency
(such as Fortran, C, C++, probably vectorised, possibly with GPU code,
probably running on multiple processors concurrently),
together with a script configuring a simulation run, selecting inputs,
directing the processing of outputs, coded in a STEERING LANGUAGE
designed to be “lightweight” and easily modified without massive
recompilation and (this is important!) without any risk of tampering
with the tightly interconnected and dangerously vulnerable to slips
computational kernel.
This concept was devised, implemented in the LLNL “Basis” system, and
written up before Python, Perl, Lua, or Matlab were twinkles in their
inventors’ eyes. TCL was consciously intended for steering programs
(such as customising an editor – I still have find memories of Alpha).
Emacs Lisp is a steering language for Emacs. Python is a steering
language for numeric calculations using SciPy and Pandas. I have a
copy of an e-book about programming astronomical simulations in Python.
You would have to be certifiably insane to do this in “native” Python.
Python is S L O W. No, slower than that. But in these simulations,
it’s setting parameters, configuring stuff, and letting the calculations
rip in library code implemented some other way. The metaphor of
steering is meant to call to mind a mere human being at the wheel of a
supertanker: a small weak thing is steering a huge strong thing.
(This Basis system is not to be confused with the 2001 LLNL BASIS
system, which was developed into BioWatch.)
So this says that it’s perfectly reasonable to have a game physics engine
coded in whatever and to steer it (configure a world, model plays, etc)
in Erlang. A web search for “Open source game physics engines” will turn
up a number of 2D and 3D engines it would be silly to rewrite when you
can just use them. (I still prefer using C nodes to NIFs; NIFs compromise
Erlang’s safety, and if the overhead of a local socket for communication
is too high, you need a better design.)
Actually, this is one way that Erlang was used at Ericsson. The AXE-10
exchange was programmed in Eri-Pascal, I think, and had more than a
million lines of code, and had reached the point where any time you
fixed one bug you introduced another. One of the Erlang people had the
bright idea of adding a little ‘external control’ interface and writing
new features in Erlang. This is very much a scripting/steering approach.
[This is IIRCAIPD because it’s old memories of what someone else told me.]
Now for some numbers to illustrate a point I was making earlier.
I wrote a micro-benchmark stressing list processing and integer
arithmetic in 5 different programming languages.
A 56
B 0.97
C 1.00
D 28
E 9.6
F 82
G 40
These are run times normalised to the run time of version C,
which oddly enough was in C. Cruelly hacked C. C where the
allocation of a list cell was done by freelist++ and GC by
freelist = &pool[0]. And of course no tag checking or
dynamic dispatch.
D was the program I started with, written in a a beautiful
programming language designed for list processing. The
language has no static type systems and is, except for
concurrency, semantically very close to Erlang. The
compiler I used compiles via C, which makes life for the
compiler rather hard.
E was of course the Erlang version. It has no -type or -spec declarations.
F and G are written in a dynamically typed object-oriented language which
really doesn’t want to do lists. It loves arrays and hash tables. F is
an idiomatic version using an existing rich List class. G is a stripped
to the bone version using just what it needed. Like D, this compiles via C.
And that has a really painful effect. Allocating a list cell requires
three (count 'em, three!) function calls in C. The equivalent of
[H|T] = L in Erlang costs three (dynamically dispatched!) function calls
in the generated C. Now there’s nothing absolutely fundamental about this.
If lists were idiomatic in this language, we could compile allocation into
a single C function call and dismantling could be inlined, without
changing anything about the language. List cells would still have to take
3 words instead of 2.
The really interesting thing is A and B. They are written in a strict
functional programming language with immutable data structures. The
most important differences here are that it has polymorphic static typing
and a wide range of fixed size signed and unsigned integers (as well as
bignums). So the integer operations could be compiled to single
instructions. The really interesting thing about A and B is that they are
the SAME program, just compiled by different compilers. One supports
incremental compilation with a REPL. The other dos whole-program compilation,
discards everything from the library that’s not used, and optimises the heck
out of what’s left. And despite being safe clean code, it manages to beat C.
So, why did Erlang not do brilliantly in this micro-benchmark?
- With types, [X|Xs] -vs- only requires testing for . The compiler
used for B can map cons = [ptr|00]–>{head,tail} and nil = [0|01] so that
testing is a cheap comparison and accessing the fields of a list cell
requires no untagging.
- with fixed size integers, integer arithmetic can be done by single
instructions with at most a trap-on-overflow instruction overhead.
There is NO tagging or untagging for integers.
- with static whole-program compilation, function calls can go direct
via instructions that make no provision for hot loading.
- as it happens, the compiler used for B does support concurrency,
but since concurrency isn’t used in this benchmark, there’s no cost
for supporting it.
- the A, B, and E compilers use different tagging systems, for good
reason.
I can imagine a subset of Erlang which could be compiled as Erlang or
could be compiled by (a hacked version of) the B compiler. I’m not sure
how hard sharing an address space and a tagging system would be, but it
is doable in principle.