
Lock freedom without garbage collection in Rust - aturon
http://aturon.github.io/blog/2015/08/27/epoch/
======
kbenson
Let's see, a TL;DR, then a table of contents list, then straight to
benchmarks...

This man knows how to get right down to business.

~~~
sitkack
Looks like the OP took [https://xkcd.com/773/](https://xkcd.com/773/) to
heart.

~~~
srean
This is gold. Just had to say it.

------
steveklabnik
I'm extremely excited about posts/libraries like these. They really show off
the unique powers of Rust in a way that's a bit more compelling than "Rust is
just C++14 with a bit stronger guarantees and more clean semantics."

Also worth noting, and something I missed when I read the draft of this post:
this isn't just an implementation of a lock-free structure: it's a whole
library, Crossbeam[1] that helps you implement your own lock-free structures.

    
    
      > In general, it’s possible to take lock-free algorithms “off the shelf” (the
      > ones on the shelf generally assume a GC) and code them up directly against
      > Crossbeam in this way.
    
    

1: [https://crates.io/crates/crossbeam](https://crates.io/crates/crossbeam)

~~~
eternalban
Is this based on his thesis?

~~~
brson
No, this isn't an implementation of Aaron's thesis (reagents [1]). It's an
implementation of Keir Fraser's thesis :) [2]

[1]: [http://www.mpi-sws.org/~turon/turon-thesis.pdf](http://www.mpi-
sws.org/~turon/turon-thesis.pdf)

[2]: [https://www.cl.cam.ac.uk/techreports/UCAM-CL-
TR-579.pdf](https://www.cl.cam.ac.uk/techreports/UCAM-CL-TR-579.pdf)

~~~
doomrobo
Wow, I am very impressed with the typesetting on [1]. Jealous, even...

~~~
bsder
Someone procrastinated a lot. :)

~~~
aturon
Not toooo much, but I will say that feeling inspired by the typesetting helped
keep my motivation up for all 299 pages...

~~~
kevin_thibedeau
Page 9 has a TL;DR footnote. Degree granted.

------
vvanders
Awesome stuff.

I'll echo that Rust really does seem to be something new and exciting in the
native land of languages.

I feel like it's still got some rough spots(around FFI and just some APIs
still being unstable) but there's so many things to like with the current
direction.

~~~
rhelmer
What do you feel is rough around FFI? Safer, zero-cost bindings to C libraries
- what's not to like? :)

~~~
vvanders
CString::from_ptr is unstable - [https://doc.rust-
lang.org/stable/std/ffi/struct.CString.html](https://doc.rust-
lang.org/stable/std/ffi/struct.CString.html)

clib is marked as unstable - [https://doc.rust-
lang.org/stable/libc/types/os/arch/c95/type...](https://doc.rust-
lang.org/stable/libc/types/os/arch/c95/type.c_char.html)

Most of the intrinsic types are usable but anything involving strings seems to
hit unstable/experimental branch pretty quickly.

I'm sure it'll improve I was just a bit surprised to not see it mentioned in
the docs.

~~~
steveklabnik
You should use libc from crates.io, which is stable. Not the compiler-internal
one you linked to.

~~~
vvanders
Ah, the docs around FFI say that libc is a part of the standard library so it
wasn't clear that I'd have to pull that from crates.io.

~~~
steveklabnik
Hmm, can you point me to where they say that so I can fix it? Seems bad.

(Also, the error message when you don't have a `#![feature(libc)]` should say
"hey, please use the external crate thanks")

~~~
vvanders
[https://doc.rust-lang.org/book/ffi.html](https://doc.rust-
lang.org/book/ffi.html) at the end of "Interoperability with foreign code"

Yeah, that error message sent me down the wrong path :).

~~~
steveklabnik
Ahh excellent, thank you! [https://github.com/rust-
lang/rust/issues/28053](https://github.com/rust-lang/rust/issues/28053)

~~~
vvanders
Nice!

Let me add another plus for Rust is the community around it.

------
unfamiliar
For each use of unsafe in Rust code, presumably the developer sees some safety
guarantee that the compiler does not understand but that he thinks will
guarantee that the code is indeed safe. Can anyone shed light on what form
these guarantees (that are not seen by the borrow checker) take, and whether
it will be possible in future Rust versions to write such code without unsafe
(either by improvements to the borrow checker or implementation of an
additional safety checker)?

~~~
kibwen
Usage of `unsafe` generally has to do with subverting the Rust compiler's
notion of ownership. Take a look at this snippet from the OP discussing the
usage of unsafe code in the client example:

    
    
      > The operation is unsafe because it is asserting that:
      >   * the Shared pointer is not reachable from the data structure,
      >   * no other thread will call unlinked on it.
    

This is only necessary because the library here is concerned with sharing a
data structure among multiple owners (the owners being threads in this case)
who all wish to mutate the data structure.

There are other places like this where one may wish to subvert the borrow
checker, and when such a pattern becomes widely observed it can be placed in
the standard library behind a safe interface, (ideally) removing the need for
unsafety in client code entirely. The `Rc` smart pointer in the stdlib is one
such example of a safely-exposed interface to code that internally uses
unsafety to subvert ownership in a specific way. And as long as you trust the
compiler to be correct, you can also trust that the "unsafe" code in the
stdlib is also correct (or at the very least that the Rust developers are
guaranteed to fix any incorrectness that is found).

~~~
unfamiliar
I understant those concepts. My question was more along the lines of, could
the code you mention from the article one day be written without unsafe?
Somehow the programmer is very confident that neither of the two restrictions
you mention will be broken. Is there any way that it can be proven from the
code that they will not be broken, and therefore remove the need for unsafe?
Or, if safety can not be proven from the code, how is the programmer so
certain of it?

------
rcthompson
Isn't there theoretically a risk that one thread could hang indefinitely while
accessing (or holding a reference to) the data structure, which would prevent
the epoch from ever advancing and prevent all memory from being reclaimed?

(On the other hand, I'm not sure what guarantees GC-based lock-free data
structures can make in the same situation.)

~~~
sanjoy_das
From the thesis the post linked to:

``` Although limbo lists are accessed using lock-free operations, and garbage
collection does not interfere with other mutator processes, this reclamation
scheme is not strictly lock-free. For example, a process which stalls for any
reason during a shared-memory operation will not observe updates to the epoch
count. In this situation the limbo lists will never be reclaimed and memory
cannot be reused. Other processes can make progress only until the application
reaches its memory limit. This drawback may also affect preemptively-scheduled
systems, in which a process may be descheduled in the middle of a shared-
memory operation with no guarantee when it will be rescheduled. ```

------
anarazel
The epoch based approach basically is on of the many variants of RCU (as e.g.
used by the Linux kernel - thoroughly documented on lwn.net BTW).

It does mean that a stuck or killed process can cause rather noticeable pain.
With a GC you usually have a upper bound of the amount of memory one stuck
process can prevent from being reclaimed, not so with a generation based
approach.

On the other hand it dies often voids concerns around ABA style problems.

~~~
anarazel
Oh, one more thing: often an approach based on hazard pointers allows to
reduce the impact of a stick or slowed down process, without using a GC. And
while stool protecting against ABA. Another good thing is that luckily the
patent application around it was rejected.

~~~
pcwalton
Hazard pointers aren't any better when it comes to stuck processes, right? If
you aren't removing your nodes from the hazard pointer list, you're stopping
reclamation just as with this algorithm.

The epoch algorithm strikes me as pretty much strictly better than hazard
pointers.

~~~
sbahra
HP is much more granular and a stuck thread does not inhibit system wide
progress (yes, the slots won't be reclaimed but other entries will continue to
be reclaimed).

------
ahh
Very nice. RCU like this can be very effective (we have a custom per-cpu
version based on
[https://lwn.net/Articles/650333/](https://lwn.net/Articles/650333/) here,
which scales better than per-thread versions for obvious reasons.) I'm glad to
see more wide use of the techniques.

~~~
haberman
Cool! Who is "we" and where can this code be found?

You say "RCU like this", but AIUI RCU and epoch-based collection like in this
article are quite different from each other.

RCU involves no-synchronization reads of a data structure and writers that
wait for a "quiescent" state to delete old nodes. It is used for data
structures that have pure readers (like a frequently read but infrequently-
updated list of things).

Epoch-based GC involves lock-free writes but puts garbage in freelists to be
collected at a later time. It is used for data structures that are mostly
based around writes (for a stack or queue, both push and pop are mutating
operations).

~~~
arielby
Could someone explain the big difference between RCU and epoch-based
reclamation? It seems that the only difference is that RCU has quiescent
periods between reschedules and epoch-based has them when you don't have a
Guard struct active. Seems like six of one, half a dozen of the other.

~~~
aturon
They are very similar; I think of the epoch scheme as a particular way of
doing RCU that does not impose the need to find quiescent states yourself (RCU
can be employed in userspace as well if your application can provide quiescent
states in some way).

This paper (linked from the blog post) was very helpful in comparing them:
[http://csng.cs.toronto.edu/publication_files/0000/0159/jpdc0...](http://csng.cs.toronto.edu/publication_files/0000/0159/jpdc07.pdf)

~~~
haberman
I think a key property of RCU as traditionally presented is that writers
always wait for global quiescence directly after their write, and then
immediately delete the garbage. This approach wouldn't make much sense for
data structures like stacks and queues where every operation is a write. I see
Epoch based GC as an approach that defers the deletion to make the write path
cheaper.

~~~
anarazel
> I think a key property of RCU as traditionally presented is that writers
> always wait for global quiescence directly after their write, and then
> immediately delete the garbage.

A rather common, in my opinion, way to use something like rcu is to delay
freeing (or reusing) memory to the next grace period, without blocking until
then. E.g. in the kernel you can use kfree_rcu(..); instead of
synchronize_rcu(); kfree(..); for that. That's basically what the epoch based
approach does with a the lists of to-be-freed allocations.

------
imh
I've been something wondering something for a while now. Rust is supposed to
be a kind of C/C++ replacement, right? But the last I heard it's still much
slower. So what's the roadmap to speeding it up? Googling for it failed me.

~~~
mbrubeck
In general it's possible to write Rust code that is competitive in speed with
C and C++. Some safety features like bounds checking do have a small
performance cost, though in typical performance-sensitive code it is on the
order of 1–2%. You can write unsafe code to avoid the penalty when really
necessary.

The programs submitted to the benchmark game currently show Rust faster than
g++ in some benchmarks, slower in others, but generally in the same ballpark.
Of course this changes as the compilers and the programs improve, and the
benchmark game isn't representative of anything in particular, but it
illustrates some of what's possible.

[http://benchmarksgame.alioth.debian.org/u64q/compare.php?lan...](http://benchmarksgame.alioth.debian.org/u64q/compare.php?lang=rust&lang2=gpp)

~~~
Veedrac
Rust has also yet to finalize SIMD, which will help a ton on some where Rust
is behind.

[http://huonw.github.io/blog/2015/08/simd-in-
rust/](http://huonw.github.io/blog/2015/08/simd-in-rust/)

------
merb
as browsing the api docs I kinda like lots of things from rust however, how is
the portability ie. target windows/linux/mac at once? is it easy to cross
compile? is it easy to include c/c++ header files and use them? kinda like to
link with nuance c sdk

~~~
kansface
To the best of my knowledge:

1\. Rust is working on cross compiling support (very hard atm).

2\. Rust can (pretty trivially) interface with C.

~~~
mastax
Some things I'd add:

1\. Cross compiling has worked in rust since before 1.0, but it's required
some manual configuration (downloading the platform's stdlib and adding
command line switches, etc.). A goal for the future is push-button cross
compilation [1]. I'd also add that the standard library has most of what you'd
want for cross platform compatibility, from higher-level interfaces that work
cross-platform (like std::thread [2]) to lower level bindings for each
platform like (std::os::unix::fs [3] or the OsStr system [4]).

2\. Yep, C interfaces are pretty trivial. However, they do need to be manually
defined. There is no built-in way to parse C header files or declarations. [5]

[1]: [http://blog.rust-lang.org/2015/08/14/Next-year.html](http://blog.rust-
lang.org/2015/08/14/Next-year.html)

[2]: [https://doc.rust-lang.org/nightly/std/thread/](https://doc.rust-
lang.org/nightly/std/thread/)

[3]: [https://doc.rust-lang.org/nightly/std/os/unix/fs/](https://doc.rust-
lang.org/nightly/std/os/unix/fs/)

[4]: [https://doc.rust-
lang.org/nightly/std/ffi/struct.OsStr.html](https://doc.rust-
lang.org/nightly/std/ffi/struct.OsStr.html)

[5]: [https://doc.rust-lang.org/nightly/book/ffi.html](https://doc.rust-
lang.org/nightly/book/ffi.html)

~~~
merb
what editor do you guys use? or what editor has the best rust support, yet? In
golang code was easily written via st2/st3

~~~
Gankro
I've submitted over a hundred patches to the standard library using only st3,
for reference.

~~~
merb
which plugins do you used?

~~~
Gankro
Google Spell Check, WordCount, and Wrap Plus for docs.

LLVM for IR highlighting, Rust for Rust highlighting.

I've tried racer in the past but it just doesn't scale with the current
compiler architecture. I'm hoping the ongoing refactoring work will get us a
great incremental/parallel/continuous compilation system for autocomplete/red-
squigglies.

My two biggest pains are: "trivial" errors (syntax, typos, etc) which I
ideally would just see as red squigglies as I go (not a compile-fail loop);
and the absolute lack of any kind of incremental compilation (which paired
with the community's over-use of generics is super painful for codegen time).

------
sbahra
Stand-alone EBR also available at
[http://concurrencykit.org](http://concurrencykit.org)

~~~
sbahra
Note that epoch reclamation is only competitive with amortization, otherwise
you may as well use hazard pointers for simple structures to benefit from
stronger progress guarantees. Of course, QSBR is best if you have quiescent
points.

------
klickverbot
Given that about every other line in the example stack implementation is
marked as "unsafe", I fail to see how Rust is any more safe or well-suited for
this than other low-level programming languages (C++, D, etc.).

~~~
dbaupp
I see 3 lines/expressions marked `unsafe` in
[http://aturon.github.io/blog/2015/08/27/epoch/#treiber%27s-s...](http://aturon.github.io/blog/2015/08/27/epoch/#treiber%27s-stack-
on-epochs) and a tweaked `unlinked` API could cut that down to 2, and removing
the unsafety from `cas_shared` (which doesn't _strictly_ need it, but is a
major contributor to any issues that could occur) would cut it to 1. Those
lines are exactly the danger-points of the data structure, i.e. where misuse
can cause use-after-free.

In any case, Rust really shines for the Owned and Shared types, where the
affine typing & lifetimes allows sharing to be controlled so that most
operations are perfectly (memory-)safe, with compile-time guarantees.

~~~
SamReidHughes
The number of individual lines marked "unsafe" isn't what's important. The
safety of those lines relies on invariants put in play by by the rest of the
code. _All_ the code behind an asserted-to-be-safe API has to be correct, or
incorrect but lucky, in order for the data structure to be safe.

~~~
kibwen
And yet Rust still provides tremendously more type safety in this application
thanks to the lifetime system statically verifying the accessibility of the
guarded data. It's true that unsafety is a stateful property than a local one,
but unsafety still has to involve the unsafe block _somehow_ and that's
enormously useful for auditing, debugging, and providing general assurance
towards code correctness.

------
littlewing
I tried Rust about a year ago, and it was pretty horrible. Not intuitive and
broken back then. Maybe it's better now, and benchmarks are great, but also
show me its community and how practical and easy it would be to use.

I like Mozilla and would like to see Rust succeed, but I just see adoption as
too risky right now. It's like when Go was the new-and-shiny; many jumped on
the hype bandwagon because it was Google backing it and it was fast, then
tried it out and realized it was a language for the <5% of teams that have
projects that fit well.

Performance is an awesome thing and there is definitely a place for Rust. But,
I don't see it taking the place of C, C++, or Java anytime soon for writing
high performance or portable apps. All the same, I look forward to using it in
a web browser!

~~~
pcwalton
> It's like when Go was the new-and-shiny; many jumped on the hype bandwagon
> because it was Google backing it and it was fast, then tried it out and
> realized it was a language for the <5% of teams that have projects that fit
> well.

I don't see that all with Go. Go is getting a good degree of traction, because
it's a really useful language for a lot of people.

> But, I don't see it taking the place of C, C++, or Java anytime soon for
> writing high performance or portable apps.

Why not, specifically?

~~~
littlewing
> I don't see that all with Go. Go is getting a good degree of traction,
> because it's a really useful language for a lot of people.

I agree that Go has the traction and community to stay around for the long
haul as a niche language.

It's not a replacement for C, and never will be. I can't imagine more than 5%
of developers ever using it.

Let's take Java which despite C# is still the number one language in the world
to use by number of developers and just compare Go to Java. That's more than
fair since the number of Java developers in the world is less than the total
number of developers in the world.

Given that, here's data to back my wild claims:

Java is in ~2.7% job postings on indeed.com currently:
[http://www.indeed.com/jobtrends?q=java&l=](http://www.indeed.com/jobtrends?q=java&l=)

Go(lang) is in ~0.0035% and the adoption curve has slowed:
[http://www.indeed.com/jobtrends?q=golang&l=](http://www.indeed.com/jobtrends?q=golang&l=)

Similarly, in GitHut which compares language use across GitHub, you can see
how the amount of Java code dwarfs the Go code currently shared:
[http://githut.info/](http://githut.info/)

I know, I know- Go still have a great growing community, but it just hasn't
taken off the way they would like you to believe. I still think it's a great
language, but it isn't making the inroads we'd all believe it has based on the
hype. That's not to say it isn't useful- it is, or that developers shouldn't
learn it or use it- they should. But, it isn't one of the top languages nor
will it be at the current rate of adoption.

> Why not, specifically?

That's a much bigger question, but the answer is: Go is not AngularJS.

What?!! You may ask. Here's what I mean:

In today's development world of every decent language and framework getting 15
minutes of fame, you have to have something really trendy and seemingly cost-
saving to get you there. Go is a practical language written for speed (similar
to Rust). That is not the Toyota Camry or Honda Civic of languages, that is
the high speed locomotive. A different use case, and tough to market.

~~~
kibwen
You don't deserve the downvotes, but I'm still not sure what your concern is.
The modern history of the programming languages ecosystem is marked by rapid
specialization and fragmentation, in which 5% market share is an _enormous_
quantity. Furthermore, relative percentage of mindshare isn't as interesting
as absolute numbers: your language needs only enough active contributors to
keep pace with the evolving trends of the industry, which can likely be
achieved with less than 20,000 active users (well less than 1% marketshare).

The point of any given programming language is not to be crowned queen of the
programming prom, it's to produce useful software and improve the state of
software development. As long as your language meets a minimum threshold for
notoriety (which both Go and Rust do), the fact that they have less
marketshare than Java is irrelevant unless your sole motivation for learning a
language is to get a job at an enterprise company (which is a fine reason to
learn a language, but I think you might be on the wrong forum if that's your
overriding concern :P ).

