NOTE:This blog had a good run, but is now in retirement.
If you enjoy the content here, please support Gregory's ongoing work on the Practicing Ruby journal.

USP: Avoiding system calls

2011-09-15 00:34, written by Eric Wong

Eric Wong wrote:

As mentioned before, syscalls are the interface user space interacts with kernel space. When a user space application makes a syscall, it is telling the kernel to execute code on its behalf.

The mode switch from user space to kernel space has more overhead and is slower than a normal library function call1.

Thus user space can (and will often attempt to) aggregate several user space calls into fewer system calls to avoid this switching overhead. This is a common concept found in user space code and Ruby itself is no exception. This aggregation often does not happen transparently, so it should be understood and explained to avoid confusion.

I/O Buffering

As a Ruby programmer, you’ll notice the IO class (and subclasses like File) will buffer data you write and you need to call IO#flush or set “IO#sync = true” to ensure other processes can read it.

If you’re a C programmer, you’ll know the stdio library can do the same type of buffering in user space. In fact, MRI 1.8 used the stdio library internally for its user space buffering needs.

Kernel space may also implement its own buffering to avoid overhead when interacting with the storage and network layers. This buffering can be influenced in some cases from Ruby. We’ll cover this later.

Memory Allocation

While Ruby programmers do not often worry about memory allocation, sometimes the following question comes up:

Why did my Ruby process stay so big even after I’ve cleared all references to big objects? I’m /sure/ GC has run several times and freed my big objects and I’m not leaking memory.

A C programmer might ask the same question:

I free()-ed a lot of memory, why is my process still so big?

Memory allocation to user space from the kernel is cheaper in large chunks, thus user space avoids interaction with the kernel by doing more work itself.

User space libraries/runtimes implement a memory allocator (e.g.: malloc(3) in libc) which takes large chunks of kernel memory2 and divides them up into smaller pieces for user space applications to use.

Thus, several user space memory allocations may occur before user space needs to ask the kernel for more memory. Thus if you got a large chunk of memory from the kernel and are only using a small part of that, that large chunk of memory remains allocated.

Releasing memory back to the kernel also has a cost. User space memory allocators may hold onto that memory (privately) in the hope it can be reused within the same process and not give it back to the kernel for use in other processes.

1 Why this is expensive outside the scope of this list so explaining this is left as an exercise for the reader :)

2 Via brk(2), sbrk(2), mmap(2) or various non-portable methods.

License: GPLv3 (or later, at the discretion of Eric Wong)

blog comments powered by Disqus