quadratic growth on some apparently simple test cases. Fixes #267925.
(Philippe Waroquiers, philippe.waroquiers@skynet.be)
git-svn-id: svn://svn.valgrind.org/valgrind/trunk@12201
/* The word-set universes for lock sets. */
static WordSetU* univ_lsets = NULL; /* sets of Lock* */
static WordSetU* univ_laog = NULL; /* sets of Lock*, for LAOG */
+static Int next_gc_univ_laog = 1;
+/* univ_laog will be garbaged collected when the nr of element in univ_laog is
+ >= next_gc_univ_laog. */
/* Allow libhb to get at the universe of locksets stored
here. Sigh. */
VG_(printf)("}\n");
}
+static void univ_laog_do_GC ( void ) {
+ Word i;
+ LAOGLinks* links;
+ Word seen = 0;
+ Int prev_next_gc_univ_laog = next_gc_univ_laog;
+ const UWord univ_laog_cardinality = HG_(cardinalityWSU)( univ_laog);
+
+ Bool *univ_laog_seen = HG_(zalloc) ( "hg.gc_univ_laog.1",
+ (Int) univ_laog_cardinality
+ * sizeof(Bool) );
+ // univ_laog_seen[*] set to 0 (False) by zalloc.
+
+ if (VG_(clo_stats))
+ VG_(message)(Vg_DebugMsg,
+ "univ_laog_do_GC enter cardinality %'10d\n",
+ (Int)univ_laog_cardinality);
+
+ VG_(initIterFM)( laog );
+ links = NULL;
+ while (VG_(nextIterFM)( laog, NULL, (UWord*)&links )) {
+ tl_assert(links);
+ tl_assert(links->inns >= 0 && links->inns < univ_laog_cardinality);
+ univ_laog_seen[links->inns] = True;
+ tl_assert(links->outs >= 0 && links->outs < univ_laog_cardinality);
+ univ_laog_seen[links->outs] = True;
+ links = NULL;
+ }
+ VG_(doneIterFM)( laog );
+
+ for (i = 0; i < (Int)univ_laog_cardinality; i++) {
+ if (univ_laog_seen[i])
+ seen++;
+ else
+ HG_(dieWS) ( univ_laog, (WordSet)i );
+ }
+
+ HG_(free) (univ_laog_seen);
+
+ // We need to decide the value of the next_gc.
+ // 3 solutions were looked at:
+ // Sol 1: garbage collect at seen * 2
+ // This solution was a lot slower, probably because we both do a lot of
+ // garbage collection and do not keep long enough laog WV that will become
+ // useful again very soon.
+ // Sol 2: garbage collect at a percentage increase of the current cardinality
+ // (with a min increase of 1)
+ // Trials on a small test program with 1%, 5% and 10% increase was done.
+ // 1% is slightly faster than 5%, which is slightly slower than 10%.
+ // However, on a big application, this caused the memory to be exhausted,
+ // as even a 1% increase of size at each gc becomes a lot, when many gc
+ // are done.
+ // Sol 3: always garbage collect at current cardinality + 1.
+ // This solution was the fastest of the 3 solutions, and caused no memory
+ // exhaustion in the big application.
+ //
+ // With regards to cost introduced by gc: on the t2t perf test (doing only
+ // lock/unlock operations), t2t 50 10 2 was about 25% faster than the
+ // version with garbage collection. With t2t 50 20 2, my machine started
+ // to page out, and so the garbage collected version was much faster.
+ // On smaller lock sets (e.g. t2t 20 5 2, giving about 100 locks), the
+ // difference performance is insignificant (~ 0.1 s).
+ // Of course, it might be that real life programs are not well represented
+ // by t2t.
+
+ // If ever we want to have a more sophisticated control
+ // (e.g. clo options to control the percentage increase or fixed increased),
+ // we should do it here, eg.
+ // next_gc_univ_laog = prev_next_gc_univ_laog + VG_(clo_laog_gc_fixed);
+ // Currently, we just hard-code the solution 3 above.
+ next_gc_univ_laog = prev_next_gc_univ_laog + 1;
+
+ if (VG_(clo_stats))
+ VG_(message)
+ (Vg_DebugMsg,
+ "univ_laog_do_GC exit seen %'8d next gc at cardinality %'10d\n",
+ (Int)seen, next_gc_univ_laog);
+}
+
+
__attribute__((noinline))
static void laog__add_edge ( Lock* src, Lock* dst ) {
Word keyW;
VG_(addToFM)( laog_exposition, (Word)expo2, (Word)NULL );
}
}
+
+ if (HG_(cardinalityWSU) (univ_laog) >= next_gc_univ_laog)
+ univ_laog_do_GC();
}
__attribute__((noinline))
static void laog__del_edge ( Lock* src, Lock* dst ) {
Word keyW;
LAOGLinks* links;
- if (0) VG_(printf)("laog__del_edge %p %p\n", src, dst);
+ if (0) VG_(printf)("laog__del_edge enter %p %p\n", src, dst);
/* Update the out edges for src */
keyW = 0;
links = NULL;
tl_assert(keyW == (Word)dst);
links->inns = HG_(delFromWS)( univ_laog, links->inns, (Word)src );
}
+
+ /* Remove the exposition of src,dst (if present) */
+ {
+ LAOGLinkExposition *fm_expo;
+
+ LAOGLinkExposition expo;
+ expo.src_ga = src->guestaddr;
+ expo.dst_ga = dst->guestaddr;
+ expo.src_ec = NULL;
+ expo.dst_ec = NULL;
+
+ if (VG_(delFromFM) (laog_exposition,
+ (UWord*)&fm_expo, NULL, (UWord)&expo )) {
+ HG_(free) (fm_expo);
+ }
+ }
+
+ /* deleting edges can increase nr of of WS so check for gc. */
+ if (HG_(cardinalityWSU) (univ_laog) >= next_gc_univ_laog)
+ univ_laog_do_GC();
+ if (0) VG_(printf)("laog__del_edge exit\n");
}
__attribute__((noinline))
all_except_Locks__sanity_check("laog__pre_thread_acquires_lock-post");
}
+/* Allocates a duplicate of words. Caller must HG_(free) the result. */
+static UWord* UWordV_dup(UWord* words, Word words_size)
+{
+ UInt i;
+
+ if (words_size == 0)
+ return NULL;
+
+ UWord *dup = HG_(zalloc) ("hg.dup.1", (SizeT) words_size * sizeof(UWord));
+
+ for (i = 0; i < words_size; i++)
+ dup[i] = words[i];
+
+ return dup;
+}
/* Delete from 'laog' any pair mentioning a lock in locksToDelete */
preds = laog__preds( lk );
succs = laog__succs( lk );
+ // We need to duplicate the payload, as these can be garbage collected
+ // during the del/add operations below.
HG_(getPayloadWS)( &preds_words, &preds_size, univ_laog, preds );
+ preds_words = UWordV_dup(preds_words, preds_size);
+
+ HG_(getPayloadWS)( &succs_words, &succs_size, univ_laog, succs );
+ succs_words = UWordV_dup(succs_words, succs_size);
+
for (i = 0; i < preds_size; i++)
laog__del_edge( (Lock*)preds_words[i], lk );
- HG_(getPayloadWS)( &succs_words, &succs_size, univ_laog, succs );
for (j = 0; j < succs_size; j++)
laog__del_edge( lk, (Lock*)succs_words[j] );
}
}
}
+
+ if (preds_words)
+ HG_(free) (preds_words);
+ if (succs_words)
+ HG_(free) (succs_words);
+
+ // Remove lk information from laog links FM
+ {
+ LAOGLinks *links;
+ Lock* linked_lk;
+
+ if (VG_(delFromFM) (laog,
+ (UWord*)&linked_lk, (UWord*)&links, (UWord)lk)) {
+ tl_assert (linked_lk == lk);
+ HG_(free) (links);
+ }
+ }
+ /* FIXME ??? What about removing lock lk data from EXPOSITION ??? */
}
//__attribute__((noinline))
//
//
// HG_(getPayloadWS)( &ws_words, &ws_size, univ_lsets, locksToDelete );
+// UWordV_dup call needed here ...
// for (i = 0; i < ws_size; i++)
// laog__handle_one_lock_deletion( (Lock*)ws_words[i] );
//
#include "hg_basics.h"
#include "hg_wordset.h" /* self */
+// define to 1 to have (a lot of) debugging of add/re-use/die WSU entries.
+#define HG_DEBUG 0
+
//------------------------------------------------------------------//
//--- Word Cache ---//
//------------------------------------------------------------------//
/* ix2vec[0 .. ix2vec_used-1] are pointers to the lock sets (WordVecs)
really. vec2ix is the inverse mapping, mapping WordVec* to the
corresponding ix2vec entry number. The two mappings are mutually
- redundant. */
+ redundant.
+
+ If a WordVec WV is marked as dead by HG(dieWS), WV is removed from
+ vec2ix. The entry of the dead WVs in ix2vec are used to maintain a
+ linked list of free (to be re-used) ix2vec entries. */
struct _WordSetU {
void* (*alloc)(HChar*,SizeT);
HChar* cc;
WordVec** ix2vec; /* WordSet-to-WordVec mapping array */
UWord ix2vec_size;
UWord ix2vec_used;
+ WordVec** ix2vec_free;
WordSet empty; /* cached, for speed */
/* Caches for some operations */
WCache cache_addTo;
UWord n_add_uncached;
UWord n_del;
UWord n_del_uncached;
+ UWord n_die;
UWord n_union;
UWord n_intersect;
UWord n_intersect_uncached;
if (wsu->ix2vec_used < wsu->ix2vec_size)
return;
new_sz = 2 * wsu->ix2vec_size;
- if (new_sz == 0) new_sz = 2;
+ if (new_sz == 0) new_sz = 1;
new_vec = wsu->alloc( wsu->cc, new_sz * sizeof(WordVec*) );
tl_assert(new_vec);
for (i = 0; i < wsu->ix2vec_size; i++)
wsu->ix2vec_size = new_sz;
}
+/* True if wv is a dead entry (i.e. is in the linked list of free to be re-used
+ entries in ix2vec). */
+static inline Bool is_dead ( WordSetU* wsu, WordVec* wv )
+{
+ if (wv == NULL) /* last element in free linked list in ix2vec */
+ return True;
+ else
+ return (WordVec**)wv >= &(wsu->ix2vec[1])
+ && (WordVec**)wv < &(wsu->ix2vec[wsu->ix2vec_size]);
+}
/* Index into a WordSetU, doing the obvious range check. Failure of
the assertions marked XXX and YYY is an indication of passing the
- wrong WordSetU* in the public API of this module. */
+ wrong WordSetU* in the public API of this module.
+ Accessing a dead ws will assert. */
static WordVec* do_ix2vec ( WordSetU* wsu, WordSet ws )
{
WordVec* wv;
that does not come from the 'wsu' universe. */
tl_assert(ws < wsu->ix2vec_used); /* XXX */
wv = wsu->ix2vec[ws];
- /* Make absolutely sure that 'ws' is a member of 'wsu'. */
+ /* Make absolutely sure that 'ws' is a non dead member of 'wsu'. */
tl_assert(wv);
+ tl_assert(!is_dead(wsu,wv));
tl_assert(wv->owner == wsu); /* YYY */
return wv;
}
+/* Same as do_ix2vec but returns NULL for a dead ws. */
+static WordVec* do_ix2vec_with_dead ( WordSetU* wsu, WordSet ws )
+{
+ WordVec* wv;
+ tl_assert(wsu->ix2vec_used <= wsu->ix2vec_size);
+ if (wsu->ix2vec_used > 0)
+ tl_assert(wsu->ix2vec);
+ /* If this assertion fails, it may mean you supplied a 'ws'
+ that does not come from the 'wsu' universe. */
+ tl_assert(ws < wsu->ix2vec_used); /* XXX */
+ wv = wsu->ix2vec[ws];
+ /* Make absolutely sure that 'ws' is either dead or a member of 'wsu'. */
+ if (is_dead(wsu,wv))
+ wv = NULL;
+ else
+ tl_assert(wv->owner == wsu); /* YYY */
+ return wv;
+}
+
/* See if wv is contained within wsu. If so, deallocate wv and return
the index of the already-present copy. If not, add wv to both the
vec2ix and ix2vec mappings and return its index.
tl_assert(wsu->ix2vec[ix_old] == wv_old);
delete_WV( wv_new );
return (WordSet)ix_old;
+ } else if (wsu->ix2vec_free) {
+ WordSet ws;
+ tl_assert(is_dead(wsu,(WordVec*)wsu->ix2vec_free));
+ ws = wsu->ix2vec_free - &(wsu->ix2vec[0]);
+ tl_assert(wsu->ix2vec[ws] == NULL || is_dead(wsu,wsu->ix2vec[ws]));
+ wsu->ix2vec_free = (WordVec **) wsu->ix2vec[ws];
+ wsu->ix2vec[ws] = wv_new;
+ VG_(addToFM)( wsu->vec2ix, (Word)wv_new, ws );
+ if (HG_DEBUG) VG_(printf)("aodW %s re-use free %d %p\n", wsu->cc, (Int)ws, wv_new );
+ return ws;
} else {
ensure_ix2vec_space( wsu );
tl_assert(wsu->ix2vec);
tl_assert(wsu->ix2vec_used < wsu->ix2vec_size);
wsu->ix2vec[wsu->ix2vec_used] = wv_new;
VG_(addToFM)( wsu->vec2ix, (Word)wv_new, (Word)wsu->ix2vec_used );
- if (0) VG_(printf)("aodW %d\n", (Int)wsu->ix2vec_used );
+ if (HG_DEBUG) VG_(printf)("aodW %s %d %p\n", wsu->cc, (Int)wsu->ix2vec_used, wv_new );
wsu->ix2vec_used++;
tl_assert(wsu->ix2vec_used <= wsu->ix2vec_size);
return (WordSet)(wsu->ix2vec_used - 1);
wsu->ix2vec_used = 0;
wsu->ix2vec_size = 0;
wsu->ix2vec = NULL;
+ wsu->ix2vec_free = NULL;
WCache_INIT(wsu->cache_addTo, cacheSize);
WCache_INIT(wsu->cache_delFrom, cacheSize);
WCache_INIT(wsu->cache_intersect, cacheSize);
WordSetU* wsu, WordSet ws )
{
WordVec* wv;
+ if (HG_DEBUG) VG_(printf)("getPayloadWS %s %d\n", wsu->cc, (Int)ws);
tl_assert(wsu);
wv = do_ix2vec( wsu, ws );
tl_assert(wv->size >= 0);
*words = wv->words;
}
+void HG_(dieWS) ( WordSetU* wsu, WordSet ws )
+{
+ WordVec* wv = do_ix2vec_with_dead( wsu, ws );
+ WordVec* wv_in_vec2ix;
+ UWord/*Set*/ wv_ix = -1;
+
+ if (HG_DEBUG) VG_(printf)("dieWS %s %d %p\n", wsu->cc, (Int)ws, wv);
+
+ if (ws == 0)
+ return; // we never die the empty set.
+
+ if (!wv)
+ return; // already dead. (or a bug ?).
+
+ wsu->n_die++;
+
+
+ wsu->ix2vec[ws] = (WordVec*) wsu->ix2vec_free;
+ wsu->ix2vec_free = &wsu->ix2vec[ws];
+
+ VG_(delFromFM) ( wsu->vec2ix,
+ (Word*)&wv_in_vec2ix, (Word*)&wv_ix,
+ (Word)wv );
+
+ if (HG_DEBUG) VG_(printf)("dieWS wv_ix %d\n", (Int)wv_ix);
+ tl_assert (wv_ix);
+ tl_assert (wv_ix == ws);
+
+ delete_WV( wv );
+
+ wsu->cache_addTo.inUse = 0;
+ wsu->cache_delFrom.inUse = 0;
+ wsu->cache_intersect.inUse = 0;
+ wsu->cache_minus.inUse = 0;
+}
+
Bool HG_(plausibleWS) ( WordSetU* wsu, WordSet ws )
{
if (wsu == NULL) return False;
VG_(printf)(" isSingleton %10lu\n", wsu->n_isSingleton);
VG_(printf)(" anyElementOf %10lu\n", wsu->n_anyElementOf);
VG_(printf)(" isSubsetOf %10lu\n", wsu->n_isSubsetOf);
+ VG_(printf)(" dieWS %10lu\n", wsu->n_die);
}
WordSet HG_(addToWS) ( WordSetU* wsu, WordSet ws, UWord w )
/* Free up the WordSetU. */
void HG_(deleteWordSetU) ( WordSetU* );
-/* Get the number of elements in this WordSetU. */
+/* Get the number of elements in this WordSetU. Note that the dead
+ WordSet are included in the WordSetU number of elements. */
UWord HG_(cardinalityWSU) ( WordSetU* );
/* Show performance stats for this WordSetU. */
WordSet HG_(isSubsetOf) ( WordSetU*, WordSet, WordSet );
Bool HG_(plausibleWS) ( WordSetU*, WordSet );
+
+
Bool HG_(saneWS_SLOW) ( WordSetU*, WordSet );
void HG_(ppWS) ( WordSetU*, WordSet );
+
void HG_(getPayloadWS) ( /*OUT*/UWord** words, /*OUT*/UWord* nWords,
WordSetU*, WordSet );
+/* HG_(dieWS) indicates WordSet is not used/not referenced anymore,
+ and its memory can be reclaimed.
+ If ever a WordSet with the same content would be needed again,
+ a new WordSet will be reallocated.
+
+ BUG ALERT: !!! Using HG_(dieWS) on a WSU introduces a risk of
+ dangling references. Dangling references can be created by keeping
+ a ws after having marked it dead. This ws (just an index in
+ reality) will be re-cycled : a newly created wv can get the same
+ index. This implies that the wrong wv will be used if the
+ "old" ws has been kept.
+ Re-using a "dead" ws will be detected if the index has not been
+ re-cycled yet.
+
+ Another possibility of bug is to ask for the payload of a ws, and
+ then have this ws marked dead while the payload is still being
+ examined. This is a real dangling reference in free or re-allocated
+ memory. */
+void HG_(dieWS) ( WordSetU*, WordSet );
+
+
//------------------------------------------------------------------//
//--- end WordSet ---//
pth_spinlock.vgtest pth_spinlock.stdout.exp pth_spinlock.stderr.exp \
rwlock_race.vgtest rwlock_race.stdout.exp rwlock_race.stderr.exp \
rwlock_test.vgtest rwlock_test.stdout.exp rwlock_test.stderr.exp \
+ t2t_laog.vgtest t2t_laog.stdout.exp t2t_laog.stderr.exp \
tc01_simple_race.vgtest tc01_simple_race.stdout.exp \
tc01_simple_race.stderr.exp \
tc02_simple_tls.vgtest tc02_simple_tls.stdout.exp \
locked_vs_unlocked1 \
locked_vs_unlocked2 \
locked_vs_unlocked3 \
+ t2t \
tc01_simple_race \
tc02_simple_tls \
tc03_re_excl \
--- /dev/null
+#include <stdio.h>
+#include <pthread.h>
+#include <stdlib.h>
+#define MANY 1000
+#define LEVEL 100
+typedef struct {
+ pthread_mutex_t m[MANY];
+ pthread_mutex_t d;
+} Level;
+
+static Level level[LEVEL];
+
+static int stat_mutex_init = 0;
+static int stat_mutex_lock = 0;
+static int stat_mutex_unlock = 0;
+static int stat_mutex_destroy = 0;
+
+/* t2t.c : test program for the laog data structure performance testing
+ and "shaking" : it creates, locks/unlocks and destroy mutex.
+
+ USAGE: t2t [many] [level] [loops]
+ many (default 100) : how many locks are created/locked/unlocked at a certain level.
+ level (default 1) : how many levels of "nested locks" are done
+ loops : how many times these locks are created and destroyed and locked/unlocked) */
+#define check if (ret != 0) printf("error %d at line %d\n", ret, __LINE__)
+int doit(int argc, char*argv[])
+{
+ int l, i;
+ int ret;
+
+ int clo_many = 100;
+ int clo_level = 1;
+
+ if (argc >= 2) clo_many = atoi(argv[1]);
+ if (argc >= 3) clo_level = atoi(argv[2]);
+
+ if (clo_many > MANY) {
+ printf("error argv[1] (many arg) %d > max MANY %d\n", clo_many, MANY);
+ exit(1);
+ }
+
+ if (clo_level > LEVEL) {
+ printf("error argv[2] (level arg) %d > max LEVEL %d\n", clo_level, LEVEL);
+ exit(1);
+ }
+
+ printf ("many %d level %d total_locks: %d\n",
+ clo_many, clo_level,
+ clo_many * clo_level + clo_level * (clo_level == 1 ? 0 : 1));
+
+ for (l = 0; l < clo_level; l++) {
+ printf ("init level %d\n", l);
+ for (i = 0; i < clo_many; i++) {
+ ret = pthread_mutex_init (&level[l].m[i], NULL);
+ check;
+ stat_mutex_init++;
+ }
+ if (clo_level > 1) {
+ ret = pthread_mutex_init (&level[l].d, NULL);
+ check;
+ stat_mutex_init++;
+ }
+ }
+
+ for (l = 0; l < clo_level; l++) {
+ printf ("locking level %d\n", l);
+ for (i = 0; i < clo_many; i++) {
+ ret = pthread_mutex_lock (&level[l].m[i]);
+ check;
+ stat_mutex_lock++;
+ }
+ if (clo_level > 1) {
+ ret = pthread_mutex_lock (&level[l].d);
+ check;
+ stat_mutex_lock++;
+ }
+ }
+
+ for (l = 0; l < clo_level; l++) {
+ printf ("unlocking level %d\n", l);
+ for (i = 0; i < clo_many; i++) {
+ ret = pthread_mutex_unlock (&level[l].m[i]);
+ check;
+ stat_mutex_unlock++;
+ }
+ if (clo_level > 1) {
+ ret = pthread_mutex_unlock (&level[l].d);
+ stat_mutex_unlock++;
+ check;
+ }
+ }
+
+ for (l = 0; l < clo_level; l++) {
+ printf ("deleting level %d\n", l);
+ if (clo_level > 1) {
+ ret = pthread_mutex_destroy (&level[l].d);
+ /// this tests the influence of the deletion in another order.
+ check;
+ stat_mutex_destroy++;
+ }
+ for (i = 0; i < clo_many; i++) {
+ ret = pthread_mutex_destroy (&level[l].m[i]);
+ check;
+ stat_mutex_destroy++;
+ }
+ }
+ return 0;
+}
+
+int main(int argc, char*argv[])
+{
+ int loops = 1;
+ int i;
+ if (argc >= 4) loops = atoi(argv[3]);
+
+ printf ("loops %d\n", loops);
+ for (i = 0; i < loops; i++)
+ doit(argc, argv);
+
+ printf ("stats: init %d lock %d unlock %d destroy %d\n",
+ stat_mutex_init, stat_mutex_lock,
+ stat_mutex_unlock, stat_mutex_destroy);
+ return 0;
+}
+
--- /dev/null
+
+
+ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
--- /dev/null
+loops 4
+many 30 level 10 total_locks: 310
+init level 0
+init level 1
+init level 2
+init level 3
+init level 4
+init level 5
+init level 6
+init level 7
+init level 8
+init level 9
+locking level 0
+locking level 1
+locking level 2
+locking level 3
+locking level 4
+locking level 5
+locking level 6
+locking level 7
+locking level 8
+locking level 9
+unlocking level 0
+unlocking level 1
+unlocking level 2
+unlocking level 3
+unlocking level 4
+unlocking level 5
+unlocking level 6
+unlocking level 7
+unlocking level 8
+unlocking level 9
+deleting level 0
+deleting level 1
+deleting level 2
+deleting level 3
+deleting level 4
+deleting level 5
+deleting level 6
+deleting level 7
+deleting level 8
+deleting level 9
+many 30 level 10 total_locks: 310
+init level 0
+init level 1
+init level 2
+init level 3
+init level 4
+init level 5
+init level 6
+init level 7
+init level 8
+init level 9
+locking level 0
+locking level 1
+locking level 2
+locking level 3
+locking level 4
+locking level 5
+locking level 6
+locking level 7
+locking level 8
+locking level 9
+unlocking level 0
+unlocking level 1
+unlocking level 2
+unlocking level 3
+unlocking level 4
+unlocking level 5
+unlocking level 6
+unlocking level 7
+unlocking level 8
+unlocking level 9
+deleting level 0
+deleting level 1
+deleting level 2
+deleting level 3
+deleting level 4
+deleting level 5
+deleting level 6
+deleting level 7
+deleting level 8
+deleting level 9
+many 30 level 10 total_locks: 310
+init level 0
+init level 1
+init level 2
+init level 3
+init level 4
+init level 5
+init level 6
+init level 7
+init level 8
+init level 9
+locking level 0
+locking level 1
+locking level 2
+locking level 3
+locking level 4
+locking level 5
+locking level 6
+locking level 7
+locking level 8
+locking level 9
+unlocking level 0
+unlocking level 1
+unlocking level 2
+unlocking level 3
+unlocking level 4
+unlocking level 5
+unlocking level 6
+unlocking level 7
+unlocking level 8
+unlocking level 9
+deleting level 0
+deleting level 1
+deleting level 2
+deleting level 3
+deleting level 4
+deleting level 5
+deleting level 6
+deleting level 7
+deleting level 8
+deleting level 9
+many 30 level 10 total_locks: 310
+init level 0
+init level 1
+init level 2
+init level 3
+init level 4
+init level 5
+init level 6
+init level 7
+init level 8
+init level 9
+locking level 0
+locking level 1
+locking level 2
+locking level 3
+locking level 4
+locking level 5
+locking level 6
+locking level 7
+locking level 8
+locking level 9
+unlocking level 0
+unlocking level 1
+unlocking level 2
+unlocking level 3
+unlocking level 4
+unlocking level 5
+unlocking level 6
+unlocking level 7
+unlocking level 8
+unlocking level 9
+deleting level 0
+deleting level 1
+deleting level 2
+deleting level 3
+deleting level 4
+deleting level 5
+deleting level 6
+deleting level 7
+deleting level 8
+deleting level 9
+stats: init 1240 lock 1240 unlock 1240 destroy 1240
--- /dev/null
+# performance test for a big laog graph + shake the add/del
+# in the laog data structure.
+prog: t2t
+args: 30 10 4