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)something similar "-j N"
Re: The Rust debate.
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d
Re: The Rust debate.
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.
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 .
Re: The Rust debate.
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.
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 .
Re: The Rust debate.
Get a Pi4 4GB!
No need to mess with extra swap, for any sized compilation, even the monstrous build of the GCC compiler.
Re: The Rust debate.
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.
Re: The Rust debate.
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.
'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 .
Re: The Rust debate.
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.
Re: The Rust debate.
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):Heater wrote: ↑Wed Oct 02, 2019 2:28 amNo 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>
Yes it does. Like "Can we have such a test that is not completely absurd?"...but the test raises some questions.
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?
Re: The Rust debate.
jahboater,
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.
I have rarely had trouble installing Clang or GCC from source. Not sure which is easier really.
Nope. Checked at run time in debug and release builds: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).
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.
I'm sure there are overheads like that. Either pointer and length or start and end pointers.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.
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.Also a 25us run time is probably too fine for a reasonable benchmark.
That is not how it works in the normal case.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?
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.
I wasn't, was I?If you are talking about installing the latest GCC, I find it far far easier than Clang/LLVM.
I have rarely had trouble installing Clang or GCC from source. Not sure which is easier really.
Memory in C++ is a leaky abstraction .
Re: The Rust debate.
(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.jahboater wrote: ↑Wed Oct 02, 2019 11:13 amIf 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.
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
Code: Select all
./bcc bcc32 -c
Re: The Rust debate.
sal55,
Ingenious. I always like a small fast C compiler.
You should start as new thread on these things. Other may be interested.
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 .
Re: The Rust debate.
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
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 .
- John_Spikowski
- Posts: 1614
- Joined: Wed Apr 03, 2019 5:53 pm
- Location: Anacortes, WA USA
- Contact: Website Twitter
Re: The Rust debate.
I LIKE patten matching built into a language. RegEx is too cryptic for me to use.
Re: The Rust debate.
The array size and index are immediately obvious at compile time are they not? (Unless I have not understood the Rust!).Heater wrote: ↑Wed Oct 02, 2019 12:14 pmjahboater,Nope. Checked at run time in debug and release builds: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).Code: Select all
$ cat src/main.rs fn main() { let my_array = vec![0, 1, 2, 3, 4]; println!("{}", my_array[5]); }
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;
}
Last edited by jahboater on Wed Oct 02, 2019 5:07 pm, edited 3 times in total.
Re: The Rust debate.
Heater,
Otherwise, determining if a pointer is valid (other than non-NULL) is difficult without raising a seg fault!!!
OK yes, I wondered if it did that. Its the only practical solution.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.
Otherwise, determining if a pointer is valid (other than non-NULL) is difficult without raising a seg fault!!!
Re: The Rust debate.
jahboater,
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.
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.
Exactly.The array size and index are immediately obvious at compile time are they not?
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.
Not considerable. At least not in light of my musings above about all the branch prediction and parallel execution going on in modern processors:Reasonably, you would expect some considerable overhead to check that
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.
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.And GCC. Though I am not sure its in a released version yet.
Memory in C++ is a leaky abstraction .
Re: The Rust debate.
Out of curiosity I triedReasonably, you would expect some considerable overhead to check that.
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);
}
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
Re: The Rust debate.
In the GCC dev list. There was quite a lot of talk about it a few months ago. Obviously never completed.
Re: The Rust debate.
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).paddyg wrote: ↑Wed Oct 02, 2019 9:34 pmApproximation 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.Code: Select all
let mut my_array: Vec<u8> = args[1].bytes().collect(); my_array[args.len()] = 42;
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.
Re: The Rust debate.
Sal55,
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.
Perhaps Clang has something like -fverbose-asm that includes the original source code interspersed with the generated assembler.sal55 wrote: ↑Wed Oct 02, 2019 10:36 pmI'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).
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.
Re: The Rust debate.
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.
Godbolt is amazing.
Memory in C++ is a leaky abstraction .
Re: The Rust debate.
Isn't it just! Good find.Heater wrote: ↑Thu Oct 03, 2019 4:36 amIf 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.
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.
- John_Spikowski
- Posts: 1614
- Joined: Wed Apr 03, 2019 5:53 pm
- Location: Anacortes, WA USA
- Contact: Website Twitter
Re: The Rust debate.
Wise advice.Sorry, if raw speed and/or small code size are important for a project, I'll stick with C, C++ or Fortran.
Portability is also a trait of C I enjoy.