The just released version 12.08 of the Genode OS Framework comes with the ability to run Genode-based systems on ARM hardware without an underlying kernel, vastly improves the support for the NOVA hypervisor, and adds device drivers for the OMAP4 SoC. Further functional additions are a FFAT-based file system service, the port of the lighttpd web server, and support for on-target debugging via GDB.

Fortunately, your concerns do not apply for Genode. In Genode's libc, the seek offset is not held at the file system but local to the process within the libc. The file-system interface is designed such that the seek offset is passed from the client to the file system with each individual file-system operation. The seek value as seen at libc API level is just a value stored alongside the file descriptor within the libc. Therefore, lseek is cheap. It is just a library call updating a variable without invoking a syscall.

Your example does indeed subvert the locking scheme. But as Genode does not provide fork(), it wouldn't work anyway. ;-)

Btw, if programs are executed within the Noux runtime (see [1]), lseek is actually an RPC call to the Noux server. So the pread/pwrite implementation carries an overhead compared to having pread/pwrite as first-class operations. So there is room for optimization in this case.

Given all the steps that are involved in a single read I/O operation, however, I am uncertain about the benefit of this specific optimization. To prevent falling into the premature-optimization trap, I'd first try to obtain the performance profile of a tangible workload. Another reason I'd be hesitant to introduce pread/pwrite as first-class operations into Noux is that in general, we try to design interfaces to be as orthogonal as possible. Thanks to this guideline, the Noux server is a cute little component of less then 5000 LOC. Introducing pread/pwrite in addition to read/write somehow spoils this principle and increases complexity.

Thanks for the pointer to the database engine. This might be a good starting point for a workload to be taken as reference when optimizing for performance and scalability.

Running two instances of this program simultaneously on linux produces "1122". However if libc uses a process-local file offset, then it would probably output "12". I imagine you just ignore the offset that gets passed for files opened in append mode?

"To prevent falling into the premature-optimization trap, I'd first try to obtain the performance profile of a tangible workload."

I'm a little surprised that even with a 10K buffer size, there's still a very noticeable half-second difference with the lseek syscall approach on linux. I suspect Genode-Noux would exhibit similar trends. But does it matter? That depends on who we ask. Sometimes design factors are worth some additional overhead. There are always trade offs.

Your experiment is pretty convincing. :-) Especially when considering your original suggestion to use a database a workload. For this application, requests for individual database records are certainly much smaller then 10 KiB.

But your experiment also shows another point quite clear: The effectiveness of the Linux block cache. A throughput of 3 GiB/sec is quite nice for accessing a disk. ;-) I think that the addition of a block-cache component to Genode would be the most valuable performance improvement at the current stage. There is actually a topic in our issue tracker but nobody is actively working on it at the moment:

"I imagine you just ignore the offset that gets passed for files opened in append mode?"

Almost. The file-system interface does not differentiate a mode when opening a file but there is an append operation that can be applied to an open file by specifying ~0 as seek position. For reference, here is the interface:

"I'm a little surprised that even with a 10K buffer size, there's still a very noticeable half-second difference with the lseek syscall approach on linux. I suspect Genode-Noux would exhibit similar trends."

I agree. Thanks for investigating. I will keep your findings in the back of my head. Once we stumble over a pread/pwrite-heavy Noux application with suffering performance, getting rid of superfluous lseek calls looks like a worthwhile consideration.