Hillel Wayne's post dissects the concept of "nondeterminism" in computer science, arguing that it's often used ambiguously and encompasses five distinct meanings. These are: 1) Implementation-defined behavior, where the language standard allows for varied outcomes. 2) Unspecified behavior, similar to implementation-defined but offering even less predictability. 3) Error/undefined behavior, where anything could happen, often leading to crashes. 4) Heisenbugs, which are bugs whose behavior changes under observation (e.g., debugging). 5) True nondeterminism, exemplified by hardware randomness or concurrency races. The post emphasizes that these are fundamentally different concepts with distinct implications for programmers, and understanding these nuances is crucial for writing robust and predictable software.
Thread-local storage (TLS) in C++ can introduce significant performance overhead, even when unused. The author benchmarks various TLS access methods, demonstrating that even seemingly simple zero-initialized thread-local variables incur a cost, especially on Windows. This overhead stems from the runtime needing to manage per-thread data structures, including lazy initialization and destruction. While the performance impact might be negligible in many applications, it can become noticeable in highly concurrent, performance-sensitive scenarios, particularly with a large number of threads. The author explores techniques to mitigate this overhead, such as using compile-time initialization or avoiding TLS altogether if practical. By understanding the costs associated with TLS, developers can make informed decisions about its usage and optimize their multithreaded C++ applications for better performance.
The Hacker News comments discuss the surprising performance cost of thread-local storage (TLS) in C++, particularly its impact on seemingly unrelated code. Several commenters highlight the overhead introduced by the TLS lookups, even when the TLS variables aren't directly used in a particular code path. The most compelling comments delve into the underlying reasons for this, citing issues like increased register pressure due to the extra variables needing to be tracked, and the difficulty compilers have in optimizing around TLS access. Some point out that the benchmark's reliance on rdtsc
for timing might be flawed, while others offer alternative benchmarking strategies. The performance impact is acknowledged to be architecture-dependent, with some suggesting mitigations like using compile-time initialization or alternative threading models if TLS performance is critical. A few commenters also mention similar performance issues they've encountered with TLS in other languages, suggesting it's not a C++-specific problem.
The post argues that the term "thread contention" is misused in the context of Ruby's Global VM Lock (GVL). True thread contention involves multiple threads attempting to modify the same shared resource simultaneously. However, in Ruby with the GVL, only one thread can execute Ruby code at any given time. What appears as "contention" is actually just queuing: threads waiting their turn to acquire the GVL. The post emphasizes that understanding this distinction is crucial for profiling and optimizing Ruby applications. Instead of focusing on eliminating "contention," developers should concentrate on reducing the time threads hold the GVL, minimizing the queueing time and improving overall performance.
HN commenters generally agree with the author's premise that Ruby's "thread contention" is largely a misunderstanding of the GVL (Global VM Lock). Several pointed out that true contention can occur in Ruby, specifically around I/O operations and interactions with native extensions/C code that release the GVL. One commenter shared a detailed example of contention in a Rails app due to database connection pooling. Others highlighted that the article might undersell the performance impact of the GVL, particularly for CPU-bound tasks, where true parallelism is impossible. The real takeaway, according to the comments, is to understand the GVL's limitations and choose the right concurrency model (e.g., processes, async I/O) for the specific task, rather than blindly reaching for threads. Finally, a few commenters discussed the complexities of truly removing the GVL from Ruby, citing the challenges and potential breakage of existing code.
Summary of Comments ( 17 )
https://news.ycombinator.com/item?id=43107317
Hacker News users discussed various aspects of nondeterminism in the context of Hillel Wayne's article. Several commenters highlighted the distinction between predictable and unpredictable nondeterminism, with some arguing the author's categorization conflated the two. The importance of distinguishing between sources of nondeterminism, such as hardware, OS scheduling, and program logic, was emphasized. One commenter pointed out the difficulty in achieving true determinism even with seemingly simple programs due to factors like garbage collection and just-in-time compilation. The practical challenges of debugging nondeterministic systems were also mentioned, along with the value of tools that can help reproduce and analyze nondeterministic behavior. A few comments delved into specific types of nondeterminism, like data races and the nuances of concurrency, while others questioned the usefulness of the proposed categorization in practice.
The Hacker News post titled "Five Kinds of Nondeterminism" linking to an article on buttondown.com has generated several comments discussing various aspects of nondeterminism in computer systems.
Several commenters discuss the nuances and overlaps between the different categories of non-determinism outlined in the article. One commenter points out the difficulty in cleanly separating these categories in practice, arguing that many real-world systems exhibit characteristics of multiple types simultaneously. They use the example of a distributed database, which can have both implementation-defined (order of messages) and essential (concurrent user actions) non-determinism.
Another commenter focuses on the performance implications of non-determinism, specifically in the context of compiler optimizations. They suggest that eliminating certain kinds of non-determinism can allow for more aggressive optimizations and improved performance predictability.
The concept of "Heisenbugs" is brought up, with one commenter explaining how these elusive bugs are often a direct consequence of unintended non-determinism. They further link this to the increasing complexity of modern systems and the difficulty in controlling all sources of non-deterministic behavior.
One commenter delves into the philosophical implications of non-determinism, touching upon the free will vs. determinism debate. They propose that the classification of non-determinism in the article could be applied to this philosophical discussion, offering a new perspective on the nature of choice.
There's also a discussion about the role of testing and debugging in the presence of non-determinism. One commenter advocates for designing systems that minimize essential non-determinism, arguing that it simplifies testing and makes debugging easier. Another suggests techniques for reproducing and isolating non-deterministic bugs, emphasizing the importance of logging and careful analysis of system behavior.
A few commenters offer specific examples of non-determinism in different programming languages and systems, illustrating the practical relevance of the article's categorization. They mention issues related to thread scheduling, memory allocation, and network communication, providing concrete examples of how non-determinism manifests in real-world scenarios.
Finally, some commenters express appreciation for the article's clear explanation of a complex topic, finding the categorization helpful for understanding and addressing non-determinism in their own work. They also suggest potential extensions to the article, such as exploring the relationship between non-determinism and formal verification methods.