You Must Fix Your Asserts

I was wondering, too… but I think it means, at the simplest level, building ReleaseFast instead of ReleaseSafe, so that you get a crash rather than UIB. I could, however, also mean comptime-if’ing condition checking explicitly, to guarantee NO (side-effect) code is run at all if your builtin.mode is such-and-such.

Is this solely the fault of the one extra indirection (in assembly) that @pzittlau mentioned above? If so, this certainly seems to encourage use of @panic(), in particular, if you always intend these “asserts” to be never be disabled in production. If, otoh, you intend to use ReleaseFast, then it would still make sense to use unreachable for the performance gain that could come from optimized compilation given the unreachable hint (at the expense of possible UIB). (That is, that performance gain could outweigh the small hit for the extra indirection; though, you wouldn’t know by how much without explicit testing or assy inspection…)

In the db I worked on in the past, a failed assertion would cause the process to print stack traces and exit. The error exit would be noticed by a very small/reliable parent process, which would restart it; if restarts occurred multiple times within a certain time interval, it would give up to avoid flooding the logs. The advantage of doing it in the parent process is that the child process may crash for reasons other than an assertion failure.

Knot Resolver actually does both nowadays. They do the forking thing I mentioned earlier, and there is a manager daemon that (among other things) restarts crashed kresd processes.

One addendum to the forking mechanism: a single kresd process may have multiple DNS queries in flight at any given point in time. There are many asserts in the codebase that would realistically only affect a single query when they happen, making it much better to fail that particular query (usually by sending a SERVFAIL response to the client) and keep the other ones running. If the whole process were to crash, all queries that were in flight at that point would fail. Combine that with DNS running over UDP in many cases and you get a less than stellar user experience in the form of DNS resolution delays.

1 Like

Don’t know why but in my (and one other coders chess engine in ZIg) we noticed that the
io print catch unreachable (just let the thing crash) produced a slower engine overall than panic.
Of course this is not too much context, but after we both changed it, it produced a higher “nodes-per-second”.

is the “debug” in the struct’s name a mistake/typo? cannot figure out why the words “debug” and “prod” are in the same sentence; debug objects have no business in prod/release iirc.

The extra indirection is only a thing if unreachable is lowered to a call to std.lang.panic which is only the case for Debug and ReleaseSafe modes. Otherwise it’s literally assumed to be unreachable by the chosen compiler backend.
Even for ReleaseSafe, you’d only ever need to follow that one extra indirection if you plan to crash your program right after anyway so it shouldn’t impact the ‘happy path’ at all.

My guess is that the performance difference is caused by the LLVM optimizer prefering @panic over unreachable in that specific case for whatever reason.

2 Likes

I’d encourage you and @npc1054657282 to read the linked blog post again to check your understanding.

“disabling assertions in a production environment” means using a language like C or Rust, where asserts are implemented via macros which are therefore deleted from the compiled code in release builds. In Zig, the closest thing would be something like using comptime if (builtin.mode != .ReleaseFast) std.debug.assert(...) and then releasing ReleaseFast builds of your software.

“leaving assertions in” means—in the case of Zig—just using plain old catch unreachable and std.debug.assert in your code as God Andrew intended.

I’m sorry, I got confused while reading the article because I feel that the ‘disabling assertions in the production environment’ mentioned in the text is not much different from the actual effect of assertions under the current ReleaseFast.

After thinking it over again, this article might be a response to the difference discussed earlier on the forum regarding whether side effects inside assertions would be retained. Overall, I feel that there isn’t a very big difference, and I feel this comment ‘disabling asserts in prod’ never clearly conveyed what it intended to express from the beginning.

Ah, I see. I think you must have missed something. One big difference that Loris alludes to in the blog post is that when code is present during compilation, the compiler knows about it. That is, when assertions are retained in production, the compiler can use them as information when optimizing.

If those same assertions are removed (be it through comptime if or macros), the compiler cannot make the same assumptions. The entire point of the article (please read it again with this in mind) is that this is strictly worse than leaving them in.

2 Likes

Thank you, now I understand this point, and assertions are indeed very valuable for performance optimization.

What helped me understand these questions around zig’s assert, is that in zig to think of the assertion being made by you, the programmer, to the zig compiler. The zig compiler always uses them:

  • in safe modes it will complain if you lied to it
  • in release modes it will take your assertion for granted and may use it for optimization

In contrast, in C for example I think of an assertion as being made by the compiler. However, it only does so in debug builds. In release builds the assertions are completely stripped out before the compiler can even see them.

1 Like