Nat! bio photo

Nat!

Senior Mull

Twitter Github Twitch

The Mullocator - Part II - Testing Boundaries continued

Let's fix the thread safety problem from last time and rant on threads in general. That should be enough for one post.

/* assume that this method is called before any call to malloc,
 * sorta like +load, just on a C-level. 
 * Otherwise we'd need to pull the lock initializer into malloc itself
 */
/* malloc.c */
static void  initialize_malloc()
{
    if( pthread_mutex_init( &lock, NULL))
        abort();
}

pthread_mutex_t  lock;
long   memory[ 0x1000000 / sizeof( long)];
long   *sentinel = &memory[ 0x1000000 / sizeof( long)];
long   *buf = memory;

/* malloc.h */
static inline void  *malloc( size_t size)
{
   extern pthread_mutex_t  lock;
   extern long   memory[];
   extern long   *sentinel;
   extern long   *buf;
   void          *p;
   long          *q;
   
   if( pthread_mutex_lock( &lock))  /* can fail, but what to do ? */
      return( NULL);

   p = NULL;
   q = &buf[ (size + sizeof( long) - 1) / sizeof( long)];  
   if( q <= sentinel)
   {
      p   = buf;
      buf = q;
   }

   pthread_mutex_unlock( &lock);  /* fail next malloc..*/
   return( p);
}

When you run both functions against each other the result is

2007-03-27 21:07:37.651 PunishmentByLocks[2320] single-thread compatible
2007-03-27 21:07:38.341 PunishmentByLocks[2320] multi-thread compatible
2007-03-27 21:07:41.932 PunishmentByLocks[2320] done

So that's .349+.341=0.690 for no locks and .659+2.0+.932=3.625 with locks.

The "optimal" malloc just became six times slower, or in other words my G5 with 2.5 GHz seems to be running with 400KHz...

This code is terrible. Unfortunately as people have come to expect, that you can malloc a memory area in one thread and free it in another, there seems to be no good way around it except optimizing the locking operation itself by inlining the locking code.

This leads me to the following rant (yeah! :)):

Threads are stupid and should go

When people wanted to do several things on a computer, bearded UNIX guys or MULTICS guys or whoever, invented the process. It soon became apparent that it would be a really good thing to memory protect a processes memory space, so that programs don't crash each other willy-nilly (see Mac OS 9).

Generally it was and is a nightmare to have two processes running and communicating. The bearded UNIX guys gave us pipes and communication over filedescriptors for processes, some UNIXes had shared memory and semaphores. The interprocess communication between processes is a royal pain in the ass. Ever sent a serialized object over a socket ? Was RPC (Ugh!) easy to program ?

Threads came into being, because people wanted something more lightweight. Threads are more lightweight than processes, since there is no memory protection between threads. Therefore the context switching becomes cheaper, because the virtual memory page tables need not be exchanged in the MMU/CPU. Since threads are fairly easy to implement even in user space they just happened without Operating System support at first.

Why did they people want something more lightweight ? I suspect this was chiefly because of windowed GUIs, where it is sort of a necessity for multiple windows.

Of course threads now crash other threads willy-nilly and something else has been lost too (see above)...

What should have been done is this: create very easy to use process interfaces, that are provided by the Operating System. User code should run without locks, basically the user programmer should not even need to know the concept of a lock. When accessing shared-process objects (NSWindow would be a good candidate), the locking should be done for him automatically. Something like Distributed Objects just even simpler, just on the localhost, possibly just within the same process "family".


Ok enough of the rant. Threads are a reality and must be dealt with. Even if we substitute the pthread code with inlined spinlocks, this is a toll we have to pay.