716 lines
46 KiB
HTML
716 lines
46 KiB
HTML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||
<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><title>Be Newsletters - Volume 3: 1998</title><link rel="stylesheet" href="be_newsletter.css" type="text/css" media="all" /><link rel="shortcut icon" type="image/vnd.microsoft.icon" href="./images/favicon.ico" /><!--[if IE]>
|
||
<link rel="stylesheet" type="text/css" href="be_newsletter_ie.css" />
|
||
<![endif]--><meta name="generator" content="DocBook XSL Stylesheets V1.73.2" /><link rel="start" href="index.html" title="Be Newsletters" /><link rel="up" href="volume3.html" title="Volume 3: 1998" /><link rel="prev" href="Issue3-29.html" title="Issue 3-29, July 22, 1998" /><link rel="next" href="Issue3-31.html" title="Issue 3-31, August 5, 1998" /></head><body><div id="header"><div id="headerT"><div id="headerTL"><a accesskey="p" href="Issue3-29.html" title="Issue 3-29, July 22, 1998"><img src="./images/navigation/prev.png" alt="Prev" /></a> <a accesskey="u" href="volume3.html" title="Volume 3: 1998"><img src="./images/navigation/up.png" alt="Up" /></a> <a accesskey="n" href="Issue3-31.html" title="Issue 3-31, August 5, 1998"><img src="./images/navigation/next.png" alt="Next" /></a></div><div id="headerTR"><div id="navigpeople"><a href="http://www.haiku-os.org"><img src="./images/People_24.png" alt="haiku-os.org" title="Visit The Haiku Website" /></a></div><div class="navighome" title="Home"><a accesskey="h" href="index.html"><img src="./images/navigation/home.png" alt="Home" /></a></div><div class="navigboxed" id="naviglang" title="English">en</div></div><div id="headerTC">Be Newsletters - Volume 3: 1998</div></div><div id="headerB">Prev: <a href="Issue3-29.html">Issue 3-29, July 22, 1998</a> Up: <a href="volume3.html">Volume 3: 1998</a> Next: <a href="Issue3-31.html">Issue 3-31, August 5, 1998</a></div><hr /></div><div class="article"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h2 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="Issue3-30"></a>Issue 3-30, July 29, 1998</h2></div></div></div><div class="sect1"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h2 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="Engineering3-30"></a>Be Engineering Insights: "Threads Don't Like Playing Ping Pong," Part I</h2></div><div xmlns:d="http://docbook.org/ns/docbook"><span xmlns="http://www.w3.org/1999/xhtml" class="author">By <span class="firstname">Pierre</span> <span class="surname">Raynaud-Richard</span></span></div></div></div><p>
|
||
The main purpose of the engineering articles is to deliver simple and
|
||
accurate technical information that's meaningful to as many people as
|
||
possible. Thus, simple articles are better than complex ones—a rule I
|
||
follow as often as I can.
|
||
</p><p>
|
||
But not today.
|
||
</p><p>
|
||
With apologies to the average developer who is never going to deal with
|
||
the subtleties I'm about to discuss, I just can't stop myself! After many
|
||
general information articles on various subjects, I felt an irresistible
|
||
urge to write some strong technical content. You've been warned, now
|
||
continue at your own risk.
|
||
</p><p>
|
||
At the heart of the BeOS graphic system lies the application server. It's
|
||
been the theater of continuous cleaning, reorganization and optimisation
|
||
during the last 12 months, and more will happen, at least until Release 6.
|
||
</p><p>
|
||
Recently, we discovered a nasty interaction between the app server and
|
||
the kernel's thread scheduler. This problem had very serious
|
||
consequences, and solving it has brought a significant performance
|
||
improvement to Release 4. It also pointed out an interesting class of
|
||
problems, that some of you could encounter in the future (if not already).
|
||
</p><p>
|
||
So in this article, we are going to study different mechanisms you can
|
||
use to synchronize threads, and try to describe their behavior under
|
||
well-defined stress conditions. For that we used one or two "worker"
|
||
threads, executing the same reference task again and again. They had to
|
||
use a synchronization mechanism to guarantee that only one thread
|
||
executed the task at a time (from now on, the reference task will be
|
||
called the "critical section").
|
||
</p><p>
|
||
To simulate the <acronym class="acronym" title="Central Processing Unit">CPU</acronym>
|
||
noise generated by other unrelated threads, we
|
||
sometimes added an additional dummy thread. The dummy thread didn't vie
|
||
for the critical section—in other words, it didn't interfere directly
|
||
with the other two threads.
|
||
</p><p>
|
||
All measurements were done on a Pentium II 350MHz in conditions as stable
|
||
and similar as possible, with most of the servers, applications and
|
||
services disabled, but with some daemons left running. In this
|
||
environment, all code that we executed was always available in the level
|
||
2 cache (if not in the level 1!). In the real world, of course, this
|
||
isn't a realistic assumption, but as you will see, studying the perfect
|
||
case is hard enough already...
|
||
</p><div class="sect2"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h3 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="id718129"></a>TEST 1—An Ideal World</h3></div></div></div><p>
|
||
In this test, one <code class="constant">B_NORMAL_PRIORITY</code> thread repeatedly executed a 6.1us
|
||
("us" = "microsecond") task that's protected by a simple semaphore:
|
||
</p><pre class="programlisting c">
|
||
while (<code class="constant">true</code>) {
|
||
<code class="function">acquire_sem</code>(<code class="varname">g_sem</code>);
|
||
<code class="function">do_critical_section</code>(6.1);
|
||
<code class="function">release_sem</code>(<code class="varname">g_sem</code>);
|
||
}
|
||
</pre><p>
|
||
We ran the loop for about two seconds and recorded the latency (time
|
||
consumption) for each lock and for each unlock. We then plotted the
|
||
results on separate graphs (i.e. one for locking, another for unlocking).
|
||
These graphs represent a very rich source of information, and we're going
|
||
to be studying them throughout most of this article.
|
||
</p><p>
|
||
The graph's horizontal axis is latency in microseconds; the vertical axis
|
||
is the hit count, or the number of locks/unlocks at a particular latency.
|
||
Both axes are exponential and elided where necessary (to fit the format
|
||
of an email, and reduce the length of this article). All graphs are
|
||
supposed to be viewed with a monospaced font.
|
||
</p><p>
|
||
This first graph shows the locking latency:
|
||
</p><pre class="screen">
|
||
-------------One thread, semaphore, locking-------------
|
||
32768| A
|
||
| AA
|
||
16384| AA
|
||
| AA
|
||
8192| AA
|
||
| AA
|
||
4096| AA
|
||
| AAA
|
||
. ...
|
||
| AAA
|
||
128| AAA
|
||
| AAA B
|
||
64| AAA BB
|
||
| AAA BB
|
||
32| AAA BBBB
|
||
| AAA BBBB
|
||
16| AAA BBBB
|
||
| AAA BBBBB C
|
||
8| AAA BBBBB C
|
||
| AAAA BBBBB C
|
||
4| AAAA BBBBB CC
|
||
| AAAA BBBBB CC
|
||
2| AAAA BBBBB # CCC # #
|
||
| AAAABBBBBB ## ## CCC # # ### # #
|
||
+!-------!-------!-------!-------!-------!-------!-------!--...--!--
|
||
0 8 16 32 64 128 256 512 2048
|
||
</pre><ul class="itemizedlist"><li><p>
|
||
The (A) spike shows the case where the semaphore is acquired without
|
||
blocking. This happens 99.6% of the time with an average latency of
|
||
3.5us.
|
||
</p></li><li><p>
|
||
In the (B) spike, a scheduler interruption occurs while <code class="function">acquire_sem()</code>
|
||
is executing, but without rescheduling (i.e. <code class="function">acquire_sem()</code> continues
|
||
after the interruption). Probability: 0.3%; average latency: 8.5us. So
|
||
both (A) and (B) represent the same phenomena, except that (B) is
|
||
delayed 5.0us by the scheduler interruption. This delay is only a part
|
||
of the more generic "scheduler noise," and so it doesn't represent any
|
||
relevant information. In further comments, we will call that phenomena
|
||
"scheduler echo"; we will identify it when needed, but we won't spend
|
||
any more time commenting about it.
|
||
</p></li><li><p>
|
||
(C) is another component of the noise, weaker than the "scheduler
|
||
echo," but with an higher latency. It's probably the signature of a
|
||
daemon (most likely a real-time priority thread) that runs regularly
|
||
(from example 60 times a second) for a short amount of time (15 to
|
||
20us). Once added with the two necessary context switch latencies, that
|
||
leaves us with what we will call the "daemon echo." In this case it's
|
||
an "echo" of (A), with an addi- tional latency of 27us.
|
||
</p></li><li><p>
|
||
What's left—the scattered "#"s—is the rest of the noise
|
||
generated by either the scheduler or some other daemons whenever they
|
||
used some CPU. Although the noise delay can reach 3000us, this is
|
||
actually reasonable since it reflects quite accurately the maximum
|
||
scheduling quanta. This contains even less relevant information than
|
||
the two previously described "echoes," so from now we won't pay any
|
||
attention to it.
|
||
</p></li></ul><p>
|
||
Here's the graph for unlocking :
|
||
</p><pre class="screen">
|
||
-------------One thread, semaphore, unlocking------------------
|
||
32768| A
|
||
| AA
|
||
16384| AA
|
||
| AA
|
||
8192| AA
|
||
| AA
|
||
4096| AA
|
||
| AAA
|
||
. ...
|
||
| AAA
|
||
256| AAA
|
||
| AAA B
|
||
128| AAA BB
|
||
| AAA BBB
|
||
64| AAA BBBB
|
||
| AAA BBBBB
|
||
32| AAA BBBBB
|
||
| AAA BBBBB
|
||
16| AAA BBBBB C
|
||
| AAA BBBBB C
|
||
8| AAA BBBBB CC
|
||
| AAA BBBBBB CC
|
||
4| AAAA BBBBBB # CCC
|
||
| AAAA BBBBBB ## CCCC
|
||
2| AAAABBBBBBB ## CCCC ## # # #
|
||
| AAAABBBBBBB### CCCCC ## ### # # #
|
||
+!-------!-------!-------!-------!-------!-------!-----...--!-----
|
||
0 8 16 32 64 128 256 2048
|
||
</pre><ul class="itemizedlist"><li><p>
|
||
(A) Again, non-blocking acquisition is the overwhelming winner:
|
||
99.06% and 3.5us.
|
||
</p></li><li><p>
|
||
(B) "scheduler echo" of (A), has a higher probability (0.86%) than in
|
||
the analogous locking test, but that's because the unlocking
|
||
measurement includes the noise associated with both the unlocking and
|
||
the critical section. This is an artifact of measuring the locking and
|
||
unlocking with only two timestamps :
|
||
</p><pre class="programlisting c">
|
||
while (<code class="constant">true</code>) {
|
||
<code class="function">TIME_STAMP</code>();
|
||
<code class="function">acquire_sem</code>(<code class="varname">g_sem</code>);
|
||
<code class="function">TIME_STAMP</code>();
|
||
<code class="function">do_critical_section</code>(6.1);
|
||
<code class="function">release_sem</code>(<code class="varname">g_sem</code>);
|
||
}
|
||
</pre><p>
|
||
This allowed us to reduce the impact of time-measurement on the system,
|
||
as we can just subtract the latency of the critical section from the
|
||
second latency to get the unlocking latency. But there was no way to
|
||
avoid increasing the magnitude of the noise. Still, it's only noise --
|
||
as long as we can identify it (and ignore it), we don't care about its
|
||
magnitude.
|
||
</p></li><li><p>
|
||
(C) is the "daemon echo" of (A).
|
||
</p><p>
|
||
Round trip: More than 99% of the time, a trip through the entire loop
|
||
(acquire the semaphore, perform the critical section, release the
|
||
semaphore), takes about 13.1us (3.5 acquisition, 6.1 critical section,
|
||
3.5 release).
|
||
</p></li></ul></div><div class="sect2"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h3 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="id718343"></a>TEST 2—An Idealer World</h3></div></div></div><p>
|
||
We replaced the semaphore with a benaphore. You can look at one of the
|
||
previous Engineering Insights articles for more detailed information
|
||
about the benaphore:
|
||
<a class="xref" href="Issue1-26.html#Engineering1-26" title="Be Engineering Insights: Benaphores">Be Engineering Insights: Benaphores</a>.
|
||
</p><pre class="programlisting c">
|
||
while (<code class="constant">true</code>) {
|
||
if (<code class="function">atomic_add</code>(&<code class="varname">g_lock</code>, 1) > 0)
|
||
<code class="function">acquire_sem</code>(<code class="varname">g_ben</code>);
|
||
<code class="function">do_critical_section</code>(6.1);
|
||
if (<code class="function">atomic_add</code>(&<code class="varname">g_lock</code>, -1) > 1)
|
||
<code class="function">release_sem</code>(<code class="varname">g_ben</code>);
|
||
}
|
||
</pre><pre class="screen">
|
||
-------------One thread, benaphore, locking-------------
|
||
65536|A
|
||
|A
|
||
32768|AA
|
||
|AA
|
||
...
|
||
|AA
|
||
16|AA B
|
||
|AA BBB
|
||
8|AA BBB
|
||
|AAA BBB
|
||
4|AAA BBB
|
||
|AAA BBBBB C
|
||
2|AAA BBBBB CC
|
||
|AAA BBBBB CC #
|
||
+!-------!-------!-------!-------!-------!--
|
||
0 8 16 32 64 128
|
||
</pre><p>
|
||
As expected, the probability and timing are much improved:
|
||
</p><ul class="itemizedlist"><li><p>
|
||
(A) represents the main and fast case of the benaphore (when we don't
|
||
have to take the semaphore at all), with an outrageously good
|
||
probability of 99.96%. Second good news, as expected the latency is
|
||
down to 0.3us (12x faster than the semaphore).
|
||
</p></li><li><p>
|
||
(B) is the "scheduler echo" of (A). Its probability is very low
|
||
because (A) runs for such a short time that it's rarely interrupted by
|
||
the scheduler.
|
||
</p></li><li><p>
|
||
Same thing for (C), a very weak "daemon echo" of (A).
|
||
</p></li></ul><pre class="screen">
|
||
-------------One thread, benaphore, unlocking-------------
|
||
65536|A
|
||
|AA
|
||
...
|
||
|AA
|
||
256|AAA BB
|
||
|AAA BBB
|
||
128|AAA BBB
|
||
|AAA BBB
|
||
64|AAA BBBB
|
||
|AAA BBBB
|
||
32|AAA BBBB
|
||
|AAA BBBBB C
|
||
16|AAA BBBBB CC
|
||
|AAA BBBBB CC
|
||
8|AAA BBBBB CC
|
||
|AAA BBBBBB CC
|
||
4|AAA BBBBBB CCC
|
||
|AAA BBBBBBB CCC #
|
||
2|AAA BBBBBBBBB CCC ## ## # # #
|
||
|AAA BBBBBBBBBBB ## # CCCC ## ## ### ## # #
|
||
+!-------!-------!-------!-------!-------!-------!-----...--!-----
|
||
0 8 16 32 64 128 256 2048
|
||
</pre><ul class="itemizedlist"><li><p>
|
||
(A) is the logical mirror of the previous (A), with a similarly good
|
||
latency of 0.3us.
|
||
</p></li><li><p>
|
||
(B) "scheduler echo" of (A), is stronger than the previous (B). This
|
||
was expected, because we get the noise for both the unlocking and the
|
||
critical section.
|
||
</p></li><li><p>
|
||
Same comment for both (C), the "daemon echo" of (A), and (#) the
|
||
other noise.
|
||
</p></li></ul><p>
|
||
Round-trip: Greater than 99% probability of a 6.7us total round-trip --
|
||
pretty good for a 6.1us critical section. But, then again, this is an
|
||
ideal—and so unrealistic—test.
|
||
</p><p>
|
||
NOTE: In all our tests, the unlocking statistics are mostly similar to
|
||
one or the other of the two already shown. Unlocking never blocks so, as
|
||
you might expect, it doesn't really affect overall system performance. So
|
||
as there isn't too much to be learned from those, we won't show any
|
||
unlocking graphs for any of the other tests, but will only make some
|
||
comments when necessary.
|
||
</p></div><div class="sect2"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h3 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="id718489"></a>TEST 3—A Small Dose of Reality</h3></div></div></div><p>
|
||
Let's see what happens when we introduce a second <code class="constant">B_NORMAL_PRIORITY</code>
|
||
thread. We'll have two equivalent threads fighting over the lock. First,
|
||
we use a semaphore:
|
||
</p><pre class="screen">
|
||
-------------Two threads, semaphore, locking-------------
|
||
16384| A
|
||
. .
|
||
| A
|
||
2048| AA
|
||
| B AA
|
||
1024| BB AA
|
||
. .. ..
|
||
256| BB AA C
|
||
| BB AA CC
|
||
128| BBB AA CC
|
||
| BBB AA CC
|
||
64| BBB AA CC
|
||
. ... .....
|
||
16| BBB AACCC D
|
||
| BBB AACCCC D
|
||
8| BBB AACCCC DD # #
|
||
| BBB AACCCC DD # #
|
||
4| BBB # AACCCC DDD # # #
|
||
| BBB # AACCCC DDDD # # # #
|
||
2| BBB # # AACCCCC DDDDD # ## # #
|
||
| BBB # ## ##AAAACCCCCDDDDDD ## ## # ## # #
|
||
+!-------!-------!-------!-------!-------!-------!-----...--!----
|
||
0 8 16 32 64 128 256 2048
|
||
</pre><p>
|
||
NOTE: This is a graph of only one of the threads. The profile of the
|
||
other thread is very similar, as expected, with the same spikes in term
|
||
of both probability and latency:
|
||
</p><pre class="screen">
|
||
(A) : 24.138us (85.677%) against 24.168us (85.285%)
|
||
(B) : 3.545us (11.741%) against 3.543us (12.159%)
|
||
(C) : 29.059us ( 2.303%) against 28.878us ( 2.236%)
|
||
(D) : 52.718us ( 0.137%) against 51.378us ( 0.157%)
|
||
</pre><p>
|
||
So from now on, we won't comment about the second thread at all.
|
||
</p><ul class="itemizedlist"><li><p>
|
||
(A) The most common case is no longer unblocked acquisition. Instead,
|
||
the threads are starting to "ping pong": 85.6% of time, the thread must
|
||
wait to acquire the semaphore, with a latency of 24.1us. That time
|
||
includes the first thread blocking, a context switch, the second thread
|
||
unblocking, the second thread executing the critical section, the
|
||
second thread blocking, another context switch and finally our first
|
||
thread unblocking. That also implies that the context switch takes
|
||
about 5us, a very impressive number, but not very realistic as in this
|
||
case everything clearly happens in the level 1 cache.
|
||
</p></li><li><p>
|
||
(B) is the unblocked acquisition spike. The latency is logically the
|
||
same as in the one-thread test (3.5us), but the probability has dropped
|
||
to 11.7%. This is easy to explain: whenever the scheduler suspends both
|
||
threads in a state where no one owns the semaphore, whatever thread
|
||
starts first is free from any synchronization issue, as if it was
|
||
alone. And that lasts until the scheduler suspends it again, which most
|
||
likely puts the system back into "ping pong" mode.
|
||
</p></li><li><p>
|
||
(C & D) are the "scheduler echo" and "daemon echo" of (A).
|
||
</p></li></ul><p>
|
||
Round-trip: 24.1 + 6.1 + 5.0 = 35.1us for 7/8 of the iterations. The
|
||
"ping pong" clearly affected the performance, but it's not a total
|
||
disaster compared to the 13.1 * 2 (don't forget, now we have 2 threads
|
||
working in parallel!) = 26.2us of the semaphore.
|
||
</p></div><div class="sect2"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h3 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="id718563"></a>TEST 4—The Benaphore Curiosity</h3></div></div></div><p>
|
||
Mostly out of curiosity, let's see what happens when we use a benaphore
|
||
in the two threads context.
|
||
</p><pre class="screen">
|
||
-------------Two threads, benaphore, locking-------------
|
||
|
||
16384| A
|
||
. .
|
||
| A
|
||
2048|B AA
|
||
|B AA
|
||
1024|BB AA
|
||
|BB AAA
|
||
512|BB C AAA
|
||
|BB C AAA
|
||
256|BB CC AAA D
|
||
... .. ... .
|
||
32|BB CC AAADD
|
||
|BBCCC AAADD
|
||
16|BBCCC AAADD E
|
||
|BBCCC AAADDD E
|
||
8|BBCCC AAADDD # E
|
||
|BBCCC AAADDD # EE
|
||
4|BBCCC AAADDD #EEE
|
||
|BBCCC # AAADDD #EEE # #
|
||
2|BBCCCC # AAAADDD #EEE # ### #
|
||
|BBCCCC ## AAAAADDDD #EEE ####### # # ## ##
|
||
+!-------!-------!-------!-------!-------!-------!-----...--!----
|
||
0 8 16 32 64 128 256 2048
|
||
</pre><ul class="itemizedlist"><li><p>
|
||
(A, D & E) The ping pong mode and its two "echoes" comprise 82% of
|
||
the iterations with a latency of 24.7us (+5.0us and +27.0us for the
|
||
echoes).
|
||
</p></li><li><p>
|
||
(B) Non-blocking acquisition comprises 14.3% at 0.31us (a result
|
||
similar to the previous non-blocking acquisition for two threads using
|
||
a semaphore, but just faster as we have a benaphore now).
|
||
</p></li><li><p>
|
||
(C) This aberration is the result of a race condition in which the
|
||
benaphore becomes a semaphore: Thread A sets the benaphore counter (bc)
|
||
to 1 and enters the critical section. Thread B comes along, sets bc to
|
||
2, and heads for <code class="function">acquire_sem()</code>—BUT—the scheduler switches to
|
||
thread A before thread B gets to actually invoke <code class="function">acquire_sem()</code>. Thread
|
||
A leaves the critical section, decrements bc to 1 and calls
|
||
<code class="function">release_sem()</code>. Since no one is actually waiting for the semaphore
|
||
(since thread B never got that far), thread A sets the semaphore
|
||
counter to 1. After that, thread A increments bc to 2 and call
|
||
<code class="function">acquire_sem()</code>. Since the semaphore has already been released, thread A
|
||
acquires it without blocking. Then it passes through the critical
|
||
section, decrements bc to 1, releases the semaphore... and so on until
|
||
the scheduler decides it's B's turn. In other words, all those
|
||
<code class="function">acquire_sem()</code> and <code class="function">release_sem()</code>
|
||
calls were unnecessary, but they don't
|
||
really hurt all that much (it's still better than playing ping pong).
|
||
</p></li></ul><p>
|
||
As expected, in ping pong mode the benaphore is slightly slower than the
|
||
semaphore (24.7us vs. 24.1us), but the non-blocking case is still much
|
||
faster (0.31us vs. 3.5us). The unlocking test (not shown) also satisfies
|
||
this expectation.
|
||
</p><p>
|
||
Round-trip: A little bit better than the semaphore. The improvement in
|
||
non-blocking performance makes up for the degraded ping pong. In real
|
||
life, it's not clear that we would get a similar behavior, but in any
|
||
case, even if the benaphore gets slower than the semaphore, it won't be
|
||
by much.
|
||
</p></div><div class="sect2"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h3 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="id718657"></a>First part conclusion:</h3></div></div></div><p>
|
||
When two threads have to fight for the ownership of a shared critical
|
||
section, performances can be significantly reduced. The snappy little
|
||
benaphore degenerates into a semaphore, and the CPU efficiency is reduced
|
||
by a small factor—the two-thread tests are 1.4 to 2.5 times less
|
||
efficient than the one-thread tests. This would seem bad enough already,
|
||
but who knows what's going to happen once we move those tests from a
|
||
ideal and isolated environment into a more realistic one. Clearly a story
|
||
for next week...
|
||
</p></div></div><hr class="pagebreak" /><div class="sect1"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h2 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="DevWorkshop3-30"></a>Developers' Workshop: Mucking With Menus</h2></div><div xmlns:d="http://docbook.org/ns/docbook"><span xmlns="http://www.w3.org/1999/xhtml" class="author">By <span class="firstname">Owen</span> <span class="surname">Smith</span></span></div></div></div><div class="sect2"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h3 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="id718701"></a>Foreword</h3></div></div></div><p>
|
||
Harlan Ellison once noted that first impressions are deadly, dangerous,
|
||
and deceiving. Since this is my debut Newsletter article, I'm aware of
|
||
what's at stake. I've had my hair cut. I've shaved my face raw and put a
|
||
styptic pen in my pocket for the nicks. I'm wearing that same collared
|
||
shirt, spiffy tie, and cotton slacks that almost lost me the job when I
|
||
interviewed here. All this so that you'll walk away from this column with
|
||
that warm, fuzzy, BeOS-coding kind of feeling. So much for introductions.
|
||
</p></div><div class="sect2"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h3 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="id718712"></a>Delicatessens, Laminates, and American Consumers</h3></div></div></div><p>
|
||
An odd phenomenon occurs after midnight on summer weekends here in the
|
||
land of suburban sprawl. In eerie emulation of the coastal grunion runs,
|
||
hordes of Hollywood zombies issue forth from their movie theater coffins.
|
||
At that time of night activities are scarce—absent certain chemical
|
||
and hormonal addictions, and/or a zoot suit and the irrepressible urge to
|
||
dance.
|
||
</p><p>
|
||
For the rest of us there's the 24-hour delicatessen. Some go for the
|
||
food; some go to avoid the grunion. Me, I head to the deli to marvel at
|
||
one thing: a laminated, folded sheet of cardboard stock which, when
|
||
unfolded, stands two feet high and three feet wide. This item is not just
|
||
a menu—it's a symbol of the bewildering diversity of American culture,
|
||
of the consumer's RIGHT TO CHOOSE. Confronted by this formidable
|
||
selection, I forego exercising my birthright and always get the corned
|
||
beef Reuben and the vanilla Coke.
|
||
</p><p>
|
||
So what has that got to do with the BeOS, you ask? The connection is
|
||
menus. These are not just the cornerstone of the contemporary GUI; they
|
||
also show off BMessages and object- oriented design to good effect. And,
|
||
they provide a safe, wholesome springboard to the wonderful world of the
|
||
Interface Kit (see the foreword on the Importance of First Impressions).
|
||
</p><p>
|
||
To whet your appetite for menus, I've concocted an example of an
|
||
application that tweaks menus a bit more than the average Be application.
|
||
Appropriately enough, it's called <span class="application">MenuWorld</span>. Go ahead, download it:
|
||
</p><p>
|
||
ftp://ftp.be.com/pub/samples/interface_kit/MenuWorld.zip
|
||
</p><p>
|
||
The following notes boil down the essence of this example.
|
||
</p></div><div class="sect2"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h3 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="id718763"></a>The Four Corners</h3></div></div></div><p>
|
||
Ninety percent of menu behavior is defined in four classes:
|
||
</p><div class="orderedlist"><ol><li><p>
|
||
<code class="classname">BMessage</code>:<br />A command that is executed when a menu item is selected.
|
||
If you're unfamiliar with <code class="classname">BMessage</code>, check The Be Book Application Kit
|
||
documentation:
|
||
</p><p>
|
||
<a class="link bebook" href="../BeBook/BMessage.html">BMessage.html</a>.
|
||
</p><p>
|
||
A romantic relationship awaits you.
|
||
</p></li><li><p>
|
||
<code class="classname">BMenu</code>:<br />
|
||
A subclass of <code class="classname">BView</code> that displays a pop-up or pull- down
|
||
menu. The most commonly used functions here are <code class="methodname">AddItem()</code>,
|
||
<code class="methodname">AddSeparatorItem()</code>, and <code class="methodname">RemoveItem()</code>.
|
||
Note that <code class="methodname">AddItem()</code> takes either a
|
||
single item or a whole submenu. You've seen hierarchical menus before;
|
||
you know the drill. Also note that <code class="classname">BMenu</code>s appear and disappear in
|
||
pop-up <code class="classname">BWindow</code>s, which makes an interesting flourish to apply to other
|
||
pop-up UI widgets—such as tool tips.
|
||
</p></li><li><p>
|
||
<code class="classname">BMenuBar</code>:<br />
|
||
A subclass of <code class="classname">BMenu</code> that specifically displays a
|
||
pull-down menu bar. This is often added to a window in the window's
|
||
constructor, and automatically sizes itself quite nicely.
|
||
</p></li><li><p>
|
||
<code class="classname">BMenuItem</code>:<br />
|
||
Represents one drawable item in the menu. Although it's
|
||
not actually a view (it uses the <code class="classname">BMenu</code> it's contained in for drawing),
|
||
it's capable of invoking a command, as any decent <code class="classname">BInvoker</code> can. This
|
||
is an excellent candidate for subclassing if you've got some custom
|
||
item drawing to do (and I did; see the <span class="application">MenuWorld</span>
|
||
class <code class="classname">BitmapMenuItem</code>).
|
||
</p></li></ol></div></div><div class="sect2"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h3 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="id718901"></a>Primping and Preening</h3></div></div></div><p>
|
||
There are three different kinds of menus as far as drawing goes:
|
||
column-drawn (standard), row-drawn (e.g., menu bars), and custom-drawn
|
||
(called "matrix," though there's no restriction on how the items are
|
||
arranged). You specify which one you're using when you create the menu.
|
||
MenuWorld proudly presents all three methods.
|
||
</p><p>
|
||
If you're arranging menu items in a column, things couldn't be easier.
|
||
The menu works with your menu items to figure out how big to make the
|
||
menu, and how to arrange the items within the menu. The menu also takes
|
||
care of drawing submenu triangles and shortcut symbols. All you need to
|
||
do is create your menu items and add them to the menu. Working with
|
||
row-by-row organization is similar.
|
||
</p><p>
|
||
If you're doing a custom arrangement of items, things get more
|
||
interesting. Here are some points to consider:
|
||
</p><div class="orderedlist"><ol><li><p>
|
||
You need to calculate the bounds of the menu, and the frame of each item in
|
||
the menu. This can't be done directly with standard
|
||
<code class="classname">BMenuItem</code>s, so you'd better be ready to derive your
|
||
own menu item subclass, and provide some ability to calculate the item's
|
||
frame based on either its contents, the menu's dimensions, or both. (Note
|
||
that blindly calling the menu item's
|
||
<code class="methodname">GetContentSize()</code> function won't work—it's
|
||
protected, and can only be called by the <code class="classname">BMenu</code>
|
||
class, not you!) In <span class="application">MenuWorld</span>, the
|
||
<span class="guimenu">Test</span> menu displays icons, whose sizes are well defined,
|
||
so this is not difficult. Each icon is represented by the simple class
|
||
<code class="classname">BitmapMenuItem</code>, and the class
|
||
<code class="classname">TestMenuBuilder</code> does the actual work of building the
|
||
menu items and menu.
|
||
</p></li><li><p>
|
||
The padding that would be inserted around normal <code class="classname">BMenuItem</code>s in a
|
||
column arrangement to take care of the submenu triangles and shortcuts
|
||
is noticeably missing in a row or custom arrangement. So, if you want
|
||
those submenu wedges and shortcuts, or the hefty left and right
|
||
margins that these imply, you're going to need to cook them up
|
||
yourself.
|
||
</p></li></ol></div></div><div class="sect2"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h3 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="id718992"></a>ZPG and Other Population Paradigms</h3></div></div></div><p>
|
||
Stuffing your menus with items is easy, and can be done in several places:
|
||
</p><div class="orderedlist"><ol><li><p>
|
||
For many purposes, it's enough to add the items once at the
|
||
beginning, generally when your window is being built. The <span class="guimenu">File</span> menu in
|
||
<span class="application">MenuWorld</span> is built up this way
|
||
(see <code class="classname">MenuWindow</code>::<code class="methodname">BuildFileMenu()</code>).
|
||
</p></li><li><p>
|
||
Simple item changes traceable to a single action can be done at any
|
||
time. For instance, when the <span class="guibutton">Add Menu</span> button is clicked, the window
|
||
immediately adds the menu to the menu bar. Similarly, when the <span class="guibutton">Delete</span>
|
||
button is pressed while a top-level menu is selected, the window
|
||
immediately removes the menu from the menu bar. You can easily extend
|
||
this approach to swap in and out different menu bars from the window
|
||
as needed (the <acronym class="acronym" title="Object Linking and Embedding">OLE</acronym>
|
||
paradigm of menu-document relations comes hurtling
|
||
to mind, but try doing this in
|
||
<acronym class="acronym" title="Microsoft Foundation Classes">MFC</acronym>
|
||
under any other circumstances!). In
|
||
<span class="application">MenuWorld</span>, clicking on the check
|
||
box <span class="guilabel">Hide User Test Items</span> swaps the
|
||
menu bar that contains the user menu items for a menu bar which
|
||
doesn't.
|
||
</p></li><li><p>
|
||
If you have a fixed set of menus in the menu bar, you may want to
|
||
defer touching up the menu items until the menus are actually being
|
||
shown (i.e., in <code class="classname">BWindow</code>::<code class="methodname">MenusBeginning()</code>). Here you can enable/disable
|
||
items, or even completely build up the contents of menus. If your
|
||
menus have especially volatile contents, or the interactions that
|
||
cause menu items to be enabled or disabled are particularly
|
||
complicated, this is a great way to go. <span class="application">MenuWorld</span> demonstrates the
|
||
approach by dynamically building up the contents of each
|
||
user-specified menu in <code class="classname">MenuWindow</code>::<code class="methodname">MenusBeginning()</code>.
|
||
</p></li></ol></div></div><div class="sect2"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h3 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="id719105"></a>Magick Mother Invocation</h3></div></div></div><p>
|
||
Since <code class="classname">BMenuItem</code>s inherit from
|
||
<code class="classname">BInvoker</code>, you can pull many of the same
|
||
tricks with them as you can with controls and other invokers. In
|
||
particular, your messages can be arbitrarily complex, and you don't need
|
||
to dispatch them to the main window. For instance, the
|
||
<span class="guimenu">File</span>-><span class="guimenuitem">About...</span>
|
||
menu item dispatches to the global application object. Also note that
|
||
each user item contains the same message ID, so they all get handled in
|
||
the same place (a separate field identifies the actual name of the item
|
||
which sent the message); the test items are set up in a similar manner.
|
||
Again, try achieving that same kind of flexibility with
|
||
<acronym class="acronym">MFC</acronym>!
|
||
</p><p>
|
||
There is a price to this flexibility, however. Assuming <span class="application">MenuWorld</span> were a
|
||
full-featured menu editor, let's say we wanted to save the menus we've
|
||
created and use them in our application, à la Visual C++ menu resource
|
||
editing. The Be way to do this is to archive the Menu Bar and its
|
||
constituents into a <code class="classname">BMessage</code>, then write the flattened
|
||
<code class="classname">BMessage</code> data to a
|
||
<code class="classname">BResource</code>s object.
|
||
</p><p>
|
||
The problem, which ties in to a recent missive on BeDevTalk, is that the
|
||
<code class="classname">BMenuItem</code>s do not store their targets—indeed, they have no inherent
|
||
way to identify a target once they've been archived and reconstituted.
|
||
One thing you can do pretty easily is to set the target of each menu item
|
||
to the main window when you instantiate them. However, if you want to set
|
||
special <code class="classname">BMenuItem</code> targets in <span class="application">MenuWorld</span>,
|
||
coming up with an archiving
|
||
mechanism is non-trivial. Some third-party apps on BeWare, such as
|
||
<span class="application">AppSketcher</span> and <span class="application">Interface Elements</span>,
|
||
address this problem:
|
||
</p><p>
|
||
http://www.be.com/beware/Development.html#Cat_Interface%20Creation
|
||
</p><p>
|
||
That's it for this week. I leave you with a German blessing: "Froehlich
|
||
sei das Codeverbessern; guten Appetit!"
|
||
</p></div></div><hr class="pagebreak" /><div class="sect1"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h2 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="BeDevTalk3-30"></a>BeDevTalk Summary</h2></div></div></div><p>
|
||
BeDevTalk is an unmonitored discussion group in which technical
|
||
information is shared by Be developers and interested parties. In this
|
||
column, we summarize some of the active threads, listed by their subject
|
||
lines as they appear, verbatim, in the mail.
|
||
</p><p>
|
||
To subscribe to BeDevTalk, visit the mailing list page on our web site:
|
||
http://www.be.com/aboutbe/mailinglists.html.
|
||
</p><div class="sect2"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h3 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="id719237"></a>NEW</h3></div></div></div><div class="sect3"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h4 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="id719243"></a>Subject: Random not so random?</h4></div></div></div><p>
|
||
The opening parry regarded the proper seeding of the random number
|
||
generator: Call <code class="function">srandom()</code> (or <code class="function">srand()</code>)
|
||
only once, and before any <code class="function">random()</code>
|
||
(<code class="function">rand()</code>) calls. The safest bet is to set the seed in
|
||
<code class="function">main()</code>.
|
||
</p><p>
|
||
Other details popped out:
|
||
</p><ul class="itemizedlist"><li><p>
|
||
The high bits are more random than the low bits. If you want to
|
||
restrict the range of the random number, you should divide rather than
|
||
mask (or mod).
|
||
</p></li><li><p>
|
||
If you want a random number that's bigger than 16 bits, combine
|
||
multiple <code class="function">random()</code> calls.
|
||
</p></li><li><p>
|
||
<code class="function">random()</code> is more random than <code class="function">rand()</code>.
|
||
But is it truly random? Some
|
||
folks think it's about as random as you can get. Others say that
|
||
because the implementation varies between compilers, you shouldn't rely
|
||
on <code class="function">random()</code>. Instead, you should find a good algorithm and roll it
|
||
yourself.
|
||
</p></li></ul></div><div class="sect3"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h4 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="id719320"></a>Subject: objects in areas</h4></div></div></div><p>
|
||
How do you put an object in a shared area so more than one application
|
||
can use it? For example, how do you share a <code class="classname">BBitmap</code>? As asked by Ingo
|
||
Weinhold:
|
||
</p><p>
|
||
“<span class="quote">The only way I see is to archive the <code class="classname">BBitmap</code> object and flatten this
|
||
archive to the area. [However,] each application has to unflatten and
|
||
reinstantiate the object, which won't save any memory...</span>”
|
||
</p><p>
|
||
“<span class="quote">So let's assume it's possible...other applications could clone the area,
|
||
but the logical addresses may differ. An object that has stored pointers
|
||
to address its data will get into trouble if its logical address changes.
|
||
Correct?</span>”
|
||
</p><p>
|
||
First things first: No, you can't share an object (not safely and
|
||
predictably, anyway). As for creating an object at a specific address,
|
||
Jon Watte reminds us that C++ provides a "placement" allocation.
|
||
</p></div><div class="sect3"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h4 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="id719369"></a>Subject: (Sigh) BeOS on PPC</h4></div></div></div><p>
|
||
Announcing a grass roots movement to petition Apple to release G3 specs
|
||
to Be. The devil's advocate response from Geoffrey Clements (which, read
|
||
literally, depicts a bizarre anatomy):
|
||
</p><p>
|
||
“<span class="quote">Who cares, really? Yeah a G3 running the Mac OS will kick a Pentium II
|
||
running Windows's ass. But a dual Pentium II running the BeOS will Kick
|
||
the G3's ass.</span>”
|
||
</p><p>
|
||
Yes, but with a proviso:
|
||
</p><p>
|
||
“<span class="quote">BeOS is a faster operating system, so long as it doesn't get klunkier in
|
||
each release. Its been rather noticeable that BeBoxen that could zip
|
||
speedily along in DR8.2 (BeOS in 1996) are almost unusably slow in R3.
|
||
I'm speculating that one of these slowdowns is due to the <acronym class="acronym">MIME</acronym> typing
|
||
file handling system.</span>”
|
||
</p><p>
|
||
The proposed solution: Make <acronym class="acronym">MIME</acronym>-typing optional. "What?!?" quoth Chris
|
||
Herborth:
|
||
</p><p>
|
||
“<span class="quote">How could the filesystem's <acronym class="acronym">MIME</acronym> file attributes slow down the display
|
||
and applications in R3? The filesystem in R3 is _orders_of_magnitude_
|
||
faster than the one in DR8. And it wasn't the only thing that changed
|
||
between DR8 and R3.</span>”
|
||
</p><p>
|
||
“<span class="quote">If the file typing is made optional, we'll have the same useless
|
||
filesystem as Windows and UNIX; our files won't know what they are, and
|
||
we'll have to rely on stupid file extensions for everything.</span>”
|
||
</p></div><div class="sect3"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h4 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="id719447"></a>Subject: Performance followup</h4></div></div></div><p>
|
||
A spawn of the BeOS on G3 thread, listeners discussed system performance.
|
||
Is it degrading? Is it the app server's fault? MIME types? Anti-aliased
|
||
fonts? After some discussion
|
||
</p><p>
|
||
THE BE LINE: (From George Hoffman).
|
||
</p><p>
|
||
“<span class="quote">The graphics team is well aware of the state of app_server performance.
|
||
Not all of the perceived slowdown from PR2 to R3 is the fault of the
|
||
app_server, but there are hard numbers which do indicate a slight
|
||
slowdown in graphics performance. ... One of the big priorities we've had
|
||
for R4 is performance, and I'm pleased to say that the R4 development
|
||
app_server is quite a bit faster than R3, and, more importantly, _feels_
|
||
faster.</span>”
|
||
</p><p>
|
||
(Also, see Pierre Raynaud-Richard's article, above.)
|
||
</p></div><div class="sect3"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h4 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="id719485"></a>Subject: Broken assembly debugger on Intel R3.1?</h4></div></div></div><p>
|
||
Delesley S. Hutchins is having trouble with Be's (ahem) streamlined
|
||
debugger:
|
||
</p><p>
|
||
“<span class="quote">...I can't get the debugger to work... I type "help", and nothing
|
||
happens... it doesn't even go back to the debugger prompt.</span>”
|
||
</p><p>
|
||
From the scept'red isle, that other Eden, demi-paradise, royal throne of
|
||
kings and Tinky Winky, Simon Clarke suggests, moddishly:
|
||
</p><p>
|
||
“Do you have
|
||
</p><pre class="screen">
|
||
export BEDEBUG=true
|
||
</pre><p>
|
||
set in your user setup environment file? I had the same problem if I went
|
||
into the debugger from the alert that a crashed app shows. However, with
|
||
the debugger on all the time it works fine.”
|
||
</p></div><div class="sect3"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h4 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="id719528"></a>Subject: Tips on maniacal code optimization</h4></div></div></div><div class="qandaset"><table border="0" summary="Q and A Set"><col align="left" width="1%" /><tbody><tr class="question"><td><a id="id719537"></a><a id="id719539"></a>Q:</td><td><p>
|
||
What's the first step along the path to optimization?
|
||
</p></td></tr><tr class="answer"><td align="left" valign="top">A:</td><td align="left" valign="top"><p>
|
||
"Profile it first!"
|
||
</p></td></tr></tbody></table></div></div></div></div></div><div id="footer"><hr /><div id="footerT">Prev: <a href="Issue3-29.html">Issue 3-29, July 22, 1998</a> Up: <a href="volume3.html">Volume 3: 1998</a> Next: <a href="Issue3-31.html">Issue 3-31, August 5, 1998</a> </div><div id="footerB"><div id="footerBL"><a href="Issue3-29.html" title="Issue 3-29, July 22, 1998"><img src="./images/navigation/prev.png" alt="Prev" /></a> <a href="volume3.html" title="Volume 3: 1998"><img src="./images/navigation/up.png" alt="Up" /></a> <a href="Issue3-31.html" title="Issue 3-31, August 5, 1998"><img src="./images/navigation/next.png" alt="Next" /></a></div><div id="footerBR"><div><a href="http://www.haiku-os.org"><img src="./images/People_24.png" alt="haiku-os.org" title="Visit The Haiku Website" /></a></div><div class="navighome" title="Home"><a accesskey="h" href="index.html"><img src="./images/navigation/home.png" alt="Home" /></a></div></div><div id="footerBC"><a href="http://www.access-company.com/home.html" title="ACCESS Co."><img alt="Access Company" src="./images/access_logo.png" /></a></div></div></div><div id="licenseFooter"><div id="licenseFooterBL"><a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/" title="Creative Commons License"><img alt="Creative Commons License" style="border-width:0" src="https://licensebuttons.net/l/by-nc-nd/3.0/88x31.png" /></a></div><div id="licenseFooterBR"><a href="./LegalNotice.html">Legal Notice</a></div><div id="licenseFooterBC"><span id="licenseText">This work is licensed under a
|
||
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">Creative
|
||
Commons Attribution-Non commercial-No Derivative Works 3.0 License</a>.</span></div></div></body></html>
|