]> git.ipfire.org Git - thirdparty/valgrind.git/commitdiff
Allow garbage collection of the LAOG data structure(s). This avoids
authorJulian Seward <jseward@acm.org>
Sat, 22 Oct 2011 19:29:51 +0000 (19:29 +0000)
committerJulian Seward <jseward@acm.org>
Sat, 22 Oct 2011 19:29:51 +0000 (19:29 +0000)
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

helgrind/hg_main.c
helgrind/hg_wordset.c
helgrind/hg_wordset.h
helgrind/tests/Makefile.am
helgrind/tests/t2t.c [new file with mode: 0644]
helgrind/tests/t2t_laog.stderr.exp [new file with mode: 0644]
helgrind/tests/t2t_laog.stdout.exp [new file with mode: 0644]
helgrind/tests/t2t_laog.vgtest [new file with mode: 0644]

index e89ef308ceb22fc4b088f1598e79c0c6d0909db2..49e76925b139e765a22514fc2f9da72d56afc6cb 100644 (file)
@@ -136,6 +136,9 @@ static WordFM* map_locks = NULL; /* WordFM LockAddr Lock* */
 /* 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. */
@@ -3300,6 +3303,85 @@ static void laog__show ( Char* who ) {
    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;
@@ -3378,13 +3460,16 @@ static void laog__add_edge ( Lock* src, Lock* dst ) {
          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;
@@ -3401,6 +3486,27 @@ static void laog__del_edge ( Lock* src, Lock* dst ) {
       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))
@@ -3611,6 +3717,21 @@ static void laog__pre_thread_acquires_lock (
       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 */
 
@@ -3624,11 +3745,17 @@ static void laog__handle_one_lock_deletion ( Lock* lk )
    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] );
 
@@ -3642,6 +3769,24 @@ static void laog__handle_one_lock_deletion ( Lock* lk )
          }
       }
    }
+
+   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))
@@ -3654,6 +3799,7 @@ static void laog__handle_one_lock_deletion ( Lock* lk )
 //
 //
 //   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] );
 //
index a7cb814774af25301146496b0725d7a15ea2a9b6..468ff168e0cbc3795953b449b1fee861800f1796 100644 (file)
@@ -44,6 +44,9 @@
 #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                                                 ---//
 //------------------------------------------------------------------//
@@ -139,7 +142,11 @@ typedef
 /* 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;
@@ -148,6 +155,7 @@ struct _WordSetU {
       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;
@@ -159,6 +167,7 @@ struct _WordSetU {
       UWord     n_add_uncached;
       UWord     n_del;
       UWord     n_del_uncached;
+      UWord     n_die;
       UWord     n_union;
       UWord     n_intersect;
       UWord     n_intersect_uncached;
@@ -235,7 +244,7 @@ static void ensure_ix2vec_space ( WordSetU* wsu )
    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++)
@@ -246,9 +255,20 @@ static void ensure_ix2vec_space ( WordSetU* wsu )
    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;
@@ -259,12 +279,32 @@ static WordVec* do_ix2vec ( WordSetU* wsu, WordSet 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 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. 
@@ -289,13 +329,23 @@ static WordSet add_or_dealloc_WordVec( WordSetU* wsu, WordVec* wv_new )
       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);
@@ -321,6 +371,7 @@ WordSetU* HG_(newWordSetU) ( void* (*alloc_nofail)( HChar*, SizeT ),
    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);
@@ -397,6 +448,7 @@ void HG_(getPayloadWS) ( /*OUT*/UWord** words, /*OUT*/UWord* nWords,
                          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);
@@ -404,6 +456,42 @@ void HG_(getPayloadWS) ( /*OUT*/UWord** words, /*OUT*/UWord* nWords,
    *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;
@@ -511,6 +599,7 @@ void HG_(ppWSUstats) ( WordSetU* wsu, HChar* name )
    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 )
index 54a175a12f74ab70868f100e428d0db8fa11e6bd..20f8bdb3a3b42b16002221e741deea600fb1c5f6 100644 (file)
@@ -55,7 +55,8 @@ WordSetU* HG_(newWordSetU) ( void* (*alloc_nofail)( HChar*, SizeT ),
 /* 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. */
@@ -82,12 +83,36 @@ WordSet HG_(singletonWS)    ( WordSetU*, UWord );
 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                         ---//
index 996a8e690562bc36bd3db44fb0b8da0c57d139f4..3271a5427537caf8cb9bd051d981e895018cb645 100644 (file)
@@ -43,6 +43,7 @@ EXTRA_DIST = \
        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 \
@@ -105,6 +106,7 @@ check_PROGRAMS = \
        locked_vs_unlocked1 \
        locked_vs_unlocked2 \
        locked_vs_unlocked3 \
+       t2t \
        tc01_simple_race \
        tc02_simple_tls \
        tc03_re_excl \
diff --git a/helgrind/tests/t2t.c b/helgrind/tests/t2t.c
new file mode 100644 (file)
index 0000000..d8a0aba
--- /dev/null
@@ -0,0 +1,125 @@
+#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;
+}
+
diff --git a/helgrind/tests/t2t_laog.stderr.exp b/helgrind/tests/t2t_laog.stderr.exp
new file mode 100644 (file)
index 0000000..d18786f
--- /dev/null
@@ -0,0 +1,3 @@
+
+
+ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
diff --git a/helgrind/tests/t2t_laog.stdout.exp b/helgrind/tests/t2t_laog.stdout.exp
new file mode 100644 (file)
index 0000000..364d876
--- /dev/null
@@ -0,0 +1,166 @@
+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
diff --git a/helgrind/tests/t2t_laog.vgtest b/helgrind/tests/t2t_laog.vgtest
new file mode 100644 (file)
index 0000000..ce27938
--- /dev/null
@@ -0,0 +1,4 @@
+# performance test for a big laog graph + shake the add/del
+# in the laog data structure.
+prog: t2t
+args: 30 10 4