Question to Claude
Including languages that don't compile to C but perhaps to VM language or even assembly, what are the top 10 most optimized languages ?
Hi all,
for the purposes of making the Xpact XML parser compete with the eXpat parser written in C, I have had to think more like a C programmer while writing Eiffel.
It's been an interesting challenge to turn off the garbage collector temporarily during a parse and then discipline myself to create as few new objects as possible, and recycle objects kept in a pool . Turns out I am not the only developer using a GC language who is thinking like that. I came across this interesting video recently.
Trading Firms Run Java Without Ever Letting It Collect
High-frequency trading firms write their hot path in Java — then engineer it so the garbage collector never runs. Here's how zero-allocation Java actually works. In a world where a few milliseconds of GC pause means a missed trade, the fastest firms on earth didn't make Java's garbage collector faster. They starved it. This is a breakdown of the low-latency techniques that let HFT systems run Java in the hot path without ever pausing to collect: object pooling, off-heap memory, the old sun.misc.Unsafe path (now the Foreign Function & Memory API and MemorySegment), and pre-market JIT warmup. It's also an honest look at what that costs — not in hardware, but in the rare engineers who can hold the whole fragile machine in their heads. This is mechanical sympathy taken to its most extreme conclusion, and a clear argument for why almost nobody should copy it.
-- SmartDevelopersUseUnderScoresInTheirIdentifiersBecause_it_is_much_easier_to_read (Eiffel = Security by Contract + C Speed)
date_time: TUPLE [ EL_DATE, EL_DATEABLE, EL_QUANTITY_INCREASE_RATE_CALCULATOR ] do create Result end initialization: TUPLE [ EL_MAKEABLE_TO_SIZE_FACTORY [EL_MAKEABLE_TO_SIZE] ] do create Result end
But with gelint I could perhaps also test classes that are not compilation targets on the current platform.
Here is some documentation explaining the design used in
gec/gelint to be able to run faster with no GC:
https://gobo-eiffel.github.io/gobo/tool/gelint/doc/technology.html
-- SmartDevelopersUseUnderScoresInTheirIdentifiersBecause_it_is_much_easier_to_read (Eiffel = Security by Contract + C Speed)
--
You received this message because you are subscribed to the Google Groups "Eiffel Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to eiffel-users...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/eiffel-users/366a043c-dc76-479e-a94a-e530c140071d%40eiffel-loop.com.
Hello Finnian,
Be interesting to know what you think thinking like a C programmer actually is?
Hi Ian
in the context of Eiffel thinking like a C programmer means thinking about what the C is doing that is generated by the compiler.
Without going into too much detail I can give a couple of
examples for a hot path:
If I am thinking like an Eiffel programmer I write this:
scan_start_tag_name (buf: SPECIAL [CHARACTER]; start_index, end_index, lead_count: INTEGER): INTEGER -- After consuming name-start char(s); scan rest of start tag name. local index, name_lower, name_upper, byte_count, bt_code, l_last_colon_index: INTEGER; done: BOOLEAN do index := start_index; name_lower := start_index - lead_count; name_upper := Unset from until index >= end_index or done loop bt_code := Byte_type_table [buf [index].code] inspect bt_code
But if I am thinking like a C programmer I write this
scan_start_tag_name (buf: SPECIAL [CHARACTER]; start_index, end_index, lead_count: INTEGER; bt_table: SPECIAL [INTEGER]): INTEGER
-- After consuming name-start char(s); scan rest of start tag name.
local
index, name_lower, name_upper, byte_count, bt_code, l_last_colon_index: INTEGER; done: BOOLEAN
do
index := start_index; name_lower := start_index - lead_count; name_upper := Unset
from until index >= end_index or done loop
bt_code := bt_table [buf [index].code]
inspect bt_code
The latter had greatly improved performance across many such routines
Or a very literal example of using C data structs in an Eiffel program
process_content (
buf: like buffer; start_index, end_index: INTEGER; bt_table: SPECIAL [INTEGER]
attributes: XT_ATTRIBUTE_BUFFER_INTERVALS; names: like name_cache
a_context: XT_ELEMENT_CONTEXT; parse_data: POINTER; a_source_type: NATURAL_8
): INTEGER
parse_data is a pointer to the data defined by class XT_C_PARSE_DATA_STRUCT (github link). I could have passed the MANAGED_POINTER reference that allocates the memory, but thinking in C terms, I pass a raw pointer instead.
Also as an Eiffel programmer, you would not want to have such a long argument list, you would put some or all of that data in another object.
but for C coder, if it gets you the performance you want, then why not.
Also Eiffel coders might prefer bt_table: ARRAY [INTEGER] but a C coder would definitely want bt_table: SPECIAL [INTEGER]
regards
Finnian
Ideally I would expect that the compiler or runtime does this kind of optimization. Doing low-level programming using a high-level language seems strange to me.
The Xpact XML parser is 98% written at the level of Eiffel, and only 2% at the level of C (using arbitrary figures)
I’m using Eiffel for the things Eiffel is good at:
I’m deliberately applying low-level performance techniques only to a very narrow hot path where they matter.
That's actually one of the interesting advantages of having a high-level language: I can choose the level of abstraction appropriate to each part of the program.
The alternative isn't necessarily:
Eiffel + GC or C.
It can be:
Mostly high-level Eiffel + a small amount of deliberately low-level Eiffel.
And that's essentially what my experiment demonstrates.
There is also a subtle misconception about what the compiler can do
A compiler cannot generally infer that I should pool objects instead of allocating them.
Suppose I have something conceptually like:
create {SPECIAL [CHARACTER_8]}.make_empty (buffer_size)
The compiler cannot simply decide:
"Ah, this object could be reused from a pool."
because that potentially changes observable program behavior.
The compiler has to preserve the semantics of my program. Object identity, creation procedures, references held elsewhere, invariants, attachment status, etc. can all matter.
Similarly, "don't allocate anything during this operation" is often an architectural decision, not merely an optimization that a compiler can safely discover.
The developer might know:
"These parser objects have a lifetime bounded by this parse and can be recycled."
The compiler generally doesn't have that semantic knowledge.
What I meant was: If the compiler detects that the features used in ARRAY are all available from SPECIAL, can't it optimize? For example ARRAY allows non-zero-based indexing by doing extra index arithmetic relativ to SPECIAL. If the ARRAY is zero-based the extra arithmetic can be avoided. I know that this is beyond what compilers can do currently, but aggressive optimization would make such high-level languages more attractive.
I see what you mean. Yes, in theory this could definitely be done. It gave me the idea to ask Claude the question below.
That said, I'm happy enough with Eiffel as it is. In my
experience it's not that inconvenient to decide the appropriate
optimization level for each part of a program yourself. Most of
the time ARRAY is perfectly fine as-is. There's also
likely a tradeoff: a smarter compiler doing this kind of analysis
would probably be slower, and I'd rather have a compiler that's
fast.
Dome years ago I wanted to demonstrate argument passing to functions as done in C when I realized that the compiler optimized "printf("%s\n", "Hello")" to "puts("Hello")", so a different function had been called, and only one actual parameter being passed. I didn't expect that level of optimization at that time..
Including languages that don't compile to C but perhaps to VM language or even assembly, what are the top 10 most optimized languages ?
Broadening the net to include VM-targeted and directly-native-compiled languages changes the picture a fair bit, since some of the most sophisticated optimizers on Earth live in JIT tiers (V8, HotSpot, JVM's C2/Graal) rather than ahead-of-time compilers. Here's a top 10, ranked more by "sophistication and number of distinct optimization techniques employed" than by raw benchmark speed — those aren't the same thing, and a couple of entries here aren't even the fastest in absolute terms but are the most technically interesting.
1. Common Lisp (SBCL) /
historically Stalin (Scheme)
SBCL is the practical entry: a
dynamically-typed, GC'd, high-level language
whose native compiler does full type inference
when you give it declarations, unboxes floats
and integers, eliminates bounds checks, and
inlines generic arithmetic dispatch away
entirely — all while remaining a fully
interactive, dynamic language the rest of the
time. It's the best living proof that
"high-level and dynamic" doesn't have to mean
"unoptimizable." Stalin (mentioned last time) is
the research-extreme version of the same idea
for Scheme.
2. Glasgow Haskell Compiler
(GHC)
Rewrite-rule-driven stream fusion, strictness
analysis, worker/wrapper transformation,
unboxing, and a core language (System FC)
specifically designed to make aggressive
whole-program transformation sound. Probably has
the largest number of distinct,
formally justified optimization passes of
anything on this list.
3. MLton (Standard ML)
Whole-program monomorphization and
defunctorization — it refuses separate
compilation specifically so it can see
everything at once and specialize
polymorphic/modular code into flat, monomorphic,
heavily inlined output. The closest realization
of your friend's original ARRAY/SPECIAL idea.
4. Java (HotSpot JVM, and
Graal)
This deserves a much higher spot than people
expect. C2's speculative optimization — inlining
through virtual calls based on observed
call-site monomorphism, escape analysis to
stack-allocate objects that never leave a
method, and deoptimization when a speculative
guard fails — is genuinely one of the most
sophisticated optimization systems in production
use anywhere. Graal extends this with partial
escape analysis and more aggressive speculation.
This is the JIT/guard-and-deoptimize philosophy
mentioned last time (in the Self/Smalltalk
lineage), industrialized at massive scale.
5. JavaScript (V8,
SpiderMonkey)
Similar philosophy to HotSpot but for a far more
dynamic language: hidden classes (shapes) turn
duck-typed property access into fixed-offset
struct access when the shape is stable, inline
caches speculate on call-site types, and
TurboFan compiles hot speculative paths down to
near-native code with deopt guards. Remarkable
given JS has essentially no static type
information to start from.
6. Rust
Monomorphization of generics per-instantiation,
zero-cost abstraction as a language design constraint
rather than an optimizer afterthought, and
LLVM's full optimization pipeline underneath.
Its trick is pushing as much proof (aliasing via
the borrow checker) into the type system so LLVM
can optimize as aggressively as it would
hand-verified C.
7. Julia
Type-specializing JIT via multiple dispatch —
every generic function gets compiled to a
specialized native version per concrete
argument-type combination the first time it's
called, via LLVM. Interesting because it gets
C-like performance from code that reads like
Python.
8. OCaml (with Flambda)
Cross-module inlining and specialization added
on top of OCaml's already-fast baseline native
compiler; closes much of the gap to GHC/MLton
while staying closer to "practical systems
language."
9. Ada / SPARK (GNAT, native
code generation)
Included again here specifically for
check-elimination: contracts and range types are
fed to the prover/optimizer and verified
statically, so bounds and overflow checks that
other languages pay for at runtime are simply
proven unnecessary and dropped.
10. Self
Historically essential rather than practically
relevant today: pioneered polymorphic inline
caches and speculative type feedback for a
language with no static types and no
classes at all (pure prototype-based objects).
Nearly every JIT on this list (V8, HotSpot,
Graal) is a direct intellectual descendant of
Self's optimization techniques from the late
'80s/early '90s at Sun.
If it's useful, I can go deeper on any one of these — in particular, HotSpot's speculate-and-deopt cycle or GHC's fusion rules would probably be the most directly instructive reading for thinking about what a more aggressive EiffelStudio backend could borrow.