HN user

ibraheemdev

4,933 karma

Software developer interested in building fast, concurrent, and robust systems.

Contact ibraheem@ibraheem.ca.

Posts185
Comments504
View on HN
astral.sh 4mo ago

Astral to Join OpenAI

ibraheemdev
1489pts901
reiner.org 9mo ago

Cuckoo hashing improves SIMD hash tables (and other hash table tradeoffs)

ibraheemdev
79pts1
www.shopify.com 1y ago

David Heinemeier Hansson joins Shopify's board

ibraheemdev
12pts2
astral.sh 1y ago

Uv: Unified Python Packaging

ibraheemdev
49pts7
github.com 2y ago

A fast and ergonomic concurrent hash-table for read-heavy workloads

ibraheemdev
2pts0
sameerismail.org 2y ago

Knuth

ibraheemdev
2pts0
changelog.com 2y ago

Thinking outside the box of code with Leslie Lamport

ibraheemdev
1pts0
ibraheem.ca 2y ago

A Lock-Free Vector

ibraheemdev
3pts1
ibraheem.ca 3y ago

128-Bit Atomics Are Practical Now

ibraheemdev
1pts0
mfleck.cs.illinois.edu 3y ago

Building Blocks for Theoretical Computer Science (2017) [pdf]

ibraheemdev
80pts3
tip.golang.org 4y ago

The Go Memory Model

ibraheemdev
2pts0
tip.golang.org 4y ago

A Guide to the Go Garbage Collector

ibraheemdev
233pts21
twitter.com 4y ago

“people depend on third-party libraries too much”

ibraheemdev
1pts0
rocket.rs 4y ago

Rocket's 2nd v0.5 Release Candidate

ibraheemdev
2pts0
jackh726.github.io 4y ago

A Shiny Future with GATs

ibraheemdev
4pts0
www.ralfj.de 4y ago

Pointers Are Complicated III, or: Pointer-integer casts exposed

ibraheemdev
11pts1
seanmonstar.com 4y ago

Hyper 1.0 Roadmap

ibraheemdev
4pts0
github.com 4y ago

Rust Strict Provenance

ibraheemdev
2pts0
github.com 4y ago

Arkenfox: Firefox privacy, security and anti-tracking user.js template

ibraheemdev
77pts8
smallcultfollowing.com 4y ago

Dare to Ask for More: Rust 2024

ibraheemdev
2pts0
dada-lang.org 4y ago

Dada Lang

ibraheemdev
1pts0
blog.rust-lang.org 4y ago

Async Rust in 2022

ibraheemdev
4pts0
github.com 4y ago

SeqCst as a default atomic ordering considered harmful

ibraheemdev
2pts0
dave.cheney.net 4y ago

Why Go and Rust are not competitors (2015)

ibraheemdev
2pts0
www.ncameron.org 4y ago

Portable and Interoperable Async Rust

ibraheemdev
3pts0
www.ralfj.de 4y ago

Do we really need undefined behavior?

ibraheemdev
54pts39
blog.yoshuawuyts.com 4y ago

Async Cancellation

ibraheemdev
114pts40
smallcultfollowing.com 4y ago

Rustc Reading Club

ibraheemdev
2pts0
www.ncameron.org 4y ago

Rust RFC Index

ibraheemdev
1pts0
willcrichton.net 4y ago

A New Medium for Communicating Research on Programming Languages

ibraheemdev
3pts0

Not the runtime per se, but cooperative scheduling has the advantage that tasks do not yield at adverse code points, e.g., right before giving up a lock, or performing an I/O request. Of course the lack of preemption has it's own downsides, but with thread-per-request you tend to run into tail latency issues much earlier than context switching overhead.

OS threads are expensive: an operating system thread typically reserves a megabyte of stack space

Why is reserving a megabyte of stack space "expensive"?

and takes roughly a millisecond to create

I'm not sure where this number is from, it seems off by a few orders of magnitude. On Linux, thread creation is closer to 10 microseconds.

I did not claim that x86 provides sequential consistency in general, I made that claim only for RMW operations. Sequentially consistent stores are typically lowered to an XCHG instruction on x86 without an explicit barrier.

From the Intel SDM:

Synchronization mechanisms in multiple-processor systems may depend upon a strong memory-ordering model. Here, a program can use a locking instruction such as the XCHG instruction or the LOCK prefix to ensure that a read-modify-write operation on memory is carried out atomically. Locking operations typically operate like I/O operations in that they wait for all previous instructions to complete and for all buffered writes to drain to memory (see Section 8.1.2, “Bus Locking”).

Yes, what I meant was that the same instruction is generated by the compiler, regardless if the RMW operation is performed with relaxed or sequentially consistent ordering, because that instruction is strong enough in terms of hardware semantics to enforce C++'s definition of sequential consistency.

There is a pretty clear mapping in terms of C++ atomic operations to hardware instructions, and while the C++ memory model is not defined in terms of instruction reordering, that mapping is still useful to talk about performance. Sequential consistency is also a pretty broadly accepted concept outside of the C++ memory model, I think you're being a little too nitpicky on terminology.

ParkingLot just uses pthread mutex and cond.

That's interesting, I'm more familiar with the Rust parking-lot implementation, which uses futex on Linux [0].

Sure that uses futex under the hood, but the point is, you use futexes on Linux because that’s just what Linux gives you

It's a little more than that though, using a pthread_mutex or even thread.park() on the slow path is less efficient than using a futex directly. A futex lets you manage the atomic condition yourself, while generic parking utilities encode that state internally. A mutex implementation generally already has a built-in atomic condition with simpler state transitions for each thread in the queue, and so can avoid the additional overhead by making the futex call directly.

[0] https://github.com/Amanieu/parking_lot/blob/739d370a809878e4...

And futexes aren’t the only way to get there. Alternatives:

- thin locks (what JVMs use)

- ParkingLot (a futex-like primitive that works entirely in userland and doesn’t require that the OS have futexes)

Worth nothing that somewhere under the hood, any modern lock is going to be using a futex (if supported). futex is the most efficient way to park on Linux, so you even want to be using it on the slow path. Your language's thread.park() primitive is almost certainly using a futex.

The message has some weird mentions in (alloc565), but the actual useful information is there: a pointer is dangling.

The allocation ID is actually very useful for debugging. You can actually use the flags `-Zmiri-track-alloc-id=alloc565 -Zmiri-track-alloc-accesses` to track the allocation, deallocation, and any reads/writes to/from this location.

Every single Future you look at will look like this,

That's not true. A Future is supposed to schedule itself to be woken up again when it's ready. This Future schedules it to be woken immediately. Most runtimes, like Tokio, will put a Future that acts like this at the end of the run queue, so in practice it's not as egregious. However, it's unquestionably a spin lock, equivalent to back off with thread::yield.

It's quite common for concurrent algorithms to only implement a subset of operations. For example forgoing, removal or iteration. It's also common to put limitations on the data structure, such as limiting keys and values to 64-bits. Papaya being feature-complete means that it does not have any of these limitations when compared to std::collections::HashMap.

Looks very interesting, but seems to serve a pretty different use case:

This is an ordered data structure, and supports very high throughput iteration over lexicographically sorted ranges of values. If you are looking for simple point operation performance, you may find a better option among one of the many concurrent hashmap implementations that are floating around. Pay for what you actually use :)

Java atomics are actually sequentially consistent. C# relaxes this to acquire/release. Though the general concept of happens-before is still immensely useful for learning atomics as sequential consistency is a superset of acquire/release.

Yeah you're right regarding signal handlers (though I'm not sure the windows equivalent), and I was planning on adding a note about the alternative. The main thing I wanted to point out was that async/await as a model is built such that expressing complex control flow with arbitrary inputs becomes very simple. Nowadays thread-per-request will work well performance wise for most, but async/await became the default in various ecosystems because it's a more expressive model, especially useful in server-level code, which more or less forces it to be used by everyone. The advantage becomes apparent when you need to compose more complex logic within a system, which async makes seamless (though in Rust specifically things are harder because of the interaction between async and the borrow checker).

I'd instinctively worry about overhead there

Yeah, one of the nice things about blocking I/O is that you can perform it with a single syscall. With block_on(async_io), you're now dealing with registration with a reactor, polling epoll, and extra syscalls for each I/O operation. Not to mention the overhead of running the state-machine as opposed to line by line.

Rust 1.62.0 4 years ago

It uses the linux futex api directly, not a user space parking lot as used by the parking-lot crate.

- Noisy neighbor problems from other threads messing with your TLB and L1 cache

Switching between threads within the same process doesn't require a TLB or L1 cache flush. Not sure if you were implying this, just wanted to point that out.

- High cost of context switches

Userspace schedulers (like rust's tokio) do make context switching cheaper, however, most of the context switching in the case of a web server is due to blocking I/O and the most expensive part of the switch, entering the kernel, is already accounted for by the I/O request. Kernel context switching is unlikely to be your bottleneck.

Unpredictable scheduling/priority inversion in the scheduler

This can definitely be an issue at scale, but a general purpose async scheduler like most use is unlikely to be any better.

More precisely, OS threads, because of their scarcity, introduce an artificial bound on throughput that's lower than what the hardware can support, and usermode threads remove that bound.

Why are OS threads scarce? The OS allocates thread stacks lazily. Given a kernel stack of ~8kb (two pages) and a user stack of ~4kb, one could spawn 100k threads with just over 1GB. A userspace runtime will allow you to bring that number down, but if you're at the scale of concurrency it is unlikely to matter much.