So while this may be unsurprising to those paying attention to Rust and memory safety, it's still relevant to a lot of software and a great confirmation of Rust's importance.
(I also don't really think Java is that much slower but substitute a more precise guess and my statement stands. And I think Java and other GCed languages really do use ~2X as much RAM which is also unacceptable.)
Using Java over C++ would have meant a factor of 2 up-front performance cost. But it would also have meant significantly less time debugging, easier testing, faster iteration, better automated refactoring support... all of which would have added up to being able to spend more development effort finding the kind of algorithmic improvements that give you those 1000x speedups. I'm not at all convinced that the end result would have been slower.
Over sufficiently long runs, throughput is easily within the factor of 2 you mentioned. But over short timescales, thousands of times slower is completely plausible. Some C++ CLI application might run in 5 ms where the equivalent Java program takes 5 seconds.
IIRC there was some article recently challenging the assumption that Java programs commonly reach the optimized steady state at all. I can't find it though.
[1] Except V8 of course on the user-supplied JavaScript. I'd call that quite different than optimizing all Chrome's own code.
In the context of something like Chrome that's already willing to implement a custom process model, I don't think that there are business requirements where that kind of large overhead is unavoidable. Running an individual unix-style JVM process can indeed perform very poorly on the default settings. But that's not the only way to build a web browser.
And the gap is getting _wider_ as Java is an increasingly bad fit for modern CPUs due to the heavy pointer chasing nature of it. It desperately needs value types to stay competitive in a performance battle.
Chromium started in 2008, at which point there were plenty of mature cross-platform high-performance memory-safe languages that would have been suitable (e.g. OCaml or Haskell).
A quick check of Wikipedia shows that they derived it from an existing small talk implementation.
Just the assembler files, for what it's worth:
https://web.archive.org/web/20100722105022/http://code.googl...
I will concede that a branch of GHC being utilized specifically for such a task could have been modified enough in the intervening years to enable a viable browser by 2020. But I really don’t know if it could be done with standard GHC.
Short answer: yes. Long answer: profiling and diagnosing performance issues is still kind of a black art, but for large codebases I've seen Haskell rewrites outperform C++ significantly. You need talented Haskell developers, but surely Google should be able to find those. Most of the tasks that I can think of in a web browser seem like things that Haskell is ideally suited to - parsing, data transformation, rule-based logic - what is it that makes you think it would be unsuitable?
From there, benchmarking could reveal if it is adequate for production use. Perhaps some parts could be rewritten in a lower level language.
What do you think Chromium developers have been doing for the last 10 years, sitting on their hands? My understanding is that the main reason why Google OSS-Fuzz exists is Chromium. The problem with this approach is that it evidently (see OP) doesn't work to find all the bugs before release.
For example, assume v8-ish semantics:
let mut cell = allocate(0.0);
let counter : &mut f64 = cell.as_mut_ref();
*counter += 1;
stuff();
*counter -= 1;
Here `stuff()` may trigger further allocations which result in moving `cell`, leaving `counter` dangling.Without any more specific knowledge, I bet the way a generational garbage collector moves data around really messes with Rust's view of the world...
There are also escape hatches, between unsafe leaking and the RefCell type, to provide “interior mutability”, i.e. mutable aliasing via an without the memory safety issues which come with this. Note I have only had to use the unsafe bits when passing memory up to a c stack.
I don’t believe there is a tracing GC implementation in the standard library, although I’ve heard plenty of discussion and you can find some simple implementations on crates.io. However, I would assume there would need to be some language changes to properly account for assumptions on how the Drop (free) functionality fires, not to mention significant backend work for tracing to work.
If you allocate something new, Rust pretends it is independent, assuming that malloc won't muck with the other objects it knows about.
What if we relax that assumption? Allocations may perturb other allocations - that's how moving GC heaps work! Can that be modeled usefully in Rust?
Rust has the capability to do things like execute load barriers on access, so it could still work when objects have been moved around.
&mut T means that there is only one pointer (the &mut T) that will be used to access some memory. You can have as many other pointers to that memory as you want, as long as you don't invalidate the first one.
This code is safe:
let mut foo = 13_i32;
let x: &mut i32 = &mut foo; // First &mut
{
let y: &mut i32 = unsafe { std::mem::transmute(x as *mut i32) }; // Second &mut T (copy)
*y = 42; // Write through second
}
dbg!(*x); // Read through first
You can run this under miri here: https://play.rust-lang.org/?version=stable&mode=release&edit...Getting shared mutability as a user of GC is basically the same as getting shared mutability in any other scenario in Rust. You use `UnsafeCell<T>` or a safe wrapper around it, like `Mutex` or `Cell`. You can get a feel for this even in idiomatic Rust using `Rc` instead of a tracing GC.
When you start looking at other methods of GC things get trickier. Even with `UnsafeCell<T>` the GC can't, in general, know whether something is borrowed (mutably or immutably) by the mutator, so the problem to be solved is finding ways to enforce that there are no references into the heap at all, when collection needs to happen.
But when you think about it, this is actually not so different from the usual problem of GC safepoints. The problem is the same- enforce that there are no un-rooted pointers into the heap, when collection needs to happen.
And it turns out there are some tricks you can do with lifetimes to get borrowck to enforce safepoints for you, and it could even be made pretty ergonomic in the future.
Some early discussion of the problem space:
* http://blog.pnkfx.org/blog/categories/gc/
* https://manishearth.github.io/blog/2015/09/01/designing-a-gc...
* https://manishearth.github.io/blog/2016/08/18/gc-support-in-...
A design from Servo, and a paper to go with it: https://github.com/asajeffrey/josephine
A series of blogposts describing a more recent attempt: https://boats.gitlab.io/blog/post/shifgrethor-i/
The most recent attempt I know of: https://github.com/kyren/gc-arena/
I think gc-arena is fairly similar to josephine- both treat the GC heap itself as a container-like object, requiring `&` or `&mut` access to dereference a GC pointer. Gc-arena also suggests that we could use generator yield points as GC safepoints- they have exactly the required lifetime properties. So you might (to borrow Python syntax) `yield from fn_that_allocates_gc_memory()`, with the compiler ensuring you don't hold any references into the heap across that call.
Both gc-arena and josephine appear to take the "unidirectional" approach, where there is no recursive re-entrance into the GC heap. For example gc-arena will not collect while the heap is being mutated, which is Rust-friendly but also impractical.
GC'd languages typically require reentrancy into managed code (managed -> native -> managed), and this has historically been the source of type confusion and other security vulnerabilities.
This is where the generator stuff I mentioned comes in- I think the approach is much more practical than it seems.
At the end of the day, any GC language needs to ensure there are no live un-rooted GC pointers at points where collection takes place. So any realistic Rust/GC integration will need to solve the same problem, if it is to retain memory safety.
And the best way (so far) to ensure all references into the heap have disappeared is to make sure they are all derived from a function parameter with an unconstrained lifetime, because then the mutator knows nothing except that it might go away when it returns. (This is "generative lifetimes" from the links.)
The real trick then is to make it look like the mutator is returning, without actually forcing it to exit. A yield point in a generator can do this, even without ever actually yielding. So you can manually insert collection points in the middle of a Rust mutator, and you can have a "managed -> native -> managed" stack where the native frame thinks its managed callee can yield across it, and borrowck will ensure anything derived from that reference parameter is gone.
Of course this is a long way off from Rust today, where generators aren't available on stable (though perhaps you could hack something together with async/await), but does look like it should eventually be possible to have reentrant heap access and memory safety across Rust and a GC language.
middle age is a time of strict typing.
old age is all dangling references and memory safety issues.
> That doesn't make any sense at all
I'm sure it's not true—70% is way too high—but the real number isn't 0% as you might expect.
In particular, (most? all?) non-Rust languages that claim or appear to be memory-safe can have data races. In Go for example, those can be exploitable. [1]
And Rust is memory-safe...in safe code, with a bug-free compiler. Real programs have some unsafe code in their transitive dependencies and are compiled with the real, buggy compiler. [2] The percentage of security bugs in Rust code that are due to memory safety problems is more than 0%.
[1] https://blog.stalkr.net/2015/04/golang-data-races-to-break-m...
[2] https://github.com/rust-lang/rust/issues?q=is%3Aopen+is%3Ais...
They all seem to misbehave on a single nightly build, affect only a very narrow and unsupported target group, be related to a bug in a unsupported release of LLVM, or simply aren’t reproducible. I think the percentage of security bugs in Rust related to memory safety, based off that list, is _effectively_ 0%. I can’t find an issue in the list that seems like it would impact Rust programs that people write today or on targets that people deploy Rust code where memory safety matters.
Still, it was cool that it was fixed so quickly.
https://blog.rust-lang.org/2020/02/27/Rust-1.41.1.html#a-sou...
I don't think soundness/security flaws due to compiler bugs are common, but they qualitatively can happen. I think that might be getting forgotten when folks are puzzled about the idea of memory safety bugs in memory-safe languages.
If I make an array of 3 integers that are not supposed to be used yet but I accidentaly access one of them, then that is a memory-safety bug because I accessed unallocated memory. The functions that languages provide to "allocate" memory don't define what allocating memory means. They just provide a tool that you can use to keep track of what memory you are using.
What memory is allocated or not is relative and there are multiple levels of allocation. If we zoom out a bit we can even call C a memory-safe language because all memory accesses in C must be to allocated memory and the ones that are not will kill the process (allocated here as in allocated to the process by the operating system).