
Why is Rust slightly slower than C? - homarp
https://github.com/ixy-languages/ixy-languages/blob/master/Rust-vs-C-performance.md
======
fluffything
Because C is slower than C.

That is, they are compiling C with GCC, but Rust uses LLVM as a backend.
Compiling their C code with clang reveals that it is also slightly slower than
the C code compiled by GCC, but not faster than Rust.

The actual conclusion here is that the GCC toolchain is slightly faster than
the LLVM toolchain for this particular use case, but that isn't really news -
it happens all the time.

If one want to focus this on Rust, then the downside of Rust vs C here is that
C has a GCC toolchain that happens to be faster for this use case, but Rust
does not.

~~~
013a
Yeah, their initial explanation is that its the "safety features", but I was
under the impression that literally everything related to rust's safety
happens at compile time. Their big selling point is the "zero cost
abstractions".

~~~
Zarel
Their initial explanation is bounds checking specifically. While most
abstractions are zero-cost, bounds checking isn't.

I do wish there were a way to track expectations about integer ranges in a
type system, but there currently aren't.

~~~
steveklabnik
Bounds checking is a “zero cost abstraction” in the sense that it is both “pay
for what you use” and “you couldn’t implement it yourself any better.” That
said, the implemention is not as fast as it theoretically can be, because the
compiler isn’t always removing bounds checks that are provably true.

When const generic lands, you will be able to make those assertions! A basic
implementation is in nightly.

~~~
dbaupp
This is taking zero-cost abstraction to the extreme, and I think waters down
the concept to the point that it almost isn't useful.

One can argue that any feature is a zero-cost abstraction for the exact set of
trade-offs it has (in the limit: "you couldn't implement a feature that
executes this exact sequence of instructions 'mov ...' any better if you did
it by hand").

I think focusing on a hypothetical perfect coder is more useful, someone who
never fails an assertion or bounds check. This person does not _need_ any
safety/convenience features (unlike us real humans), but they can still use
some such features without a penalty. Bounds checks are not one of them.
(But... At least they don't have much of a pervasive/whole program cost.)

~~~
imtringued
Bounds checking is not an abstraction. Therefore you are the one who is
twisting the word "zero-cost abstraction" to cover things it doesn't cover.

~~~
haberman
GC isn't an abstraction either. By that argument, you could claim that you
have zero-cost abstractions even if you have stop-the-world GC.

~~~
iopq
It does abstract over memory management quite a bit taking out the
implementation details. What is an abstraction?

------
CodesInChaos
Currently Rust doesn't make proper use of its aliasing rules because of bugs
in LLVM. This can reduce performance of code by forcing unnecessary memory
accesses. As an experiment you could try the `-Zmutable-noalias=yes` option,
which should enable these optimizations (but exposes you to those bugs). See
[masklynn's
comment]([https://news.ycombinator.com/item?id=20948779](https://news.ycombinator.com/item?id=20948779)).

In general Rust suffers a bit because LLVM is primarily a C++ compiler
backend. For example infinite loops without side-effects are UB in C++, but
supposed to be well defined in Rust. This is even an issue for C, because
`while(1) {}` is a valid in C but UB in C++. [https://github.com/rust-
lang/rust/issues/28728](https://github.com/rust-lang/rust/issues/28728)

~~~
emmericp
Yeah, enabling noalias optimizations is on our todo-list. I think that’s one
of the most interesting performance features of Rust. It could be the thing
that makes Rust faster than C, ultimately.

Too bad it‘s so broken in LLVM :(

~~~
masklinn
> Too bad it‘s so broken in LLVM :(

TBF it wasn't just LLVM: in [https://github.com/rust-
lang/rust/issues/54878](https://github.com/rust-lang/rust/issues/54878)
"nikic" and "comex" provided C test cases which failed in both clang[0] and
gcc[1]. Though GCC has since fixed the issue while it remains open on LLVM.

[0]
[https://bugs.llvm.org/show_bug.cgi?id=39282](https://bugs.llvm.org/show_bug.cgi?id=39282)

[1]
[https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87609](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87609)

------
ncmncm
C is the wrong language to compare to. More precisely, Rust should be able to
_beat_ C, routinely. Presumably it will, when it gets more optimizer
attention. With any luck, that improvement can go into LLVM proper, and speed
up many other languages besides.

Why should Rust beat C? First, it does not suffer from the pointer aliasing
faults C has. Second, const really means const, where in C the compiler has to
assume any pointer-to-const might be use to write through. Because alias
analysis is ironclad, copies can be elided even if some function borrows a
reference to the copy.

At a larger scale, improved abstraction tools mean more powerful libraries may
be written and used, that would be unavailable to the C coder.

It is routine for C++ programs to be substantially faster than a C program
that attempts the same job, even though C++ suffers from the same aliasing
flaws C has. In the finance world, you would be laughed out of the room for
proposing C for a performance-critical task.

This is why articles about other languages comparing them to some fraction of
C performance are amusing. C is a low bar. If you can't beat C, you are Doing
It Wrong. In Rust's case, that would be " _still_ doing it wrong"; we may
assume fixing this is on the schedule, after various "getting it right" and
"compiling faster" goals are met.

~~~
codesushi42
_It is routine for C++ programs to be substantially faster than a C program
that attempts the same job, even though C++ suffers from the same aliasing
flaws C has. In the finance world, you would be laughed out of the room for
proposing C for a performance-critical task._

That depends on how you write your C++ programs. Virtual functions, runtime
type information, STL, RAII ownership can all be performance hits in C++ when
compared to C.

Rust has optimization issues of its own apparently.

C isn't a low bar. It is the benchmark.

~~~
jeremyjh
Is there really a performance cost from RAII? Presumably whatever a destructor
has to do to release resources, C code would also have to do.

~~~
comex
Indeed, RAII is a zero-cost abstraction compared to running the same
destructors manually. On the other hand, C++ makes it really easy to write
programs that copy and destroy things when it isn't necessary.

For example, if a function takes a `std::string` as an argument (by value),
any string you pass in will be copied into a new allocation, which will then
have to be deallocated. That's fine if the function really needs its own
allocation – but it might not. In that case you can avoid the copy by changing
the argument type to `const std::string &` or `std::string_view` (the latter
being new in C++17)... but the difference is subtle enough that even an
experienced programmer might not notice the extraneous copy.

Don't believe me? Consider that in 2014, "std::string was responsible for
almost half of all allocations in the Chrome browser process"! [1]

(Rust does a better job here by requiring more explicitness if you want to
make expensive copies.)

Oh, there's also an issue where the presence of a destructor pessimizes the
calling convention for passing and returning objects of that type by value,
but only slightly, and the issue will be addressed in the future. [2]

[1]
[https://groups.google.com/a/chromium.org/forum/#!msg/chromiu...](https://groups.google.com/a/chromium.org/forum/#!msg/chromium-
dev/EUqoIz2iFU4/kPZ5ZK0K3gEJ)

[2] [https://quuxplusone.github.io/blog/2018/05/02/trivial-
abi-10...](https://quuxplusone.github.io/blog/2018/05/02/trivial-abi-101/)

~~~
ncmncm
This is obsolete and faulty advice.

Given a modern compiler and library, a by-value string temporary can be passed
down a chain of calls with no allocations beyond the first, and returned,
likewise.

Passing a reference means the optimizer cannot optimize accesses, because it
doesn't know what other pointers might be aliasing it. string_view has the
same problem.

Quotes about Chromium and Firefox are likewise obsolete. Old code, old coding
standards. Neither uses RAII, so they pay a 20-30% runtime penalty. With a
modern library, short strings do no allocation. (IIRC Firefox uses 16-bit
characters, so they get less.)

That said, if the runtime performance of code trafficking in string objects
matters, you are Doing It Wrong.

~~~
comex
> Given a modern compiler and library, a by-value string temporary can be
> passed down a chain of calls with no allocations beyond the first, and
> returned, likewise.

For calls (as opposed to returns): If you use std::move, yes. Otherwise, no.
But using std::move is similar to changing the type in that it requires
noticing the problem first.

> Passing a reference means the optimizer cannot optimize accesses, because it
> doesn't know what other pointers might be aliasing it. string_view has the
> same problem.

For `const std::string &`, the optimizer has to assume that the string pointer
and length could change, which is indeed a problem as it has to keep reloading
them if you call, e.g., `c_str()` or `size()` multiple times (with other
things in between). For both `const std::string &` and `std::string_view`, the
optimizer has to assume that the string _data_ could change, but not the
pointer or length, which is much less of a problem because you don't usually
repeatedly load the same piece of string data in a loop. Therefore,
`std::string_view` is a decent choice.

Passing `std::string` by value does indeed have the advantage that the
compiler indeed could theoretically assume the string data is not aliased. But
I just checked and none of Clang, GCC, MSVC, or ICC actually do so:

[https://gcc.godbolt.org/z/DZPsuU](https://gcc.godbolt.org/z/DZPsuU)

~~~
ncmncm
Again, doing it wrong is Doing It Wrong.

But moving can pessimize string-passing and -returning. Just write the code in
the clearest way possible, and optimize hot paths where it turns out to
matter, according to measurements. People have been demonstrated to be very
poor at picking which those are, a priori.

~~~
codesushi42
_Quotes about Chromium and Firefox are likewise obsolete_

Yeah, because neither of those teams had any idea what they were doing,
obviously. Even with C++ 11 at their disposal. But somehow, interestingly
enough, you do.

 _Neither uses RAII, so they pay a 20-30% runtime penalty_

Who knows where you pulled that number from.

I guess then, according to you at least, there are no performance penalties to
be paid for any C++ language features. Please, continue to impart your
knowledge and unreferenced benchmark tests on us all.

------
tom_mellior
Looking at performance counter data is good, but I would have liked to see a
real validation of the hypothesis that bounds checking is to blame for the
extra branches and instructions. That is, modify the Rust compiler to not emit
bounds checks (or maybe there is even a flag for this?) and look at
performance and counters. I would imagine that this would bring the data for
Rust to pretty much the same as C. But other compiler (micro-)optimizations
might be at play as well.

Also, from the paper's Conclusions: "The cost of [Rust's] safety and security
features are only 2% - 10% of throughput on modern out-of-order CPUs." 10%
network throughput is a lot.

~~~
steveklabnik
> (or maybe there is even a flag for this?)

We specifically do not give you a flag to control this behavior, but you can
choose to call different functions to use unchecked access.

~~~
glangdale
This, imo, is absolutely correct (it is a dark idea to have a "let's be unsafe
for more performance" flag) but maybe a experimental build of the Rust
compiler could have this as a configuration option? Possibly the toolchain
could warn every step of the way if such a 'tainted' module is ever linked,
etc.

It just seems like this sort of question is going to recur, and being able to
persistently track the overhead of checking (it would allow you to monitor
specific performance improvements) is much nicer than having someone do a one-
off experiment.

~~~
steveklabnik
If it is implemented, it will be used. And people will put it in their own
builds.

We already have one “secret” escape flag feature, and people do use it, as
much as we don’t talk about it and tell people not to use it when they find
it.

~~~
glangdale
Maybe put a tainted flag in it that causes the linker or runtime to fail? Then
don't open source or release the modifications to allow the linker/runtime to
avoid that failure check and refuse to let anyone check in a "fix" that allows
this check to be skipped to an official build...

This surely seems like an incredibly important cost. Surely it's worth doing a
bit of ugly magic to be able to keep track of it persistently.

~~~
tom_mellior
Thanks to both of you for the insightful discussion. A flag would be helpful
for testing, but it's true that if it's there, it will be used. Still, this
can be tracked as part of a CI system by keeping around a patch for disabling
bounds checks and regularly building and benchmarking a patched version. Less
nice, but should get the job done.

------
asdkhadsj
As an aside, can I just say how happy I am that the language I _generally_
find a joy to use is nearly as fast as C? It's my daily driver, and I have
_almost_ no complaints.

I really do love this language.

~~~
atoav
Agreed. And even if it would be gone over night I still learned _a lot_ by
using it.

------
gameswithgo
I wonder if some of the bounds checks could be eliminated by using iterators
instead of loops? It is common when coming from C to Rust to sometimes avoid
complicated iterators because you imagine it can't be fast, so you use a loop,
but usually the iterator really is faster.

And I believe the checked math can be eliminated just by explicitly stating
you want to use unchecked math. It doesn't require unsafe to do so.

~~~
holy_city
>I wonder if some of the bounds checks could be eliminated by using iterators
instead of loops

It can and often is. Don't use [] to index into data if you can afford not to.
Anecdotally, rustc is also much better at generating SIMD-friendly code with
iterators than idiomatic C/C++, but that depends largely on what you're doing.

~~~
GordonS
As an aside, I seem to recall .NET making an interesting optimisation here,
such that if you access an array using, for example, `data[20]`, bounds checks
are omitted for lower indexes.

~~~
shepmaster
Rust and/or LLVM will make this optimization as well. The easiest example I
can find of this is in the buffered IO code [1], but there have been others

[1]: [https://github.com/rust-
lang/rust/blob/f0b58fcf03391a91f7422...](https://github.com/rust-
lang/rust/blob/f0b58fcf03391a91f74224fe38a696d5a5b789d9/src/libstd/io/buffered.rs#L277-L278)

------
aloknnikhil
It's be interesting to see how Rust performs compared to vector packet
processing ([https://fd.io/technology/](https://fd.io/technology/)). My hunch
is, it's not very easy to implement something similar in Rust that can keep
the instruction cache hot, especially going by the data from this paper.

------
im3w1l
I'm surprised how much bloat rusts adds and how little it affects the speed.

Is there any other downside? Electricity consumption / heat? Evicting other
stuff from cache?

~~~
snuxoll
We can see in this specific case there was better cache locality and more data
was served from the L1 and L2 cache with a drop in L3 cache misses (no hits,
because it didn’t have to look in L3 for anything).

6 cycles for bounds checks that the branch predictor never had to rewind on is
nothing in comparison to saving a couple trips to L3.

~~~
topspin
> We can see in this specific case there was better cache locality

Dramatically better L1 and L2 cache behavior. It _seems_ clear that the
additional instruction load of the Rust driver is partially made up by the
excellent cache utilization.

This "Rust vs C" document is just one part of a larger analysis of network
driver implementations in many languages; C, Rust, Go, C#, Java, OCaml,
Haskell, Swift, Javascript and Python. Have a look at the top level README.md
of that GitHub repo.

------
viraptor
I wish they included rust benchmark code so we can play around and improve it.
If the issue really is the bounds checks, then there are many ways to massage
Rust code to make it obvious they're not needed. But it seems you can't really
play with it without running on actual hardware.

~~~
NickMar
They have provided the code :[https://github.com/ixy-
languages/ixy.rs](https://github.com/ixy-languages/ixy.rs) and the benchmark
script : [https://github.com/ixy-languages/benchmark-
scripts](https://github.com/ixy-languages/benchmark-scripts) . I found that in
the readme of the repo containing the article.

~~~
viraptor
Those generate traffic on a real, connected PCI device. I meant branches of
isolated sections of code with fake memory mapping.

------
maxk42
The two biggest factors involved are

(1) Runtime. Even though C has "no" runtime, there is some runtime overhead:
Variable allocation / de-allocation and alignment. Function invocation / call
stack setup. Et cetera. I don't know enough about Rust to understand what its
exact "runtime" is like, but I'd bet it's not exactly like C. (Rust is
strongly-typed, is it not? Does it do run-time type-checking?) Exception
handling in particular requires a non-negligible amount of run-time overhead
and may account for a significant proportion of the difference between the
performance of Rust and C.

(2) Optimization. Rust and C are going to be compiled with either GCC or LLVM.
The way both work is they compile the program to an internal representation of
opcodes, which then get optimized and platform-specific, optimized machine
code is then emitted. In some cases the specific set of opcodes or
optimizations used with various conventions / data structures may be more
efficient or less efficient. Over time this will improve.

~~~
andolanra
Others have pointed out that Rust's type-checking is all done at compile time.
Rust also does not have exception handling.

To be more specific: errors in Rust are either propagated by function return
values (usually in the form of `Result` or `Option` types) which do not
require any kind of runtime support, or by panicking, which unwinds the stack
for clearing-up of resources and then halts the running thread. (Panics cannot
be caught, although they can [EDIT: sometimes but not always; see burntsushi's
comment below] be observed by a parent thread if they have occurred in a child
thread.) Rust's "runtime", therefore is negligible, much like C's: it consists
mainly of functions which can be called for things like stack unwinding or
backtrace support.

~~~
jadbox
AFAIK, some Rust types are checked at runtime. For example, RefCell:

"Because RefCell<T> allows mutable borrows checked at runtime, you can mutate
the value inside the RefCell<T> even when the RefCell<T> is immutable."

*source [https://doc.rust-lang.org/book/ch15-05-interior-mutability.h...](https://doc.rust-lang.org/book/ch15-05-interior-mutability.html)

EDIT: Sorry, I don't mean 'runtime type checks' are slowing down Rust, but
rather that Rust performs more general runtime safety checks (like RefCell).

~~~
pknopf
That isn't "type checking", that is just a guard to prevent simultaneous
writes to the same data.

~~~
jadbox
You're correct- what I had meant to say is Rust is smarter with more runtime
checks over C for several cases, like RefCell. Therefor, Rust can be slower
than C because of safety (but not because of type checking itself).

~~~
pknopf
RefCell has an internal semaphore. It's used specifically for multi threaded
scenarios.

If someone is writing a multi threaded C app, they will likely be using
semaphores as well. At least, they should be. Rust just enforces it.

So, I wouldn't say rust is "slower" in the regard.

~~~
PrototypeNM1
Iirc RefCell is marked !Sync, I thought Mutex was the multithreadong analog?

~~~
steveklabnik
That's correct.

------
physicsyogi
There are a few numerical computing benchmarks for which Rust is sometimes
faster than C: [https://benchmarksgame-
team.pages.debian.net/benchmarksgame/...](https://benchmarksgame-
team.pages.debian.net/benchmarksgame/fastest/rust.html)

~~~
emmericp
Geometric mean over their "fastest measurement at the largest workload" data
set actually shows that Rust is faster than C in this benchmark

~~~
igouy
Please share!

If true, can you say the difference is not due to chance?

( _fwiw_ the GM that I see — whether the scores are based-on elapsed secs, cpu
secs or "busy" secs — are a little slower for the Rust programs.)

Perhaps the median score of the Rust programs is faster?

------
jamessteel
This is an additional comparison I found with other languages.

[https://github.com/ixy-
languages/ixy.swift/blob/master/perfo...](https://github.com/ixy-
languages/ixy.swift/blob/master/performance/Comparison.pdf)

------
wyldfire
> Our Rust driver is a few percent slower than our C driver

Please qualify the title: it's not generally the case.

------
timvisee
We should not forget that Rust wants safety over speed, so it utilizes array
bound checks and such at runtime, because programmers make mistakes. Sadly,
that introduces slight overhead.

------
didibus
Would be nice to also see why Java is slower than C# and Go.

~~~
masklinn
[https://www.reddit.com/r/programming/comments/d2pku3/a_highs...](https://www.reddit.com/r/programming/comments/d2pku3/a_highspeed_network_driver_written_in_c_rust_go_c/ezwm64t/)

> We are at ~20 bytes of allocation per forwarded packet in Java.

C# and Go have much better facilities to avoid heap allocations.

~~~
pjmlp
Java will eventually get them via Panama and Valhalla projects, something that
should have been there from the the get go, though.

------
hamilyon2
So, the main point I got from article is: rust has runtime array bound checks
and integer owerflow checks. I am excited. Where do I sign up?

------
andrepd
Newbie to rust, but surely there must be a way to disable bounds checking in
rust, right?? Like C++ std::vector has operator() (bounds checked) and
operator[] (pointer dereference, unsafe), or other languages have
get/unsafe_get (ocaml), surely rust has a way to disable bounds checking as
well (or disables it for optimised builds)

~~~
masklinn
> Newbie to rust, but surely there must be a way to disable bounds checking in
> rust, right??

There's an _unsafe_ method to not do bounds-checking, keeping in mind that
indexing outside the collection is UB. It's usually a better idea to e.g. use
iterators, or try and nudge the optimiser towards removing the bounds checks.

> disables it for optimised builds

A compiler flag to add UBs to a valid (though not correct) program is not
considered a great idea by the rust team.

------
graycat
On how "fast" C is, guys, sorry to say this, but in my experience actually (1)
even assembler is not very "fast", (2) for much the same reasons C is not very
fast, (3) often Fortran is faster than C, and (4) PL/I can be the fastest of
all of (1)-(4). More generally, how Fortran and PL/I can be faster than C
should be exploited quite generally but apparently has not been fully
generally.

Why? How can Fortran and PL/I beat assembler and C?

In particular, first, Fortran can easily beat C in subroutines that receive
multidimensional arrays as parameters. Here PL/I also can beat C for the same
reasons. Second, PL/I beats both Fortran and C in string handling.

How, why, what the heck is going on? In simple terms, first, Fortran gets to
_compile_ the multidimensional array handling within the language and C does
not: In C, the programmer has to write the array addressing logic (e.g., row
major or column major) that looks to the C compiler like just more code where
the compiler has to treat that handling as general purpose code; the Fortran
compiler KNOWS that the work is array handling and gets to make better uses of
registers and to store intermediate results where the Fortran source code
can't see them and, thus, take some liberties. That is, in Fortran, the array
handling is in the language where the compiler can take liberties where a C
compiler has to treat the work as ordinary code and not take the liberties.
PL/I has the same advantages. Second, similarly for string handling in PL/I.

In particular, commonly in C and Fortran, string handling is via subroutines
that to the linkage editor look like external references that must be linked.
That indirection, stack manipulation, register save, restore, etc., PL/I
doesn't have to do.

Scientific, engineering, _analytical_ code is awash in multidimensional
arrays, and there Fortran and PL/I can beat C. Now nearly all code is awash in
string handling, and there PL/I can beat both Fortran and C.

For assembler, the old remark was that in practice an assembler programmer
would write his own code that was less well designed and, thus, slower than C,
Fortran, and PL/I. Further, the assembler programmer would be tempted to have
a large library for arrays and strings that would be external subroutine calls
likely with the overhead. Sure, in principle, assembler should win, but in
practice writing code good enough to win is usually a bit too much work.

More generally, compiled functionality offered directly by the language can be
faster than libraries of external subroutines, _methods_ , etc.

~~~
glangdale
How current is this impression? There continue to be first-rate Fortran
compilers that probably win on numerics for exactly the reasons you state, but
I would be surprised that PL/I has a toolchain that's kept up with modern
architecture.

String handling, on most modern toolchains, mostly goes to inline functions or
builtins that the compiler has been (rather pragmatically, if a big
disgustingly) made aware of. It's very unlikely that strcpy or memmem is going
through external linkage.

~~~
graycat
My experience is old, but my summary point remains: Language defined and
compiler supported functionality usually is faster than libraries of external
functions.

The relevance here: C isn't the fastest thing around and can be beaten. Maybe
similarly for Rust.

For PL/I and strings, for the IBM versions on S/360 and S/370, the compiler
might have used some of the hardware instructions for string handling.

I'd be surprised if in C strcpy, etc., were not still external: They USED to
be, and then someone might have written their own version, e.g., for their own
versions of strings, and linked to it; for compatibility, strcpy would about
have to be an external function call still.

~~~
monocasa
The compiler (off the top of my head this applies to GCC, clang, and MSVC,
dont know about ICC) generally understands the C string functions at a
fundamental level and will emit optimized code instead of a function call that
will use hardware instructions and might know the size of the buffer at
compile time. For instance on GCC you have to specify -fno-builtin to say "you
don't know what you think you know about c library, actually emit those
function calls instead of trying to optimize the idea of those calls."

~~~
graycat
I get it now: My view of HN just went down to the sewer: We're into the old,
bitter, go out back and fight it out, religious computer language wars. And
there C is one of the grand sacred subjects:

 _C is the greatest. The greatest, best ever. The greatest, fastest of all
time, never to be improved on. Pure genius. Pure gold. And C programmers are
like people who climb Mount Everest barefoot with no tools and the only REAL
programmers._

Yup, C religion.

My view, bluntly: C was a toy language for an 8 KB DEC computer, well known to
be a toy at the time. It's still a toy language and so primitive that it is a
huge waste of effort in computing. There's no excuse for it and hasn't been
even from the first day it was written, even for operating system code, since
even at the time Multics was written in PL/I. And C is so primitive, in its
K&R definition, that for string and array functionality, it is SLOW.

Besides, the idea that we are stuck-o with strcpy, etc. as in the original K&R
external library means that we can't implement array bounds checking, reports
on memory usage, etc. with an upgraded library of external functions.

My points here are rock solid: Functionality defined in the language and
implemented in the compiler can be a lot faster than implementations with
libraries of external functions. On strings, C and Fortran are examples. On
arrays, C is an example.

If Rust is not a lot faster than K&R C, then I'm surprised at Rust.

All those hero barefoot Mount Everest climbers might notice that they are
really tired, their feet hurt, and they very much need some better equipment!

Ah, programming language religious wars!!!!!

Grow up HN; set aside the toys of childhood. Be rational and set aside
religious wars.

~~~
glangdale
You appear to be making your points in a way in which no developments from the
part 10-20 or so years are intruding. Can you point to a extant PL/I compiler
that produces good code on modern architectures? How sure are you that the
issues that separated C, Fortran and PL/I historically are even remotely
relevant now?

I would imagine inlined functions (and for the brave, interprocedural
analysis) renders many of your points moot. Fortran still has an edge on
numerics as I understand it, but I don't think it's all that decisive.

The other reason that baking functionality into the language is problematic is
that you wind up having a few things that go fast, while you neglect to build
facilities that optimize _other_ people's data structures. So you get the
world's fastest dense array (say) but your sparse array is resolutely
mediocre. Instead of a language packed with special-cases, I would much rather
a minimal language with good analyses (many of which can be supported by
language design; I think Rust has a good future here to support good analyses
by how carefully it tracks lifetimes and how it doesn't fling aliases around
with the abandon of C).

~~~
graycat
> You appear to be making your points in a way in which no developments from
> the part 10-20 or so years are intruding. Can you point to a extant PL/I
> compiler that produces good code on modern architectures? How sure are you
> that the issues that separated C, Fortran and PL/I historically are even
> remotely relevant now?

It's simple. Again, once again, over again, yet again, one more time, the main
point is that functionality in a language definition and implemented in a
compiler is faster than that functionality implemented in external functions.
Simple. Dirt simple. An old point, and still both true and relevant and not
changed in the last "10-20" years.

C is not supposed to have changed from K&R -- that it is backwards compatible
was one of its main selling points and main reason to put up with such meager
functionality that had programmers digging with a teaspoon.

Fortran and PL/I just illustrate the point. The point is thus illustrated. For
this illustration, I can go back to old Fortran 66 and PL/I F level version 4.
Still the point is illustrated and remains true.

And for the changes in C, does the language yet have definitions and support
for multidimensional arrays and something decent in strings? And what about
Malloc and Free -- have they been brought up to date?

I did NOT propose, I am NOT proposing, using Fortran or PL/I now. Instead,
again, once again, over again, ..., I just made a point about language design
and implementation. That this evidence is old is fully appropriate if we
regard C as defined in K&R and not changed since. If people changed it, then
it's a different language and likely not fully compatible with the past.

So, this thread was about C: I assumed C hasn't changed so assumed K&R C.

I don't want to use C but have, recently in one situation was essentially
forced into it. But after that case, I left C. I'm not using Fortran or PL/I,
either.

I'm using Microsoft's .NET version of Visual Basic (VB). Right, laughter,
please, except it appears that .NET VB and .NET C# differ essentially only in
_syntactic sugar_ (there are translations both directions), and I prefer the
VB flavor of that sugar as easier to teach, learn, read, and write than C#
which borrowed some of the C syntax that K&R asserted was _idiosyncratic_ and
in places really obscure.

For this thread, I was surprised that Rust would not run circles around old,
standard K&R C. Soooo, the answer is in part that the C of this thread is not
old K&R C. Okay, C has changed and is no longer seriously backwards
compatible. Okay. Okay to know, but for a huge list of reasons in my work I'm
eager to f'get about C and its digging with a teaspoon.

If the new versions of C do not do well handling multidimensional arrays
passed as arguments, then C should still be consider TOO SLOW, and too clumsy.

But, whether or not I use C, and whatever I think of it, the point remains:
Functionality in the language is faster than that implemented via external
functions.

Of course, this point is important in general when considering _extensible_
languages. Sooo, maybe if could do for syntax as much as has been done for
semantics, then we could do better on extensible languages. But then we could
extend 1000s of ways, maybe once for each application, and then have to worry
about the 1000s of cases of documentation, etc. So, extensible, when do it,
has pros and cons.

I see: Discussing language design, especially for sacred C, is heresy in the
religious language wars. Forbidden topic. Who'd uh thunk -- HN is full of
hyper sensitive and hostile religious bigots.

~~~
SAI_Peregrinus
> C is not supposed to have changed from K&R -- that it is backwards
> compatible was one of its main selling points and main reason to put up with
> such meager functionality that had programmers digging with a teaspoon.

C has gained new functionality and features. It just hasn't deprecated the old
ones. K&R C will still compile with a modern C compiler, but it won't be as
good as modern (C99/C17) C.

