User avatar
paddyg
Posts: 2464
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: The Rust debate.

Wed Oct 02, 2019 8:16 am

something similar "-j N"
there is codegen-units which I think does something similar. I've previously ignored these options but with the latest rustc on Raspberry Pi this seems to have to be set to 1 (not really noticed any difference in compile times)
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

Heater
Posts: 14474
Joined: Tue Jul 17, 2012 3:02 pm

Re: The Rust debate.

Wed Oct 02, 2019 8:30 am

My latest Rust effort will not compile on a Pi 3B+ in release mode unless I set "codegen-units = 1" in the project's Cargo.toml file.

Without it the build fails with a seg fault not sure why. The above is the suggested work around for now.

In the past I have had to use "j=1" when making large C++ programs on the Pi else it runs out of memory as it tries to compile too many things at the same time on all those cores.
Memory in C++ is a leaky abstraction .

Heater
Posts: 14474
Joined: Tue Jul 17, 2012 3:02 pm

Re: The Rust debate.

Wed Oct 02, 2019 9:36 am

So I was thinking....

Some here have complained that all that array bounds checking that Rust does is a performance hit. As much as I am prepared to accept that small performance hit as the price of extra correctness, memory safety and reliability it is a valid concern.

Somewhere on the YouTube there is a talk by Tony Hoare where he tells some history of his Algol compiler. He tells the story of how it was adapted to compile Fortran but the Fortran customers complained because their "perfectly good" Fortran code would not run when compiled with that compiler. Turns out all the checking that the Algol engine put in was halting with errors when it hit such bounds checks in the Fortran code. He tells how the Fortran guys basically said "yeah, yeah, never mind that, we just want our code to run like it does with other compilers, even if it is buggy"! I'm not sure if they gave in to that request or not.

And, certainly extra checking like that was a performance hit back in those days.

Today our processors have this amazing feature, branch prediction. That means they learn which way your branches normally go in hot loops and send their execution pipelines along that path until they find they made a wrong prediction.

That means that the tests and branches required for array bounds checking happen with essentially zero overhead.

The fft_bench program is of course dominated by array access. The fact that the Rust and C versions run at the same speed rather demonstrates the point.

Edit: I think this is the Tony Hoare presentation I was referring to: "Null References The Billion Dollar Mistake": https://www.youtube.com/watch?v=YYkOWzrO3xg

Which appropriately is about that other major source of bugs and security issues in C/C++ and other languages over the decades, the null pointer.
Memory in C++ is a leaky abstraction .

jahboater
Posts: 5074
Joined: Wed Feb 04, 2015 6:38 pm
Location: West Dorset

Re: The Rust debate.

Wed Oct 02, 2019 9:46 am

Heater wrote:
Wed Oct 02, 2019 8:30 am
In the past I have had to use "j=1" when making large C++ programs on the Pi else it runs out of memory as it tries to compile too many things at the same time on all those cores.
Get a Pi4 4GB!
No need to mess with extra swap, for any sized compilation, even the monstrous build of the GCC compiler.

jahboater
Posts: 5074
Joined: Wed Feb 04, 2015 6:38 pm
Location: West Dorset

Re: The Rust debate.

Wed Oct 02, 2019 9:49 am

Heater wrote:
Wed Oct 02, 2019 9:36 am
The fft_bench program is of course dominated by array access. The fact that the Rust and C versions run at the same speed rather demonstrates the point.
Perhaps its turned off by default like integer overflow checks, or perhaps the array dimensions and indexes are known, or deducible, at compile time (my guess).
If the memory was obtained by malloc(), then it would have to store the size (at least 8 bytes nowadays) alongside it for future checks.

Also a 25us run time is probably too fine for a reasonable benchmark.

NULL pointer de-referencing:
Again, if the compiler cannot know the provenance of a pointer, how can it determine that its valid, or even simply not NULL, without some extra code?

A pointer might be non-null and yet pointing outside the user program's address space, how does Rust check for that with zero cost?
Last edited by jahboater on Wed Oct 02, 2019 10:00 am, edited 1 time in total.

Heater
Posts: 14474
Joined: Tue Jul 17, 2012 3:02 pm

Re: The Rust debate.

Wed Oct 02, 2019 9:58 am

I'm working on it.

'er indoors won't allow such extravagance when I already have piles of Pis and other dev boards and electronica cluttering the house :(

However Heater Inc (Not the real name) is looking forward to buying a bunch of Pi 4 and accessories for a new project, the customer's lawyers have been a bit slow in finalizing the deal.

Said project is a major reason I'm getting into Rust just now. Not that I would not be anyway. They are rather demanding of reliabiltiy and security as well as requiring performance.
Memory in C++ is a leaky abstraction .

jahboater
Posts: 5074
Joined: Wed Feb 04, 2015 6:38 pm
Location: West Dorset

Re: The Rust debate.

Wed Oct 02, 2019 11:13 am

Heater wrote:
Wed Oct 02, 2019 9:58 am
Said project is a major reason I'm getting into Rust just now.
If you are talking about installing the latest GCC, I find it far far easier than Clang/LLVM.

On the Pi4, just start a simple script, wait for about 3 hours, and its all done. All the dependencies are automatically dealt with. And of course its optimized for your specific hardware.

sal55
Posts: 63
Joined: Sat Sep 21, 2019 7:15 pm

Re: The Rust debate.

Wed Oct 02, 2019 11:54 am

Heater wrote:
Wed Oct 02, 2019 2:28 am
No it does not. On my 7 year old Windows 10 PC I get the following when compiling 1024 lines of "a=a+c*d;":

Code: Select all

    Finished dev [unoptimized + debuginfo] target(s) in 0.80s
<snipped>
...but the test raises some questions.
Yes it does. Like "Can we have such a test that is not completely absurd?"
I made a long reply this morning but that seems to have disappeared (post removed or being moderated, or I forgot to press Submit?). But here are the highlights (and I will save the text this time just in case):

What is that cargo program doing; is it actually compiling the example, or deciding it doesn't need compiling? My figures were for a fresh installation of rust on a Raspberry Pi 4, running 32-bit Raspbian. That 1000-line test took 3 seconds using 'rustc test.rs', compared to 0.3 seconds for tcc to do 100,000 lines, that is the reality. gcc-O0 took 21 seconds for 100,000 lines. All figures for unoptimised code.

As for test being absurd, yes it is, but so what? It is just code which a compiler should be able to take in its stride. If it struggles with 1000 lines of a=a+c*d, what it's going to be like with real code which is more complex?

Replacing 'a=a+c*d' with 'println!("Hello World")' made it take 4 seconds. 250 lines/second, unoptimised code, on a 1.5GHz processor; now that is absurd. If I'm doing something wrong here, then I would be happy to acknowledge that.

As has been mentioned, generated code can look like this, and it can happen that the code can be in one function. My own tools, when they target C, generate a large single C source file (not in one function though). I expect a C compiler to cope with it.

Real example: https://raw.githubusercontent.com/sal55 ... rpi/qq32.c, which is a 43KLoc interpreter. On the RPi4, tcc compiles it in 0.25 seconds, and gcc-O0 in 7 seconds. What would rustc do?

Heater
Posts: 14474
Joined: Tue Jul 17, 2012 3:02 pm

Re: The Rust debate.

Wed Oct 02, 2019 12:14 pm

jahboater,
Perhaps its turned off by default like integer overflow checks, or perhaps the array dimensions and indexes are known, or deducible, at compile time (my guess).
Nope. Checked at run time in debug and release builds:

Code: Select all

$ cat src/main.rs
fn main() {
    let my_array = vec![0, 1, 2, 3, 4];
    println!("{}", my_array[5]);
}

pi@aalto-1:~/junk $ cargo run
   Compiling junk v0.1.0 (/home/pi/junk)
    Finished dev [unoptimized + debuginfo] target(s) in 2.77s
     Running `target/debug/junk`
thread 'main' panicked at 'index out of bounds: the len is 5 but the index is 5', /rustc/625451e376bb2e5283fc4741caa0a3e8a2ca4d54/src/libcore/slice/mod.rs:2715:10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
pi@aalto-1:~/junk $ cargo run --release
   Compiling junk v0.1.0 (/home/pi/junk)
    Finished release [optimized] target(s) in 2.13s
     Running `target/release/junk`
thread 'main' panicked at 'index out of bounds: the len is 5 but the index is 5', /rustc/625451e376bb2e5283fc4741caa0a3e8a2ca4d54/src/libcore/slice/mod.rs:2715:10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
If the memory was obtained by malloc(), then it would have to store the size (at least 8 bytes nowadays) alongside it for future checks.
I'm sure there are overheads like that. Either pointer and length or start and end pointers.
Also a 25us run time is probably too fine for a reasonable benchmark.
Ironically because that run time is so short, and because the program is timing itself (no load/clean up time included) it turns out to be a very accurate timing. If you run it thousands of times, which I have, and mostly get the same time within a couple of uS then there is no time for the OS to have barged in in those cases and they are correct.
NULL pointer de-referencing:
Again, if the compiler cannot know the provenance of a pointer, how can it determine that its valid, or even simply not NULL, without some extra code?

A pointer might be non-null and yet pointing outside the user program's address space, how does Rust check for that with zero cost?
That is not how it works in the normal case.

The compiler does know the provenance of the pointers it creates. It can trace the ownership of those pointers through your source at compile time. It can therefore tell from your source that the pointer is never null or pointing at something invalid.

Of course at run time there is a malloc or whatever going on under the hood, which will return error indications which Rust will check. That is not a big thing for Rust to be doing automatically and is something you will be doing manually in C/C++ anyway.

Only when you use "unsafe" can you use pointers of unknown provenance. Like when interfacing to C libraries or hardware registers Then it is up to the programmer not to use them wrongly of course.
If you are talking about installing the latest GCC, I find it far far easier than Clang/LLVM.
I wasn't, was I?

I have rarely had trouble installing Clang or GCC from source. Not sure which is easier really.
Memory in C++ is a leaky abstraction .

sal55
Posts: 63
Joined: Sat Sep 21, 2019 7:15 pm

Re: The Rust debate.

Wed Oct 02, 2019 12:59 pm

jahboater wrote:
Wed Oct 02, 2019 11:13 am
Heater wrote:
Wed Oct 02, 2019 9:58 am
Said project is a major reason I'm getting into Rust just now.
If you are talking about installing the latest GCC, I find it far far easier than Clang/LLVM.

On the Pi4, just start a simple script, wait for about 3 hours, and its all done. All the dependencies are automatically dealt with. And of course its optimized for your specific hardware.
(I've heard stories of Clang taking 30 hours to build on a 'Rock64' (competitor to RPi), although that might have been a 'debug' build.

As a contrast, here is my own (Windows) C compiler (a one-file self-contained C rendering): https://raw.githubusercontent.com/sal55 ... pi/bcc32.c. You build it on RPi4 using:

Code: Select all

tcc bcc32.c -obcc -lm -ldl
which takes 0.19 seconds. However, if you then try the result on itself (note this generates code for Windows on x64 only, so can only use -s or -c options on RPi):

Code: Select all

./bcc bcc32 -c
it takes 0.65 seconds. Using gcc-O3 will make it faster, but that takes 40 seconds. The result will be double the speed however.)

Heater
Posts: 14474
Joined: Tue Jul 17, 2012 3:02 pm

Re: The Rust debate.

Wed Oct 02, 2019 1:21 pm

sal55,

Ingenious. I always like a small fast C compiler.

You should start as new thread on these things. Other may be interested.
Memory in C++ is a leaky abstraction .

Heater
Posts: 14474
Joined: Tue Jul 17, 2012 3:02 pm

Re: The Rust debate.

Wed Oct 02, 2019 2:19 pm

STOP PRESS: Breaking news!

KEEP CALM AND CARRY ON C++.

Turns out that there is no need for Rust anymore. In it's relentless quest to absorb every feature of every language ever created C++ is now getting some features from Rust. I thought C++ had jumped the shark with the introduction of lambdas and closures but now there is more:

1) As of Clang 10 and new MSCVCC there is object lifetime analysis in C++.

See this presentation from CppCon published yesterday: Gábor Horváth, Matthias Gehre “Lifetime analysis for everyone”: https://www.youtube.com/watch?v=d67kfSnhbpA

That is to say the idea of "ownership" of data and static analysis, at compile time, of how it is created, mutated and disposed of and referenced. Somewhere in there is analysis intended to find use of null pointers and such. As far as I can tell this requires adding lifetime attribute annotations to objects. It is not intended to be 100% bullet proof. I have no idea what annotations in C++ are. Perhaps a master of modern C++ could try this out and report back.

2) On the table is a pattern matching syntax for C++. Pattern matching is that syntax you find in languages like Haskell and present in Rust's match constructs.

See: CppCon 2019: Michael Park “Pattern Matching: A Sneak Peek”: https://www.youtube.com/watch?v=PBZBG4nZXhk
Memory in C++ is a leaky abstraction .

User avatar
John_Spikowski
Posts: 1614
Joined: Wed Apr 03, 2019 5:53 pm
Location: Anacortes, WA USA
Contact: Website Twitter

Re: The Rust debate.

Wed Oct 02, 2019 4:03 pm

I LIKE patten matching built into a language. RegEx is too cryptic for me to use.

jahboater
Posts: 5074
Joined: Wed Feb 04, 2015 6:38 pm
Location: West Dorset

Re: The Rust debate.

Wed Oct 02, 2019 4:46 pm

Heater wrote:
Wed Oct 02, 2019 12:14 pm
jahboater,
Perhaps its turned off by default like integer overflow checks, or perhaps the array dimensions and indexes are known, or deducible, at compile time (my guess).
Nope. Checked at run time in debug and release builds:

Code: Select all

$ cat src/main.rs
fn main() {
    let my_array = vec![0, 1, 2, 3, 4];
    println!("{}", my_array[5]);
}
The array size and index are immediately obvious at compile time are they not? (Unless I have not understood the Rust!).
So yes there will be no run-time overhead.

What about the Rust equivalent of:

Code: Select all

int
main( int argc, const char *argv[] )
{
   char * my_array = malloc( atoi( argv[1] ) );
   my_array[argc] = 42;  
}
Reasonably, you would expect some considerable overhead to check that.
Last edited by jahboater on Wed Oct 02, 2019 5:07 pm, edited 3 times in total.

jahboater
Posts: 5074
Joined: Wed Feb 04, 2015 6:38 pm
Location: West Dorset

Re: The Rust debate.

Wed Oct 02, 2019 4:53 pm

Heater wrote:
Wed Oct 02, 2019 2:19 pm
1) As of Clang 10 and new MSCVCC there is object lifetime analysis in C++.
And GCC. Though I am not sure its in a released version yet.

jahboater
Posts: 5074
Joined: Wed Feb 04, 2015 6:38 pm
Location: West Dorset

Re: The Rust debate.

Wed Oct 02, 2019 4:56 pm

Heater,
The compiler does know the provenance of the pointers it creates. It can trace the ownership of those pointers through your source at compile time. It can therefore tell from your source that the pointer is never null or pointing at something invalid.

Of course at run time there is a malloc or whatever going on under the hood, which will return error indications which Rust will check. That is not a big thing for Rust to be doing automatically and is something you will be doing manually in C/C++ anyway.

Only when you use "unsafe" can you use pointers of unknown provenance. Like when interfacing to C libraries or hardware registers Then it is up to the programmer not to use them wrongly of course.
OK yes, I wondered if it did that. Its the only practical solution.
Otherwise, determining if a pointer is valid (other than non-NULL) is difficult without raising a seg fault!!!

Heater
Posts: 14474
Joined: Tue Jul 17, 2012 3:02 pm

Re: The Rust debate.

Wed Oct 02, 2019 5:45 pm

jahboater,
The array size and index are immediately obvious at compile time are they not?
Exactly.

However Rust does not complain about it at compile time. It fails at run time.

From which I conclude all the array bounds checking is done at run time.
Reasonably, you would expect some considerable overhead to check that
Not considerable. At least not in light of my musings above about all the branch prediction and parallel execution going on in modern processors:
https://www.raspberrypi.org/forums/view ... 0#p1545728

Certainly the fact that the C and Rust fft_bench run at the same speed indicates the performance hit of array bounds checking is lost in the noise.

Overflow checking however does slow the fft_bench down by 10 percent or so when enabled in release builds.
And GCC. Though I am not sure its in a released version yet.
Where did you read that? The guys in the presentation I linked to said it was not in GCC but they would be happy to help with the GCC devs if they want to adopt the object lifetime analysis code. That presentation was only days ago.
Memory in C++ is a leaky abstraction .

User avatar
paddyg
Posts: 2464
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: The Rust debate.

Wed Oct 02, 2019 9:34 pm

Reasonably, you would expect some considerable overhead to check that.
Out of curiosity I tried

Code: Select all

use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    let mut my_array: Vec<u8> = args[1].bytes().collect();
    my_array[args.len()] = 42;
    //println!("{:?}", my_array);
}
Approximation for your example, to see how the bounds checking is done. But talk about needles in haystacks! I've put the results online if you want to look at all 37,000 lines of disassembled code. The executable file (and stripped version) are there as well if you have a better way of viewing them unstripped, unstripped_dump, stripped and stripped_dump

PS I just overwrote an existing rust file sudoko/main.rs which is why that crops up. There is a movb 0x2a around line 312 but how the bounds checking happens is not so clear (it does check and gives a relevant panic message if too short a string is entered).
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

jahboater
Posts: 5074
Joined: Wed Feb 04, 2015 6:38 pm
Location: West Dorset

Re: The Rust debate.

Wed Oct 02, 2019 10:24 pm

paddyg wrote:
Wed Oct 02, 2019 9:34 pm
But talk about needles in haystacks! I've put the results online if you want to look at all 37,000 lines of disassembled code.
37,000 lines !!!!!! Eek.
For the C version, GCC removed everything and just did the return zero from main (2 instructions).

jahboater
Posts: 5074
Joined: Wed Feb 04, 2015 6:38 pm
Location: West Dorset

Re: The Rust debate.

Wed Oct 02, 2019 10:31 pm

Heater wrote:
Wed Oct 02, 2019 5:45 pm
Where did you read that? The guys in the presentation I linked to said it was not in GCC but they would be happy to help with the GCC devs if they want to adopt the object lifetime analysis code. That presentation was only days ago.
In the GCC dev list. There was quite a lot of talk about it a few months ago. Obviously never completed.

sal55
Posts: 63
Joined: Sat Sep 21, 2019 7:15 pm

Re: The Rust debate.

Wed Oct 02, 2019 10:36 pm

paddyg wrote:
Wed Oct 02, 2019 9:34 pm

Code: Select all

let mut my_array: Vec<u8> = args[1].bytes().collect();
  my_array[args.len()] = 42;
Approximation for your example, to see how the bounds checking is done. But talk about needles in haystacks! I've put the results online if you want to look at all 37,000 lines of disassembled code.
I've had the same problems. Then I might put special markers in the code that will hopefully be easy to find, eg. an assignment involving the constant 123456 or 0x123456 (or both). In the above example, perhaps change the 42 to a bigger magic number (may need to change the array element type).

But often it's a battle trying to outwit the compiler optimiser in order to ensure code is kept in that the compiler deems is unnecessary.

jahboater
Posts: 5074
Joined: Wed Feb 04, 2015 6:38 pm
Location: West Dorset

Re: The Rust debate.

Wed Oct 02, 2019 10:43 pm

Sal55,
sal55 wrote:
Wed Oct 02, 2019 10:36 pm
I've had the same problems. Then I might put special markers in the code that will hopefully be easy to find, eg. an assignment involving the constant 123456 or 0x123456 (or both). In the above example, perhaps change the 42 to a bigger magic number (may need to change the array element type).
Perhaps Clang has something like -fverbose-asm that includes the original source code interspersed with the generated assembler.

For GCC it works surprisingly well, despite optimization moving things around.
I have got used to just searching for the source code line when looking for the right place in the assembler output.

Heater
Posts: 14474
Joined: Tue Jul 17, 2012 3:02 pm

Re: The Rust debate.

Thu Oct 03, 2019 4:36 am

If you drop that code into the Godbolt Compiler Explorer, here: https://godbolt.org/z/8PJ72Q it shows all the generated assembler. If you click on the source line you are interested in and hit Cnt-F10 it takes you to the appropriate assembler lines, nicely highlighted in blue.

Godbolt is amazing.
Memory in C++ is a leaky abstraction .

jahboater
Posts: 5074
Joined: Wed Feb 04, 2015 6:38 pm
Location: West Dorset

Re: The Rust debate.

Thu Oct 03, 2019 8:28 am

Heater wrote:
Thu Oct 03, 2019 4:36 am
If you drop that code into the Godbolt Compiler Explorer, here: https://godbolt.org/z/8PJ72Q it shows all the generated assembler. If you click on the source line you are interested in and hit Cnt-F10 it takes you to the appropriate assembler lines, nicely highlighted in blue.

Godbolt is amazing.
Isn't it just! Good find.

That Rust code is all very bloated. You can see all the intrinsic's like "is_aligned_and_not_null" to check pointers which seem to be far bigger than they should be with many jumps and function calls. Worse, they are implemented as functions. Even the simple "non_null" check functions (there are several of them) involve two function calls! (I would have expected a simple test/jmp pair to precede a pointer de-reference - inline).

The Control-10 jump on "my_array[args.len()] = 42;" takes you to a large amount of unreadable code.

See this in Godbolt which I think is the equivalent C:
https://godbolt.org/z/__rJ4H
Control-F10 on the "my_array[argc] = 42;" line takes you directly to:-

mov BYTE PTR [rax+rbx], 42

On line 20 just after the "call malloc"

Sorry, if raw speed and/or small code size are important for a project, I'll stick with C, C++ or Fortran.

User avatar
John_Spikowski
Posts: 1614
Joined: Wed Apr 03, 2019 5:53 pm
Location: Anacortes, WA USA
Contact: Website Twitter

Re: The Rust debate.

Thu Oct 03, 2019 8:57 am

Sorry, if raw speed and/or small code size are important for a project, I'll stick with C, C++ or Fortran.
Wise advice.

Portability is also a trait of C I enjoy.

Return to “Other programming languages”