Is zig lang faster than rust?

Do you remember a particular example?

Of course, unsafe Rust can also employ safe functions/methods. But it gives you the option to do stuff that you otherwise can't, so I'd (colloquially) say it's "faster". But yeah, I think there are a lot of surprises with unsafe Rust, and it wouldn't surprise me if there are many cases where it doesn't really make things faster. An example would be helpful here, if anyone has one?

1 Like

I presume Rust on FreeBSD has the same bug as on Linux, where release builds always get debug info of libstd, even if you tried to disable debug info. You can't disable it. It adds over 2MB of size to hello world. You have to use strip, otherwise comparison is not valid (it's just a debug info, it doesn't make language not lightweight, since it's not something that is executed, and you don't have to keep it — use strip).

Secondly, C even statically linked with its puny stdlib is not even equivalent to Rust linked with its much more comprehensive libstd. If you don't want to be comparing cost of unused code, use LTO.

7 Likes

Okay, let's use strip:

fn main() {
    println!("Hello, world!");
}

I'm using:

% rustc --version
rustc 1.60.0-nightly (bd3cb5256 2022-01-16)
% cargo --version
cargo 1.60.0-nightly (06b9d3174 2022-01-11)

Compiling with cargo --release gives a 3695600 bytes big binary (3.5 MiB). After I use strip, it shrinks to 307168 bytes, which are 300 kiB.

Now to the C version:

#include <stdio.h>

int main() {
    printf("Hello World!\n");
}

Note the code is longer :grin:.

I'm using:

% cc --version
FreeBSD clang version 11.0.1 (git@github.com:llvm/llvm-project.git llvmorg-11.0.1-0-g43ff75f2c3fe)
Target: x86_64-unknown-freebsd13.0
Thread model: posix
InstalledDir: /usr/bin

Compiling with cc -Wall -g -O2 hello.c (note that this includes debug info!) gives a 15296 byte binary (14.9 kiB). After I use strip, it shrinks to 4976 bytes, which are 4.9 kiB.

Even after stripping, Rust's binary is 61.7 times bigger than the binary created using C.


I didn't want to say that the overhead isn't worth it. I just wanted to note that it is there.

What is LTO?


P.S.: Note that the Rust binary (as well as the C binary) is dynamically linked with libc (see my pasted ldd output in my previous post).

2 Likes

This has been discussed many times. It's not showing what you're tying to show, only that you're building different things in different ways. Your C executable links to a 30MB dynamic library. You're not using LTO, so Rust also includes support for panicking, stack traces, debug info parsing, strings, vectors, and a ton of other stuff. Rust pays a hefty price to "handle" errors when printing to stdout, which the C version doesn't do.

And even with all of that, it's still one-time cost. It doesn't have to be executed on startup (modern OSes may not even load it from disk at all). It doesn't grow linearly with code size, so the multiplying factor applies only to hello world, and goes towards zero for everything else.

I am bothered by the extra overhead Rust's libstd and debug info add (which is why I created that other thread about removing backtrace costs), but let's be real that for majority of applications it doesn't matter.

21 Likes

Regarding my quantification of this (minimal) example, I was comparing default settings:

I assume that this overhead isn't inherent to Rust (e.g. you can also use no_std), so yeah, maybe my comparison here wasn't fair.

Anyway, I still feel like Rust has more overhead than C. And again: That isn't necessarily bad.

2 Likes

Could you (or someone else) provide an example (or provide a link to one) of how to make a smaller hello world binary?

1 Like
11 Likes

I tried to enable LTO:

[profile.release]
strip = true
opt-level = "z"
lto = true

This reduces binary size to 272904 bytes (266.5 kiB). Still big.

Furthermore adding

codegen-units = 1
panic = "abort"

gets me down to 254992 bytes (249.0 kiB).

Using -Os for the C hello world (plus strip) gives me a 4960 bytes binary.

Anyway, maybe this isn't the right thing to be discussed in this topic, and perhaps it has been discussed before plenty. Thanks for the link in that matter, I'll read into it.

Normally I'm not really worried about binary sizes, except when doing something for microcontrollers / embedded systems. But I'm sure Rust can produce small binaries for those with the right settings.

1 Like

It isn't entirely fair as the rust version statically links to the formatting machinery, while the C version dynamically links to printf. In addition the rust version is capable of printing nice panic messages when aborting, while the C version isn't. Using -Zbuild-std -Zbuild-std-features=panic_immediate_abort to disable panic messages results in a 47k binary with LTO enabled and 67k with LTO disabled after stripping. This includes code to ensure that stdin, stdout and stderr are open, to give an understandable message on stackoverflow rather than SIGSEGV. These aren't done by libc and the lack of the former can cause security issues.

12 Likes

Of course there are reasons why the Rust binary is bigger. If I erased all the structural differences in my observation then, well, the binaries would be of equal size.

But in the end, the binary I created with Rust is bigger. :man_shrugging:

2 Likes

Getting back to the original topic of this thread, "Is zig lang faster than rust?", isn't "unfairness" inherent when we compare languages with a different focus? It's also unfair if I compare the speed of C with the speed of Python, because they work very different. But there's still a difference in speed, and there is a reason why we compare speed between these two very different languages.

When talking about "overhead", I'm coming from C and Lua and usually work with a very small dependency tree. Since I use Rust, that has drastically changed. For example, cargo tree easily gives me a long list of crates that I don't even know (env_logger:thinking:). That, of course, isn't inherent to Rust either, as it's up to me which crates I use and which not. Anyway, I feel like Rust comes with more overhead than I'm used to. And I would like to repeat for a third(?) time that this isn't necessarily bad!

Anyway, may I ask the question: Does Zig have less overhead than Rust?

Of course, "overhead" is a vague term. And I don't really know Zig, so I have no idea if this hypothesis is true. But I know Rust, and I feel like things quickly get complex in Rust. I'm not surprised that Rust's hello world is 60 times bigger than when I do it in C. Even if (or especially as) this comparison is unfair.

1 Like

If you're going to ask the question, you need to include a precise definition of what "overhead" means to you.

3 Likes

As I said, it's a vague term. I could think of:

  • binary size
  • external dependencies needed when working with idiomatic code
  • compile time
  • compiler complexity (see also bus factor)
  • compiler size
  • language complexity (e.g. how complex is a document that describes the whole language in a way it can be understood by an average programmer)

Maybe the word "overhead" is not the best here. I could rephrase the question "Is zig lang more lightweight than rust?" And I can't provide a precise definition of what "lightweightness" is, but the list above should give a rough idea. There are many aspects. I didn't mean a single, isolated aspect.

But perhaps this forum is also not the best place to talk about whether $language might be better than Rust in some $regard. I still find these questions being of interest, even if Rust is my most favourite language – by far. But we don't need to discuss this. I was just making some comments that I already regret. It isn't really that important.


P.S.: I forgot

  • runtime overhead

in my list :sweat_smile:. (But that's already covered by "speed", of course.)

1 Like

Well, if you optimize for that stuff you end up in a very different place from Rust. The thing that comes to mind is Forth (programming language) - Wikipedia

These are, arguably, mutually contradictory.

2 Likes

Isn't this addressed in 1.58 with the stabilization of -C strip ?
It's not the default, but at least you don't have to call another tool after build.

Yes, I noticed I can simply add the following to my Cargo.toml:

[profile.release]
strip = true

Boxing. Both Java and Javascript put all objects on heap. That adds significant “pointer chasing” overhead.

One can, of course, just allocate huge array of bytes in both languages and do all the required data structures on top of that (and yes, I've seen it done in Java), but at this point the very acute question raises it's head: do you still write Java if you give all the “nice” language constructs to achieve speed?

Yeah. That is not quite what I was getting at.

As far as I understand a human could read Javascript, say, and implement the same functionality in C, or Rust, or whatever. With out all that, everything on the heap business. Which ends up as a binary executable. Probably faster than the original Javascript.

I speculate then, that a sufficiently smart compiler could do the same.

If that were possible, it would support my statement that programming languages do not have a speed. It all depends on what you do with them.

My suggestion was that nobody wants to spend the time to build such a compiler, even if anyone knew how. Much easier just to create a new language, Rust say, that is more easily compiled to performant binaries.

So yes, you are of course correct, practically the language syntax/semantics do heavily influence a languages performance.

Nope. It couldn't. Because, lol. Java and JavaScript don't follow ownership-and-borrow model thus finding out lifetimes of objects is pretty big problem.

Java and JavaScript compilers can, sometimes, do it when object is local to a certain function (after inlining) but most non-trivial programs have objects which are non-local.

If that were possible then we wouldn't have been needed Rust with it's ownership-and-borrow approach. Any GC-based language program could have been converted to something like that.

Alas, that's not even remotely close to what compilers can do.

Nope. The issue is not with the compiler. Issue lies just with the fact that Rust can say to you “hey, you violated ownership-and-borrow rules… go and fix your code”… but Java or JavaScript compiler can not do it: if program is valid Java/JavaScript program then it must work… somehow.

5 Likes

In 40 years, when Rust's libstd is shipped with every OS like libc is, Rust will also generate 1KB-large hello world. Until then, you need to either build languages you compare with the same constraints, or avoid drawing too many conclusions on apples vs oranges (like comparing statically-linked exe with dynamically-linked, or comparing statically linked more complete and functional library with one that would require adding more code later).

You're trying to make a point about efficiency, but Hello World is a terrible benchmark for it, with results that depend on so many invisible factors, it's nothing but misleading. It's like trying to demonstrate which car is faster, but with a race track that is 1 meter long, and timings that include the time it takes to get into the car.

Even regarding dependencies, the picture is not simple. C dependencies tend to be big (because it's hard to use them, so when you need a small one, you reinvent or copy-paste instead). C doesn't have an easy tool like cargo tree, but it doesn't meant that your C dependencies don't have other C dependencies:

https://wiki.alopex.li/LetsBeRealAboutDependencies

18 Likes