IT Hare is a blog by a game-development veteran on low-level performance, networking, memory allocators and multiplayer game architecture.
HN links it for its reference-style posts, such as CPU operation costs and the TCP-versus-UDP debate for games.
FWIW: IMO, we should separate CQRS and ES.
CQRS is a Good Thing(tm); for a real-world example of it in work on a not-so-shabby system processing 10B+ transactions/year - see http://ithare.com/gradual-oltp-db-development-from-zero-to-10-billion-transactions-per-year-and-beyond/ .
ES, however, is more controversial. If speaking about "pure" ES (i.e. not having any mutable state, and reconstructing current state from input events all the time) - versioning and potential synchronization failures (and synchronized access is a prereq for event sourcing) will kill it very quickly (and I didn't even start speaking about performance, which is going to be a very serious challenge).
OTOH, if understanding ES just as an ADDITION to classical mutable-state processing - it can be made very useful. Not only ES will serve as a perfect audit, the duplication of information (once in mutable state and once in input events) will allow such things as regression testing, and fixing data problems caused by bugs, within the DB. BTW - with this model, the latter can be done in a post-factum-fix manner and this, unlike "pure-ES" fixes, is not confusing to the readers who already got and stored previous state of the DB (with "pure" ES, after the fix, all the history can change, invalidating all the data which might have been stored by the third parties, and this is really crazy - imagine if your bank statements would change overnight; with a "ES+mutable" model, bugs still can be identified, and…
OP specifically uses that redundancy to prevent use-after-free errors. That redundancy is limited to the handle, and checked at every dereference (in the get_pointer() so you don't forget to check). Desyncs between a handle and the object it points to ought not to be possible at this point.
If you remove the redundancy, you have to give up on use-after-free checks. If you want to prevent use-after-free anyway, you need to use a simpler allocation pattern (stack or arena), or fall back to heap allocated pointers (smart pointers in C++, various kind of references in Rust…). But heap allocated pointers were precisely what we were trying to avoid in the first place, most notably because they're not cache friendly, cause memory fragmentation, make leaks harder to detect, and in many cases simply take up much more space than handles (remember, pointers are 8 bytes nowadays).
Using handles atomically is probably a consequence of a very bad idea: threads with shared mutable state and locks. As much as humanely possible, you don't want to share any mutable memory to begin with. Send messages instead, it's less error prone, and with current computers, it's often faster as well. See http://ithare.com/ on (re)actors for the gory details.
About debugging: some people read code by running it in the default step by step debugger provided by their IDE. I don't understand that way of thinking, so I don't bother catering to it. My code then is optimised for readability without special…
Hardware branch prediction is very powerful (https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html) but it's also not perfect and costly misdirections may happen (http://ithare.com/infographics-operation-costs-in-cpu-clock-cycles/).
Branchless computing (https://www.youtube.com/watch?v=g-WPhYREFjk) is a technique which may help a lot with mitigating the overhead of branching.
Alternative way, coming from linux kernel (https://docs.kernel.org/staging/static-keys.html) is to leverage knowledge about branches direction and literally change the instructions via run-time code patching to avoid the potential branching overhead all together.
When to go for such extreme solution? When performance really matters:
- `and` branches are known at compile-time - `or` branches are not changing often at run-time - `and/or` branches are expensive to compute/require memory access - `and/or` branches are hard to learn by the hardware branch predictor due to their random nature Some examples include logging, tracing, configuration, hot-path, algo, etc.
> https://github.com/boost-ext/sb - x86-64/linux/gcc/clang moves the solution to user space (for run-time) as well as to compile-time with C++20.
> static_branch - Minimal overhead run-time branch (https://godbolt.org/z/797WeYYGK) / (nop=not taken / jmp=taken)
void fun() { // can be inlined/constexpr or not if (sb::static_branch<"semi run-time branch">::get()) { …
Apologies if I'm being too academic here but not all memory bottlenecks or communication bottlenecks are the "Von Neumann Bottleneck".
The term was originally focused on both data and program memory being on the other side of a shared bus from the CPU, which meant you could only do one at a time. If you were trying to figure out your next instruction, you wait. If you then need to data, you wait. It wasn't a problem back with slowly executing EDVAC code [0]. Based on this definition most architectures today do not have the Von Neumann Bottleneck as they are not Von Neumann architectures [1, 2, 3].
A slightly looser definition of the Von Neumann Bottleneck refers to the separation between CPU and memory with a single bus. This likely originated because fully Von Neumann architectures are so rare but that the general problem is similar enough to share the name. GPUs dont have this issue because they employ parallelism thru multiple memory ports talking to off chip RAM. TrueNorth also doesn't have this issue because it has 4096 parallel cores with their own localized memory and no off chip memory. There could certainly be other bottlenecks in the system, even with the memory system, but those wouldn't be the Von Neumann Bottleneck [0].
[0] https://en.wikipedia.org/wiki/Von_Neumann_architecture#Von_Neumann_bottleneck
[1] https://news.ycombinator.com/item?id=2645652
[2] http://ithare.com/modified-harvard-architecture-clarifying-confusion/
[3]…
The best resource in my opinion is still "What every programmer should know about memory." It is now over ten years old but still the most comprehesive and comprehensible post I know about that topic.
https://lwn.net/Articles/250967/
AFAIK there have been no major breakthroughts since that time in memory technology (Transactional memory could be one). While modern processors may be build a bit different (e.g. added shared L3 chache) you will have no problem understanding them.
There is also this github :
https://github.com/Kobzol/hardware-effects
for programs which should show such effects. (I haven't used them yet but they may be interesting for you to play with.)
IT Hare has published a nice article about those costs including rules of thumb costs.
http://ithare.com/infographics-operation-costs-in-cpu-clock-cycles/
Then there are the Agner tables. Giving you latencies and reciprocal throughput for your instructions.
https://www.agner.org/optimize/instruction_tables.pdf
If you are through these you have to read the intel and AMD optimization guides. (ARM may or may not have something similar.)
The paper by Kazushige Goto ("Anatomy of a High Performance Matrix Multiplication") is an example of Cache and TLB considerations in a non-trivial example.
http://www.cs.utexas.edu/users/pingali/CS378/2008sp/papers/gotoPaper.pdf
I love seeing more options appear on the horizon for doing stateful serverless work. This article[1] provides a little more motivation for the use cases:
> For quite a long time (and especially in the webdev world), there exists a perception that to achieve scalability, all our request handlers need to be as stateless as possible. In the world of the all-popular Docker containers, it means that all the app containers need not only to be immutable, but also should be ephemeral ... keeping our request handlers stateless, does NOT really solve the scalability problem; instead it merely pushes it to the database.
Though the problems and solutions pointed out in that article don't mean you have to go straight to process-per-X. One solution might be, as mentioned in passing in the OP's launch blog, to keep state in a cache like Redis. If the data fits this approach, it would ease load on the database while allowing each request handler to remain stateless.
Durable Objects seem less focused on heavy computation, but I think they're really interesting as points of synchronisation for e.g. collaborative editing. Having all requests go into a _single thread_ seems important.
[1]: http://ithare.com/scaling-stateful-objects/