HNHacker News
TopNewBestAskShowJobs

jstimpfle

4,224 karma · joined May 21, 2015

http://jstimpfle.de
submissionscomments
jstimpfle··on Orthodox C++ (2016)
Orthodox C++, to me, is C plus the one good feature of C++: you don't have to type struct all the time.
jstimpfle··on Queues Don't Fix Overload (2014)
This book is one of the few books I own and I couldn't agree more with these reviews. I read maybe 20 to 50 pages of it, and didn't and up with a lot of practically relevant insights. I couldn't say it's a bad book, maybe it's even a good one.

But theory about computer science is always waaaay to removed from practical reality. Only a tiny bit of basic theory is applicable in reality, and from then on, in practice we're just busy fighting with practical problems, we're hardly getting to the theoretic ones.

jstimpfle··on Queues Don't Fix Overload (2014)
I'd like to note that the only latency that gets increased is latency of the backpressure "signal". Shorter queues can never result in items being processed earlier, I don't think so. It will only increase the time they have to wait until they can "enter the system". Or more of those items will be rejected ("lost event").
jstimpfle··on Using Git's rerere feature to escape recurring conflict hell
Rebasing all the time will most likely result in intermediate commits that don't build anymore (because who builds all the intermediate commits after a rebase), and which don't make sense anymore as a "history" of changes.

So my simple resolution has become: don't rebase anything beyond trivial. I use rebase -i to merge, delete, or reorder individual commits on an insolated feature branch. Anything else? Not worth the effort in my opinion. Just merge, in any direction. I even merge between feature branches sometimes, though one must be aware that this essentially glues the two feature branches, meaning either both or none can get merged to master.

Something worth stressing: The most important commit is always the one HEAD points to. Older commits don't matter so much to the degree that cleaning them up later is wasted time. The most value of git commit graph comes as a support structure for git merge. In most cases, nobody gives a shit that all intermediate commits are perfect for some definition of "perfect". Most commits will never be read again.

jstimpfle··on Restartable Sequences
I think it wasn't explained in a very accessible way. If I got the gist right, this essentially brings "per-CPU" synchronization to userland. It's typical in the kernel to have per-cpu data, while per-thread data is rare and typically impractical. There is a high number of threads managed by the kernel, most of which probably belong to a userland process, most of which do not participate in any given synchronisation scheme. Also threads are often too much of an abstraction for parallel programming needs, given that they are hiding for example cache effects. So it's natural to want to use per-cpu data instead of thread_local data in a userland process, I know I've been wishing for that many times.

With rseq, we can allocate in any userland process one instance of a given synchronisation data structure per each CPU. It's important to understand that userland code accessing per-cpu data structures cannot prevent being scheduled away from a CPU and being replaced by another thread (kernel code can block scheduler for short critical sections). Such a replacement thread may subsequently corrupt that same data that was still in the middle of the transaction. But we can make a subset of transactions safe at least: If a transaction gets committed in a single (final) atomic instruction, and we get kernel support for this transaction to be restarted in case there has been a schedule mid-way, this is a guarantee that at the time of commit, the entire transaction hasn't been interrupted by the scheduler. I.e. a kind of "mutual exclusion" guarantee.

Did I get that right?

jstimpfle··on Zig ELF Linker Improvements Devlog
What is the compile and link perfomance that you get then? Numbers on table... I know that even with MSVC, single file rebuild+link of C code at about 100 KLOC/second should be well possible.

You keep posting about obscure products like whatever UML Editor that noone cares about, why would I listen to what they say, and why do you not even link anything to look up?

jstimpfle··on Performance of Rust Language [pdf]
Congratulations, empty marketing speech, not reacting to what I say.
jstimpfle··on Performance of Rust Language [pdf]
Why are you unable to get my point? I understand that GCC doesn't compile with plain C compiler anymore. A lot of my own code doesn't!

I'm saying that most of features like templates, constexpr, reflection etc. don't scale well to serious use, as a broad statement. I fully acknowledge this is not a black and white situation. But I encourage you to look at actual pedestrian code, it's mostly not abstracted fluffy magic template code at all. It's pushing individual bytes with totally basic means (mostly C code). Why? Because code using these fluffy features is terribly hard to maintain. Templates lock you in their own language world with incredibly bad syntax and bad ergonomics, in short: it's a pain!

Personally I think even C++ classes (i.e. 1980's C++) are unusable because they bifurcate syntax/semantics needlessly and add implicit invisible scope. But I acknowledge it's somewhat possible to program with classes, and some people like to lean on RAII heavily. I mostly do not like to use RAII, and I've tried many times, I think it sucks for non-toy programming, even though obviously the idea is intuitive.

jstimpfle··on Performance of Rust Language [pdf]
Actually care to open GCC and see what I mean? Check the newest commits and see what they do. Maybe you're living in a dream world where some magic language features do the work for you. Meanwhile people out in the field do actual work by just pushing bytes at the low level.
jstimpfle··on Performance of Rust Language [pdf]
You must be talking about Linux, the BSDs, sqlite, postgres, gcc, the mold linker, or let's take some new kids on the block: raddebugger, FilePilot, TaskSlinger?
jstimpfle··on Performance of Rust Language [pdf]
I said, I avoid, I don't love, I was talking about preference. And I'll state: Most of these are written mostly like I say. Please find serious counter-examples.
jstimpfle··on Performance of Rust Language [pdf]
I think they're all ideas that are relatively obvious intuitive responses to the problem, and yet they may only incrase complexity tbh. For example, constexpr can be used relatively independent of template programming even, yet where they can be used practically before it becomes an unmaintainable mess of boilerplate are the most trivial cases, almost those which you could have hacked in with macros. TBF I think if you need serious metaprogramming, just compile and run a program at compile time.

Reflection has always been a mess no matter which implementation or language I've used. Fine for scripting languages, unusable for anything serious complex. The data you need is never there, and the data that is there is unusable, at the wrong semantic level (programming language level not what actually your own domain model semantics).

Also I avoid templates for the same reason, they're quickly becoming unmaintainable. Yes, I've tried to make use of them many times, and I have a fair number of them in deployed software. They work without bugs, of course. But I still don't love them, they're boilerplatey hard to maintain complexity that would be better solved with the right factoring plus a tiny bit of ad-hoc boilerplate. I would like to remove many of my deployed templates if I had the time.

And yes, I even avoid std:: template containers and such. Most uses I regret later. Again, this is for systems programming. They're fine for "scripting", leetcode, business software.

jstimpfle··on C constructs that still don't work in C++
Systems programming is not primarily about programming languages though...
jstimpfle··on C extensions, portability, and alternative compilers
Wasn't aware of preprocessor conformity issues, good to know!
jstimpfle··on C extensions, portability, and alternative compilers
What do you even miss, honestly works fine for me? In terms of platform APIs, I prefer the Windows ones on Windows anyway
jstimpfle··on C constructs that still don't work in C++
Most of these methods and ideas turned out to not work, neither in messy C libraries, nor in C++ or other languages.
jstimpfle··on C constructs that still don't work in C++
such a strawman again... You don't want to be writing explicit platform specific SIMD most of the time. You just want to write a dumb function that doesn't do any non-obvious calls, doesn't cause thread contention, doesn't hide complexity, isn't going to be a nightmare to change later, no surprises.

I am talking about self-inflicted complexity that is entirely within the C(++) machine model. Avoid that complexity and you're pretty good already. Only drop down to concrete hardware arch level where it makes sense. But largely, the C machine model is still very much suited as a model for actual hardware. Writing straightforward obvious code allows you to stay in control of memory layout and the data transformation paths. It easily gets you within <<2x of what you could achieve with hand coded assembler for the >90% of the code that are pretty boring and straightforward. And obviously you couldn't get the work done in time when coding everything in assembler.

jstimpfle··on C constructs that still don't work in C++
It is the case that you can more easily know what happens when you don't use the wrong abstractions but stay in control. Highly-abstracted C++ code basically makes allocations and syscalls in the whitespace between the source code tokens. You can't do systems software like that, you have to roll back the abstractions and roll back the use of pre-canned containers and libraries that you don't understand.

So it's all about understanding and control, not about some idea that C was defined in terms of assembly instructions, which it obviously is not. That's a total strawman.

jstimpfle··on C constructs that still don't work in C++
Caring for the actual assembler output in selected critical pieces of code is not the same as ignoring the abstract machine model. What you claim is simply not the case if you check actual proficient systems programmers. Of which there are an astonishingly high share C and C++-but-mostly-C programmers.
jstimpfle··on Everything in C is undefined behavior
Have you ever wanted to do this? I find the premise ridiculous.

But anyway, you're complaining that you have to work too hard to do unaligned loads (i.e. the wrong thing even if it should work on a particular machine) in C, when basically every other language makes you work more for basic systems programming tasks?

Whether unaligned loads can work on the machine level, it depends on the hardware. On some other architectures, you probably get anything from traps to unpredictable behaviour. It's totally fine that C does not define the behaviour for unaligned loads.

If you want to do some weird stuff like loading a single unaligned 16 byte quantity, where there was no "middle part" to begin with, just do memcpy then. The compiler might just do the appropriate thing on this architecture. Or if you need to closely control what's happened, write assembly then. But again, why would you even do this?

jstimpfle··on Everything in C is undefined behavior
I understand that. It makes no practical difference. 99,99% of my additions don't rely on signed overflow for example, and if I'd ever need it there are ways to get just it.

Or tell me how you write a Rust program differently given that signed overflow is apparently defined? I bet you write it exactly the same way, and you get pretty much the same behaviour in practice. And we're even only debating actual overflow situations, meaning there is a bug whatever the compiled behaviour is.

C the language doesn't even guarantee that the machine has native integers with 8, 16, 32, 64 bits etc, that a cacheline is 64 bits, that a page is 4K, and here I am, writing programs for exactly that.

jstimpfle··on Everything in C is undefined behavior
I think people make up way too much of it. What is the actual term of art? What is the meaning of UB? If you look in the standard, UB is basically what its name says, it is behaviour (or state) that is not defined. It can be anything. And that makes sense in many cases: What if you construct a random pointer, and read it or write it? It's not useful or practically possible to define the behaviour from then on. So the behaviour is left undefined, simple as that.

Now are there many cases of UB in C, many more than strictly need to exist on contemporary platforms? For sure there are. But does it affect me? Not unless I need a specific behaviour common to most contemporary platforms that I can't get within the confines of C, even considering compiler specific extensions. Honestly I can't come up with any of the top of my head. Maybe some integer-shifting stuff or such, if the compiler was able to prove I'm doing sth undefined, it can leave out that code (or delete my mail, for the doomers). Personally, it hasn't happened to me, and it's on the compiler authors to not do stupid things too.

Leaving all the semantic hair-splitting aside. What is the practical difference in how you write a Rust program compared to a C program, given that integer overflow is "defined" in Rust?

jstimpfle··on Everything in C is undefined behavior
> It didn't assume in that case either. It did a well defined thing: add checks.

It did. The compiler added the checks (which panic on overflow, from a quick web search) precisely so it (and importantly, the developer!) can assume the overflow didn't happen in the subsequent code. Unless you consider a panic a defined state, and consider wrap-on-overflow equally valid in all cases, it's essentially the same as UB. (panic seems to be considered "unrecoverable").

Difference is _at most_ that C spec gives compiler more freedom to "implement UB", but then again, hit any unsafe code in Rust with wrapped around integer, you probably have comparable practical result -- machine doing random things, corrupting memory and so on.

jstimpfle··on Everything in C is undefined behavior
As you said, overflow is defined in Cargo, given a specific build type and/or specific build flags. It's not defined in Rust.

Just saying that it's defined and then not saying what the definition is, is no different from saying it's undefined.

jstimpfle··on Everything in C is undefined behavior
So in Rust, you are actually specificing TWO programs with a single source? Those Rust users are surely too clever for my liking!

You can tune a C compiler as well to have a very specific defined behaviour for integer overflow. You can add -fwrapv or you can add UBSAN.

The user never intended overflow to happen, because if they did, they could have used something like __builtin_mul_overflow() or whatever. Or they are an emotionally unstable user with destructive tendencies. The user also never intended the program to abort with a (nicely formatted) error message, unless they are a very very sad depressed nihilistic user who also never runs their program in Release mode.

To say that overflow would be defined in Rust is at least half a lie. We could agree that cargo has a choice of diagnostic policy though, a policy how to handle what is essentially a state with no defined or useful path forward, or in other words, UB.

Throwing errors might be a wanted property to detect oversights. C ecosystem has UBSAN too! But essentially the same is still true: Basic arithmetic operations are not closed over the numbers 0..2^N. Rust doesn't have a (unique and useful) definition for those operations for a subset of numbers. Even if you claim the operations are defined (say wrapping arithmetic in Release mode), it's not what the programmer wants. Probably the majority of algorithms work over natural numbers or integer numbers. These algorithms don't work when the arithmetic on integers modulo 2^N.

So the user has to constrain the set of valid inputs, and do manual sanitization, just like in C.

jstimpfle··on Everything in C is undefined behavior
UB is a runtime property. As far as you can statically verify some code parts, you can see UB at compile time, but the point of UB is exactly that it is about stuff you can't predict, or that is hard to predict as a compiler.

Now why you can cook up trivial artificial examples where a compiler will remove some code sections based on statically detected UB, instead of printing an error, you have to ask the compiler authors.

> The semantics are well-defined in both modes.

So they're not the same? So the behaviour is not uniquely defined by the source code alone, but is actually _very_ different based on compile mode? Between two modes whose point was never to have different semantics, but to have the _same_ semantics while being debuggable vs being fast?

> You can predict exactly what will happen in either case. In C, the semantics are not defined at all, you can't predict what will happen and it's allowed to change between compilations of the same source.

You can make the same "predictability" argument for C, you can easily write a compiler that has semantics exactly laid out. Case in point: -fwrapv. Case in point: UBSAN.

jstimpfle··on Everything in C is undefined behavior
I asked where is the part about unaligned pointers in your string processing example. Saying that you want to load multiple bytes at a time does not imply at all that you have to do unaligned loads.

Doing unaligned loads using SSE or AVX might have been possible on Intel architectures for a long time, but it is still a little bit slower afaik. But anyway when you get into sub-architecture specific details like that, you've essentially left C-land, and you're essentially doing assembler level programming.

jstimpfle··on Everything in C is undefined behavior
I'm saying that string search algorithms are _not_ a legitimate use case for unaligned loads.
jstimpfle··on Everything in C is undefined behavior
TBF that is the same as saying "signed overflow is UB".
jstimpfle··on Everything in C is undefined behavior
But did the rust compiler assume that the integer would not overflow? It did so in Debug mode where runtime checks were added. If it's not the case in Release mode, does that mean semantics are different between Debug and Release?
← PreviousPage 6 of 34Next →