Spartan programming gathers many techniques discussed in the literature, adding some of its own, into a unique coding style whose main objective is minimal use of various elements of the programming language which may contribute to complexity. This programming style relies on strict self-discipline, avoiding some of the opportunities offered by the underlying language, geared at achieving the programming equivalent of laconic speech. Spartan programming is not directly concerned with readability, at least not in its subjective and cultural-dependent sense. In fact, spartan programs will bring much misery to anyone preferring long, verbose programs. In certain ways, spartan programming is a coding style, just like the Linux kernel style guide. But, spartan programming is more than just a technical coding style, in that is has a single underlying, unifying principle - minimalism and simplicity taken to extremes. The coding guidelines began with a dozen or so printed pages, "a little book of style", which the author handed out to computer science students in the Hebrew university of Jerusalem. The term "Spartan Programming" was coined in 1996, when the author gave a tutorial on "Spartan C++" at the TOOLS (Techniques of Object Oriented Languages and Systems) scientific conference, held in Santa Barbara, CA USA. The guidelines were taught under this name in numerous Technion courses since then. Spartan programming strives for simultaneous minimization of all of the following measures of code complexity: The latter two are related to, but not the same as cycolomatic complexity On the one hand, the Babylonian tower principle states that there is a limit to the number of abstraction levels that a software system may have. On the other hand, the seven plus minus one or two principle sets a limit on the number of subcomponents that may constitute a super component. The spartan programming approach makes it possible to erect slightly higher software towers, by stretching the capabilities of basic modules further. Simple, spartan like modules, make a stronger foundation. The main techniques offered by the discipline are: Minimizing the number of variables, by inlining variables which are used only once, grouping related variables in a common data structure, and by using advanced programming constructs such as foreach loops (for (Variable in Collection)) and other chaining constructs in supporting languages Minimizing the visibility of variables and other identifiers. That is to say, defining these at the smallest possible scope. In C++ one would thus prefer variables defined in a block to those defined in a function scope; function scoped variables are better than class scoped variables, i. e., fields; and fields are not as desirable as variables defined in the file scope. Further, variables defined in the file scope are better made static so that they are not visible in other files. Minimizing the accessibility of variables, by preferring greater encapsulation, e. g., private variables, to public variables. Minimizing the variability of variables, that is striving to make variables final in Java, const in C++, etc., and by using nonnull annotations or restrictions, whenever the development environment or programming language supports it. Minimizing variables' name length, by applying the generic names technique. Minimizing variables life time, by preferring ephemeral variables to longer lived ones, and by avoiding, as much as possible, persistent variables (i. e., files and the such). Thus, in C, one should prefer auto (that is stack) variables to static variables. Heap storage on the other hand, although potentially shorter lived than static data, is considered inferior, since heap management requires extra code. Minimizing the use of arrays, and replacing these by collections provided by standard and of-the-shelf libraries. The following represents a not-so-small sample of spartan programming code in Java. Spartan programming suggests encapsulating control with appropriate abstraction mechanisms. The following Java classes:
The Convivial Society: Vol. 7, No. 5
Tech sector says only carbon-emitting gas plants are reliable enough today to power the EU’s AI goals.
J.P. Morgan hits photographer with cane This is just a brief post to explain to my old boss, Eric Schmidt , why he and his ilk are getting ...
Instant browser party games for friends. No accounts, just a room code.
One of the most painful arguments I keep having with fellow techies is the question of whether you can distinguish between human-written and AI-generated text.
Bevy, Rust, Graphics, etc
epoll vs io_uring in Linux 2026-06-20 > #programming > #c > #linux First, I want to tell you how exactly I got to this point and why I started researching different options for handling asynchronous I/O on Linux… Last year, my students and I built a reverse proxy server called TinyGate. It was super simple, worker-based, and it basically worked well. Of course, I didn’t expect it to be very fast, but it was an educational project, and since we’d made a real, kind of production-ready tool, I was really proud of it. But my students weren’t as happy as I was - they wanted to build something genuinely useful, and they were really disappointed that our “product” had strong architectural limits and couldn’t outperform titans like nginx and haproxy. So they literally forced me to research together how those tools work under the hood and how to handle asynchronous I/O to cut down on the heavy overhead… Long story short, we made a second version of TinyGate, based on epoll. It still lost to nginx/haproxy in benchmarks, but it had a dramatic performance boost compared to the first version. But epoll isn’t perfect either (as I’ll explain below), and we eventually switched to io_uring, which led to a full rewrite of our project from scratch, again… So it’s a really interesting topic, and today I’ll share an overview of the two queueing systems Linux gives you for asynchronous I/O. epoll heritage When I just started developing for Linux, epoll was a new feature, and basically it had no alternatives. Everyone used it to manage asynchronous execution - there was no other choice. The problem is, epoll relies heavily on syscalls: it tells you when I/O is possible, but you still have to call read()/write() yourself afterward - that’s two syscalls per I/O event, on top of the one-time epoll_ctl registration. Each of these syscalls causes a context switch between user and kernel mode, which creates HUGE overhead once you’re handling a lot of connections. But we have a solution! About 17 years after epoll landed in the Linux kernel (2002), io_uring appeared (2019)! Instead of telling you when I/O is possible, it tells you when I/O is done - no polling loop, and far less associated syscalls. The kernel consumes submissions from memory shared between your app and the kernel, and posts completions back into that same shared memory - both live in ring buffers, hence the name. The catch: by default you still have to call io_uring_enter() to tell the kernel “go check the submission queue” - but one call can submit a whole batch of operations and reap a whole batch of completions, instead of one syscall pair per operation like with epoll + read. If you want close to zero syscalls during steady state, there’s IORING_SETUP_SQPOLL, which spins up a dedicated kernel thread that polls the submission queue for you - at the cost of that thread burning CPU (more on this below). A little comparison Basic architecture: as I said before, epoll notifies you when I/O is possible, io_uring notifies you when I/O is done. Where epoll makes every I/O operation cross the kernel boundary, io_uring lets you pay a small “setup fee” once (creating the ring) plus a per-batch fee (the io_uring_enter() call) instead of a fee per operation. So instead of a syscall pair per I/O, you get a syscall per batch of I/Os - or, with SQPOLL, close to none at all. As you can see, with a ton of I/O happening, this saves a lot of syscalls. On relatively new systems where io_uring is supported (kernel v5.1+, released in 2019), there’s often not much reason to reach for epoll. The shift from a readiness model to a completion model is a huge architectural change - it moves a big part of the work out of your application and into the kernel. Let’s code! Of course, I won’t leave you without some code showing how both systems work. We’ll use C. (The io_uring example uses liburing, the userspace helper library - install it via liburing-dev/liburing-devel, or drop down to the raw io_uring_setup/io_uring_enter syscalls if you want zero dependencies.) epoll Let’s make a simple example of how epoll works. We’ll create the instance, register a file descriptor (stdin, in our case), and process the incoming event. #include #include #include #include #define MAX_EVENTS 8 int main() { // Creating the epoll instance int epoll_fd = epoll_create1(0); if (epoll_fd == -1) { perror("epoll_create1"); return 1; } // Registering a file descriptor (stdin in our case) struct epoll_event ev, events[MAX_EVENTS]; ev.events = EPOLLIN; ev.data.fd = STDIN_FILENO; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &ev) == -1) { perror("epoll_ctl"); return 1; } // Blocking until something is readable int n = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); if (n == -1) { perror("epoll_wait"); return 1; } // For each fd, issue a SEPARATE syscall to do the I/O for (int i = 0; i < n; i++) { if (events[i].data.fd == STDIN_FILENO) { char buf[256]; ssize_t count = read(STDIN_FILENO, buf, sizeof(buf)); printf("read %zd bytes\n", count); } } // Cleaning up close(epoll_fd); return 0; } As you can see, this example uses three syscalls in total: epoll_ctl (a one-time registration), then epoll_wait and read for the event - so two syscalls per actual I/O event, like I mentioned above. The code itself is pretty easy to follow. io_uring Now let’s do the same thing with io_uring instead of epoll. #define _GNU_SOURCE #include #include #include #include int main() { struct io_uring ring; char buf[256]; // Setting up the ring if (io_uring_queue_init(8, &ring, 0) < 0) { perror("io_uring_queue_init"); return 1; } // Prepare a READ operation on stdin struct io_uring_sqe *sqe = io_uring_get_sqe(&ring); io_uring_prep_read(sqe, STDIN_FILENO, buf, sizeof(buf), 0); // Submitting the read io_uring_submit(&ring); // Waiting for completion struct io_uring_cqe *cqe; if (io_uring_wait_cqe(&ring, &cqe) < 0) { perror("io_uring_wait_cqe"); return 1; } if (cqe->res < 0) { fprintf(stderr, "read failed: %d\n", cqe->res); } else { printf("read %d bytes\n", cqe->res); } // Marking seen then cleaning up io_uring_cqe_seen(&ring, cqe); io_uring_queue_exit(&ring); return 0; } What can we see here? Similar instance creation step. No epoll_ctl registration step needed. No readiness check needed before submission. No separate read() call at completion. Yeah, io_uring takes way fewer resources for this - though, as noted above, there’s still one io_uring_enter() call hiding inside io_uring_submit() and io_uring_wait_cqe() unless you’re running with SQPOLL. When you test these examples, keep in mind that for the sake of simplicity, some important parts are missing. For example, it will block forever if stdin never produces any data, and the io_uring example skips checking for a NULL sqe (which io_uring_get_sqe() can return if the submission queue is full). Something additional about io_uring Zero-copy. For real zero-copy I/O, register your buffers ahead of time with io_uring_register_buffers() - this avoids the kernel re-mapping memory on every single operation. For network sends specifically, look at IORING_OP_SEND_ZC (kernel 6.0+ needed), which skips copying the buffer into the kernel entirely. SQPOLL uses CPU. Even when your queue is empty, IORING_SETUP_SQPOLL keeps a kernel thread spinning and polling, which burns CPU. There’s an idle timeout (sq_thread_idle) after which it backs off to sleeping, but it’s not free. Asynchronous error handling. Errors come back (and must be handled) asynchronously, as part of the cqe’s res field - not as a direct return value like a normal synchronous syscall. Summary io_uring is the new standard for async I/O in the modern Linux world, and honestly, I don’t see much reason to still reach for epoll on a system that has it. For a from-scratch project on a modern Linux server, like our TinyGate rewrite, io_uring is absolutely the way to go. I’m a die-hard supporter of dropping support for old systems as soon as it’s reasonable - if you’re still running a kernel released more than 7 years ago, in my opinion, that’s not a great idea…
Cryo chambers, mini pigs, and $26 billion are fueling his quest for immortality.
The study of bureaucracy
Play Historic Firsts, the daily history timeline game where you sort landmark inventions, discoveries, breakthroughs, and historic firsts from earliest to latest.
A markup language and transport format for HTML and XML fragments.
We have the pleasure of celebrating the birthday of Blaise Pascal by announcing the release of OCaml version 5.5.0. Some of the highlights in OCaml 5.5.0 are: Module-dependent Functions Modules can now be used as function arguments in a form of lightweight functors. For instance, we can define a function for printing a map generated by the Map.Make functor: let pp_map (module M: Map.S) pp_key pp_v ppf set = if M.is_empty set then Format.fprintf ppf "ø" else let pp_sep ppf () = Fo...
Benchmarks, context-window behavior, token economics, and the MCP wiring for running Claude Code and OpenAI Codex as a single coding pipeline.