Linked by Hadrien Grasland on Fri 28th Jan 2011 20:37 UTC
OSNews, Generic OSes It's recently been a year since I started working on my pet OS project, and I often end up looking backwards at what I have done, wondering what made things difficult in the beginning. One of my conclusions is that while there's a lot of documentation on OS development from a technical point of view, more should be written about the project management aspect of it. Namely, how to go from a blurry "I want to code an OS" vision to either a precise vision of what you want to achieve, or the decision to stop following this path before you hit a wall. This article series aims at putting those interested in hobby OS development on the right track, while keeping this aspect of things in mind.
Thread beginning with comment 460234
To view parent comment, click here.
To read all comments associated with this story, please click here.
RE[13]: Machine language or C
by Alfman on Sun 30th Jan 2011 22:22 UTC in reply to "RE[12]: Machine language or C"
Alfman
Member since:
2011-01-28

"Second reason why early optimization is bad is that, as I mentioned earlier, there's a degree of optimization past which code becomes dirtier and harder to debug."

Re-writing code in assembly (for example) is usually a bad idea even after everything is working, surely it's even worse to do before. But then this isn't the sort of optimization I'm referring to at all.

Blanket statements like "premature optimization is the root of all evil" put people in the mindset that it's ok to defer consideration of efficiency in the initial design. The important factors proposed are ease of use, manageability, etc. Optimization and efficiency should only be tackled on at the end.

However, some designs are inherently more optimal than others, and switching designs mid stream in order to address efficiency issues can involve a great deal more difficultly than had the issues been addressed up front.

For a realistic example, see how many unix client/server apps start by forking each client. This design, while easy to implement up front, tends to perform rather poorly. So now we have to add incremental optimizations such as preforking and adding IPC, then we have to support multiple clients per process, etc.

After all this work, the simple app + optimizations end up being more convoluted than an more "complicated" solution would have been in the first place.

The Apache project is a great example of where this has happened.


The linux kernel has also made some choices up front which has made optimization extremely difficult. One such choice has been the dependence on kernel threads in the filesystem IO layer. The cement has long dried on this one. Every single file IO request requires a kernel thread to block for the duration of IO. Not only has this design been responsible numerous lock ups for network file systems due to it being very difficult to cancel threads safely, but it has impeded the development of efficient asynchronous IO in user space.

Had I been involved in the development of the Linux IO subsystem in the beginning, the kernel would have used async IO internally from the get go. We cannot get there from here today without rewriting all the filesystem drivers.

The point being, sometimes it is better to go with a slightly more complicated model up front inorder to head off complicated optimizations at the end.

Edited 2011-01-30 22:36 UTC

Reply Parent Score: 2

RE[14]: Machine language or C
by Neolander on Mon 31st Jan 2011 07:35 in reply to "RE[13]: Machine language or C"
Neolander Member since:
2010-03-08

Blanket statements like "premature optimization is the root of all evil" put people in the mindset that it's ok to defer consideration of efficiency in the initial design. The important factors proposed are ease of use, manageability, etc. Optimization and efficiency should only be tackled on at the end.

However, some designs are inherently more optimal than others, and switching designs mid stream in order to address efficiency issues can involve a great deal more difficultly than had the issues been addressed up front.

For a realistic example, see how many unix client/server apps start by forking each client. This design, while easy to implement up front, tends to perform rather poorly. So now we have to add incremental optimizations such as preforking and adding IPC, then we have to support multiple clients per process, etc.

Actually, I think this whole fork() thing started as a memory usage optimization. On systems with a few kilobytes of memory like the ones which UNIX was designed to support, being able to have two processes using basically the same binary image was a very valuable asset, no matter the cost in other areas.

Nowadays, however, even cellphones have enough RAM for the fork() system to be a waste of CPU time and coding efforts (hence the decision of the Symbian team not to include it a while ago). But due to legacy reasons and its mathematical elegance, it still remains.

After all this work, the simple app + optimizations end up being more convoluted than an more "complicated" solution would have been in the first place.

The Apache project is a great example of where this has happened.

All hail the tragedy of legacy code which sees its original design decisions becoming irrelevant as time passes (the way I see it).

The linux kernel has also made some choices up front which has made optimization extremely difficult. One such choice has been the dependence on kernel threads in the filesystem IO layer. The cement has long dried on this one. Every single file IO request requires a kernel thread to block for the duration of IO. Not only has this design been responsible numerous lock ups for network file systems due to it being very difficult to cancel threads safely, but it has impeded the development of efficient asynchronous IO in user space.

I think that should we look deeper, we'd find legacy reasons too. After all, as Linux was designed as a clone of ye olde UNIX, maybe it had to behave in the same way on the inside for optimal application compatibility too ? Asynchronous IO is, like microkernels, a relatively recent trend, which has only be made possible due to computers becoming sufficiently powerful to largely afford the extra IPC cost.

Edited 2011-01-31 07:42 UTC

Reply Parent Score: 1

RE[15]: Machine language or C
by Alfman on Mon 31st Jan 2011 09:26 in reply to "RE[14]: Machine language or C"
Alfman Member since:
2011-01-28

Aw, wonderful Neolander! A new topic, I was getting tired of listening to myself go on about premature optimization.


"I think that should we look deeper, we'd find legacy reasons too. After all, as Linux was designed as a clone of ye olde UNIX, maybe it had to behave in the same way on the inside for optimal application compatibility too ?"


An asynchronous kernel can easily map to synchronous or asynchronous userspace.

An synchronous kernel easily maps to synchronous user space.

However, a synchronous kernel does not map well to asynchronous user space, which is the scenario we find ourselves in under Linux.

Some AIO background:
For those who aren't aware, setting O_ASYNC flag is not supported for file IO. Posix defined a few new functions to enable explicit asynchronous access to arbitrary descriptors.


In the case of posix aio support in linux, it's built directly on top of pthreads, which is a major disappointed to anyone looking to use AIO. Each request creates a new thread which blocks synchronously inside the kernel waiting for a response. When done, the kernel returns to the user thread which is configured to either raise a signal or call a user space function.

The problem here is that the whole point of AIO is to avoid the unnecessary overhead of a new thread and stack for each IO request. Ideally, we'd just tell the kernel what we need to read/write and it will notify us when it's done. With true AIO, there is no need to block or use threads.


More recently, kernel developers have created a new syscall to expose a real AIO interface to userspace. You can install "libaio" to access this interface. It's not 100% posix compatible, and sadly this still uses threads within the kernel (due to legacy design decisions in the kernel), but at least userspace is completely free to replace the synchronous file interface.

There's more bad news though, the kernel level AIO has only been implemented for block devices. Therefor sockets, pipes, etc won't work. This is why posix AIO on linux continues to use pthreads.


"Asynchronous IO is, like microkernels, a relatively recent trend, which has only be made possible due to computers becoming sufficiently powerful to largely afford the extra IPC cost."

Actually, AIO interfaces reflect the underlying hardware behavior more naturally and efficient than blocking alternatives. It has it's roots in interrupt driven programming, which is inherently asynchronous.

For whatever reason, operating system began exposing a blocking interface on top of asynchronous hardware such that multi-process/multi-threaded programming became the norm.


I seem to be trailing off topic though...

Reply Parent Score: 1