Issue 4-46, November 17, 1999

Be Engineering Insights: The Hidden Cost of Things

By Pierre Raynaud-Richard

"Be creates software platforms that enable rich media and web experiences on personal computers and Internet appliances."

You've probably seen statements similar to this many times during the last few months. In engineering terms, the last two words—"Internet appliances"—imply a new and significant constraint: memory footprint. This is because many Internet appliances have no hard disk. Rather, they have only a small, compact flash, usually with an IDE interface, and only a limited amount of memory. Typically, this means 16-32MB of main memory, and 8-16MB of storage space, at least for devices trying to be much more than a cellular phone, text-based Web browser. As you can imagine, swap space isn't even an option with devices such as these.

One consequence is that, more than ever, you need to be careful about memory usage. Unfortunately, this is often difficult to measure, since things can be shared by multiple teams or hidden by VM or heap fragmentation. Also, there are two types of memory allocation: visible and hidden. Visible memory allocation stores user data (like the buffer of a Bitmap, the messages of a MessageQueue, the points of a Polygon, and so on). Hidden memory allocation is used by BeOS objects and components themselves, so you don't really know what's there. And that's what this article is about—measuring the memory footprint of empty objects, or how much does it cost to set up a mechanism even before sending any data through it...

But enough talk, here are the numbers (tested under R4.5):

<less than 16 bytes range>
1 or 2 byte(s) per 1K for areas:
VM maps each 4KB page using a 4 byte value. For very large areas, a
second word may also be allocated for virtual page info (some are
preallocated at boot time).

<16 to 32 bytes range>
creating a BMessenger
creating an empty BPolygon

<32 to 64 bytes range>
creating a port
creating a semaphore with short names (<= 15 chars)
creating a BMessageFilter filtering a single command code
creating a BFont

<64 to 128 bytes range> creating a semaphore with long names (<= 31
chars): as a general comment, long names are not free (no miracles here)
creating a BHandler
creating an empty BPicture
creating an empty BMessageQueue
creating an empty BShape
creating a BLocker: please notice it's a bit more expensive than a
semaphore. Not a big issue if you don't use a lot of them...
creating an empty BMessage

<128 to 256 bytes range>
~150 bytes: creating an empty BRegion

<256 to 512 bytes range>
~350 bytes: creating a bitmap (not accepting view), not counting the
buffer itself
~375 bytes: creating an area (not including the 4 bytes per 4K page
previously discussed)

<1KB to 2KB range>
~1600 bytes: creating a view
~1750 bytes: creating a view and setting a font context. As you can see,
the font context on the server side is optional and gets added lazily.

<16KB and more...>
~16 1/4 K: spawning a thread. At this point, the main hit is the kernel
stack (16K of locked memory).
~20 1/4 K: spawning and resuming a thread. At this point, the thread
starts to use another 4K page for the user stack.
~20 3/4 K: creating and running a BLooper. That's essentially the cost of
having a thread waiting to dispatch messages.
~32 K: creating a bitmap accepting view, not counting the buffer itself.
Most of it is used by the thread on the server side handling the drawing
commands. That's also the reason why you can't have too many bitmap
accepting views (the app_server team runs out of thread).
~34 K: creating a window, without running the looper. This only creates
one of the two threads required by a fully functional window.
~37 K: creating a TCP socket. This includes a server side thread and some
buffers.
~56 K: creating a window and running the looper by hand (you don't need
to do that usually). In this case, we effectively created both threads
(client and server sides). The rest (around 15K) is used by the client
and server side structs.
~70 K: creating a window, and showing it (outside the display area). As
you see, the memory footprint of the window jumps again one more time. At
this point the window is ready to go, but nothing is drawn on the screen
yet...
~79 K: creating a new team (doing nothing, linking against none of the
shared libraries except libroot). It's essentially:
~32K for the MMU tables
~20K for the thread
~16K for loader structures
~8K for team

~214 K: creating a new team with a BApplication object, but doing nothing
else but initialization. As expected, the BApplication is a fairly
respectable beast. Not very surprising, as it includes a couple of
threads, needs a relocated copy of parts of libbe, and provides many
services.

Remember, these numbers don't include whatever memory will be used by the objects once you start using them (and that can be a lot, depending on what you do).


Be Engineering Insights: Building Interdependent Shared Libraries

By Marc Ferguson

If you've ever ported a large UNIX application to BeOS you may have run into the problem of building shared library versions of a set of interdependent static libraries. One way to do this is to build a shareable file for each library that contains only the exported symbols of that library. The symbol files can be used to link the other shared libraries in the set.

Here is a bash script that automates the process. It takes a list of .a files and builds a corresponding .so file for each one. The only thing missing from this listing is the extract_symbols function, which is described later.

TMP_DIR=/tmp/so-builder.$$
GCC_ARGS=-nostart
ARCHIVES=
SYMBOLS=


while [ $# != 0 ]
do
  case "$1" in
    -*) GCC_ARGS="$GCC_ARGS $1";;
    /*.a) ARCHIVES="$ARCHIVES $1";;
    *.a) ARCHIVES="$ARCHIVES 'pwd'/$1";;
    *) GCC_ARGS="$GCC_ARGS $1";;
  esac
  shift
  done


rm -rf $TMP_DIR
mkdir $TMP_DIR
pushd $TMP_DIR


for arch in $ARCHIVES; do
  tail=${arch##*/}
  base=${tail%.a}
  extract_symbols $arch >$base.s
  soname="-Xlinker -soname=$base.so"
  echo gcc -nostdlib $soname -o $base.syms $base.s
  gcc -nostdlib $soname -o $base.syms $base.s
  SYMBOLS="$SYMBOLS $base.syms"
done


for arch in $ARCHIVES; do
  ar x $arch
  echo gcc $GCC_ARGS -o ${arch%.a}.so *.o $SYMBOLS
  gcc $GCC_ARGS -o ${arch%.a}.so *.o $SYMBOLS
  rm -f *.o
done


popd
rm -rf $TMP_DIR

The while loop scans the shell arguments, putting an absolute path to each .a file in $ARCHIVES. The rest of the arguments are passed to the gcc command which builds the .so files.

In a temporary directory, the first for loop builds a symbol file for each archive by compiling the result of the extract_symbols function. The -soname argument to the linker provides the name of the .so file in which the symbols will ultimately be found.

The second for loop relinks each archive into a shared library using the symbol files just created.

The extract_symbols function takes an archive and creates an assembler file which contains only symbols. One way to do that is to use a sed script on the output of nm:

extract_symbols () {
  nm $1 | sed -n '
    s/^[0-9a-f]* T \(..*\)/\
      .text\
      .globl \1\
      \1:/p
    s/^[0-9a-f]* D \(..*\)/\
      .data\
      .globl \1\
      .size \1,4\
      \1:/p
    s/^[0-9a-f]* C \(..*\)/\
      .comm \1,4,4/p'
}

Developers' Workshop: Of Pointers, Constructors, and Conversions

By Owen Smith

In this week's installment I intend to put a certain small function in the Be API to rest. Although it was created with the best of intentions, there are several subtle problems with its use, so we're deprecating it. By describing these problems in detail, I hope to illustrate some of the pitfalls of C++ syntax that may be haunting your code.

Our victim for solemn interment today is BMessage::BMessage(BMessage*).

Now, ever since the good old days, this function has lurked in the shadow of its more hygienic partner, the standard BMessage copy constructor: BMessage::BMessage(const BMessage&). Recall that the copy constructor is called in either of the following two cases:

BMessage a;
BMessage b(a);     // copy constructor specified explicitly
BMessage c = a;    // also calls the copy constructor,
                   // NOT operator=() !

This works, looks nice, and is the C++ Approved Way of Doing Things. So why have a redundant variant that takes a BMessage* instead of a const BMessage&? In a word, sloth. Consider the following code that uses the copy constructor:

MyHandler::MessageReceived(BMessage* msg)
{
     BMessage tmpMsg(*msg);
     // do something with tmpMsg
}

With BMessage(BMessage*) we could get away with this:

MyHandler::MessageReceived(BMessage* msg)
{
     BMessage tmpMsg(msg);
     // do something with tmpMsg
}

As you can see, by using the BMessage(BMessage*) constructor, instead of the widely acclaimed copy constructor, we get one really minimal gain -- we save an asterisk. (Note that dereferencing the pointer here and taking a reference does not incur a performance penalty, as the compiler can easily optimize it.) Nevertheless, since much of the Be API centers around passing pointers to BMessages around, it seems the most natural way to express the copying of one message to another. So, its use is pandemic in both our code and—I suspect—in many of your apps as well.

Now, where are the subtle problems I spoke of? First, there is the question of constness. Since copying a BMessage does not alter the BMessage, we should be able to pass a const BMessage* into the constructor. Sadly, though, we overlooked this in the initial implementation of the BMessage API, and we can't fix it because of backwards compatibility issues. So if you have a const BMessage, you have to work around this bug in the API by casting away the constness of your BMessage:

const BMessage* a;
BMessage b(const_cast<BMessage*>(a));

The C++-style cast I've used here looks ugly for a reason: a const_cast is a potentially dangerous operation, and should not be invoked carelessly. Because you're bypassing the compiler's enforcement of constness, you have to blindly trust our claim that the BMessage constructor doesn't modify the original message. Because the regular BMessage copy constructor handles const correctly, it's much easier to use in this case.

Secondly, there are a couple of cases where a subtle error in your code will result in an unwanted side effect which is difficult to debug. First, there is the following code:

BMessage msg = new BMessage;

In this case, I forgot to type an asterisk, and would hope that the compiler would catch the error. But not only does this code compile, it seems to run fine!

Why does this compile? It compiles because initialization invokes the copy constructor:

BMessage msg(new BMessage);

There are two undesirable results of this code:

  1. There is a redundant copy of data from the message on the heap to msg.

  2. The pointer to the message you just created goes out of scope when the constructor exits, meaning that you've just leaked memory.

If this wasn't bad enough, things get even worse. There is also the possibility of unwanted implicit conversions.

As a quick review of implicit conversions, recall that a constructor with a single argument defines an implicit conversion from the argument type to the class type. For example, you can define a conversion from a double to a complex number in the following manner:

struct Complex {
     Complex(double aReal) : real(aReal), imag(0.0) {}
     double real;
     double imag;
};

Then, you can write the following:

double mag(Complex c)
{
     return sqrt(c.real*c.real + c.imag*c.imag);
}

double a;
double aMag = mag(a);

because the double will automatically be converted into a Complex object for you.

Let's say we have the following function which takes a reference to BMessage and modifies the message:

void FillMessage(BMessage& msg)
{
     msg.AddString("password", "metatron");
}

But when we use this function, we forget that it takes a reference, and we pass it a pointer instead:

BMessage* myMsg = new BMessage;
FillMessage(myMsg);

Again, this is probably a simple typo in your code, and you'd want this code to refuse to compile. But before the compiler rejects this code, it will look for some way to turn your BMessage* into a BMessage&. It can't do this directly, but it can turn a BMessage* into a BMessage, and then it can create a reference to a BMessage from this resulting object. This would be the same as writing:

BMessage* myMsg = new BMessage;
FillMessage(BMessage(myMsg));

So, the code will compile and run with nary a complaint. The subtle and dangerous problem with this conversion, as the latter piece of code illustrates, is that you are creating a reference to a temporarily constructed BMessage, NOT the original message itself. FillMessage ends up modifying a temporary BMessage that gets thrown away, and the original message is left untouched. This is almost certainly not what you want!

Both this problem and the memory leak could be solved by declaring the constructor to be explicit. With the proper constness declared, this would look something like:

class BMessage {
public:
     explicit BMessage(const BMessage*);
     ...
};

This prevents the constructor from being used in implicit conversions and initialization—you have to call the constructor explicitly. However, between these issues, the const issue, and redundancy, we chose to deprecate this constructor instead.


With a Name Like Be...

By Jean-Louis Gassée

We're bound to run into questions, some teasing, and the odd bit of trouble. One question I'm often asked is how the name came to Be. (I forgot to mention feeble puns in the preceding list.) When Steve Sakoman and I started the company, I wanted to call it UT, as in the music note the Latin conjunction... and as in United Technoids. Steve was his usual diplomatic self, not voicing such thoughts as "here we go again." Instead, he offered to investigate other options by looking through the dictionary with his kids over the weekend.

The following Monday, I called Steve from the road. How did it go? We stopped at "be," he said. The letter "b," I asked? No, "to be," he replied. And that was it. We looked for conflicting names and, to our surprise, found none we thought might be troublesome. I fired up MacDraw II and typed the two letters of the logo in 72-pt. Times Roman. The spacing between the "B" and the "e" looked awful. However, by typing each letter as a separate object and using the left cursor key, I was able to arrange the letters just so, and we had our first logo.

As for name conflicts, we'd been a little too sanguine. One company, Better Education, used its initials publicly. We visited them and reached an amicable arrangement. We'd chosen Be Labs for our legal name, because it sounded good to us. Bell Labs agreed that it sounded good; in fact, they thought it sounded too close to their former, prestigious name. We deferred, particularly since we didn't want a misunderstanding at a time when we were working with AT&T's Hobbit microprocessor. So Be Incorporated became our legal name and we kept Be for our logo and "common" name.

Naturally, we want to protect the value of our name, of our brand. This is a lot easier with a name like Exxon or Hewlett-Packard than with a word as common as "Be." If you call your windows replacement business Better Windows, chances are the Redmond company will leave you alone. But if you start a Windows management software company, or, worse, a Windows management utility business, calling it Better Windows could get you into trouble. Apple, for example, has had no end of trouble dealing with the use of "Mac" in the names of products, services, magazines, newsletters, stores...

Popularity is not a bad problem to have though, and on a smaller scale, we see good news in the occasional problematic use of our name. (Osborne has no such trouble.) Recently, we came to an amiable arrangement with BeComputing. The name was obviously meant to indicate support of the Be platform, but there was a problem with the perception it could create. BeDepot is part of Be, Inc., but what about BeComputing? In fact, it's not affiliated with us.

Fortunately, Eric Schlissel, the CEO of BeComputing, understood our concern. That is, confusion, in one way or another, lowers the value of the brand. This, in turn, hurts everyone associated with the platform -- probably what sages mean when they speak of "brand equity." BeComputing will rename itself Geek Teknologies. We appreciate their understanding of our efforts as well as their support of the Be platform.

And now, off to Comdex.

Creative Commons License
Legal Notice
This work is licensed under a Creative Commons Attribution-Non commercial-No Derivative Works 3.0 License.