- If I want to write a multithreaded program, is it best to have one io_uring per thread?
- Per CPU core?
- Per device or PCIe lane to the device? (It might make sense ...)
- Or one for the whole program? (Will Linux distribute requests across cores and run them in parallel for me?)
- If I'm writing a library that uses io_uring, should I create my own io_uring or offer an interface to add requests to one which the main program creates? (Or perhaps both?)
Yes! But it's not quite that simple: Sometimes you might need some of the ordering operations in io_uring, which then will force you to use a separate (possibly dedicated) ring for that. Typical cases might be a database's journal.
If you use multiple threads (or processes) to access the same ring you'll need synchronization around submission / completion (potentially separately). Avoiding that when not necessary is obviously good.
> - Per CPU core?
That'll depend heavily on your program. If you have a program which has tasks running on specific CPUs, yes, that can make sense - but at that point it's basically a subset of the muti-threaded program question.
> - Per device or PCIe lane to the device? (It might make sense ...)
Hm. I think that will effectively also boil down to the per-task thing above. Particularly if you use polling it's good to be on the actual NUMA node the PCIe device is attached too.
> - Or one for the whole program? (Will Linux distribute requests across cores and run them in parallel for me?)
I think that'll depend on the type of device. For e.g. NVMe devices, there'll be multiple hardware queues. The CPU the submitter is on will determine which hardware queue is used (and that in turn will influence the interrupt location, at least by default).
> - If I'm writing a library that uses io_uring, should I create my own io_uring or offer an interface to add requests to one which the main program creates? (Or perhaps both?)
I don't think there's a generic answer to this one. It'll heavily depend on the type of library.
Caveat: I've used io_uring a bunch, read some of the code, but I'm not an authority on it.
const grent = match (passwd::getgroup(group)) {
void => fmt::fatal("No '{}' group available", group),
gr: passwd::grent => gr,
};
Interesting, is void being used as the "None" of an option type here? At least that's what it looks like to me.Here are some opinions shared by ddevault: https://news.ycombinator.com/item?id=15494222
> I’m interested to see how it measures up under more typical workloads. People keep asking me what I think about Zig in general, and I think it has potential, but I also have a lot of complaints. It’s not likely to replace C for me, but it might have a place somewhere in my stack.
With cross-platform standards like OpenGL, POSIX, Vulkan the API is declared in terms of high-level ANSI C or C99 language constructs and binary constants may be unspecified and left up to vendor.
Ideally new language designers which don't want to support H files would propose an even simpler header file format for listing procedures, enumerations, structs, constants that existing C compilers could also support. Library vendors don't have time to generate header bindings for every new language using language specific constructs, and application developers don't have time to manually write bindings for large libraries when testing new languages.
Zig supports C header parsing, but as you mention is based on LLVM. It also allows defining new functions inside structs and return values, which can result in heavily nested code that some programmers may not be a fan of.
So there is theoretically some room for another language which maintains compatibility with C headers (or defines an even simpler header format which can be supported by C compilers), which does not use LLVM, and which maintains stronger conceptual separation between namespaces and structs.