File: bitlock.c

package info (click to toggle)
glib2.0 2.85.2-2
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 66,240 kB
  • sloc: ansic: 542,609; python: 9,667; sh: 1,572; xml: 1,482; perl: 1,222; cpp: 535; makefile: 316; javascript: 11
file content (176 lines) | stat: -rw-r--r-- 4,612 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
#include <glib.h>

#include "gtestutils.h"

static void
test_bitlocks (void)
{
  guint64 start = g_get_monotonic_time ();
  gint lock = 0;
  guint i;
  guint n_iterations;

  n_iterations = g_test_perf () ? 100000000 : 1;

  for (i = 0; i < n_iterations; i++)
    {
      g_bit_lock (&lock, 0);
      g_bit_unlock (&lock, 0);
    }

  {
    gdouble elapsed;
    gdouble rate;

    elapsed = g_get_monotonic_time () - start;
    elapsed /= 1000000;
    rate = n_iterations / elapsed;

    g_test_maximized_result (rate, "iterations per second");
  }
}

#define PARALLEL_N_THREADS 5
#define PARALLEL_LOCKBIT 31
#define PARALLEL_TOGGLEBIT 30
#define PARALLEL_SETBIT 29
#define PARALLEL_LOCKMASK (1 << PARALLEL_LOCKBIT)
#define PARALLEL_TOGGLEMASK (1 << PARALLEL_TOGGLEBIT)
#define PARALLEL_SETMASK (1 << PARALLEL_SETBIT)
#define PARALLEL_MAX_COUNT_SELF 500
#define PARALLEL_MAX_COUNT_ALL (10 * PARALLEL_MAX_COUNT_SELF)
static int parallel_thread_ready = 0;
static int parallel_atomic = 0;

static void
_test_parallel_randomly_toggle (void)
{
  if (g_random_boolean ())
    g_atomic_int_or (&parallel_atomic, PARALLEL_TOGGLEMASK);
  else
    g_atomic_int_and (&parallel_atomic, ~PARALLEL_TOGGLEMASK);
}

static gpointer
_test_parallel_run (gpointer thread_arg)
{
  const int IDX = GPOINTER_TO_INT (thread_arg);
  int count_self = 0;

  (void) IDX;

  g_atomic_int_inc (&parallel_thread_ready);
  while (g_atomic_int_get (&parallel_thread_ready) != PARALLEL_N_THREADS)
    g_usleep (10);

  while (TRUE)
    {
      gint val;
      gint val2;
      gint new_val;
      gint count_all;

      _test_parallel_randomly_toggle ();

      /* take a lock. */
      if (g_random_boolean ())
        {
          g_bit_lock (&parallel_atomic, PARALLEL_LOCKBIT);
          val = g_atomic_int_get (&parallel_atomic);
        }
      else
        {
          g_bit_lock_and_get (&parallel_atomic, PARALLEL_LOCKBIT, &val);
        }

      _test_parallel_randomly_toggle ();

      /* the toggle bit is random. Clear it. */
      val &= ~PARALLEL_TOGGLEMASK;

      /* these bits must be set. */
      g_assert_true (val & PARALLEL_LOCKMASK);
      g_assert_true (val & PARALLEL_SETMASK);

      /* If we fetch again, we must get the same value. Nobody changes the
       * value while we hold the lock, except for the toggle bit. */
      val2 = g_atomic_int_get (&parallel_atomic);
      val2 &= ~PARALLEL_TOGGLEMASK;
      g_assert_cmpint (val, ==, val2);

      count_all = (val & ~(PARALLEL_LOCKMASK | PARALLEL_SETMASK));

      if ((g_random_int () % 5) == 0)
        {
          /* regular unlock without any changes. */
          g_bit_unlock (&parallel_atomic, PARALLEL_LOCKBIT);
          continue;
        }

      /* unlock-and-set with an increased counter. */
      new_val = MIN (count_all + 1, PARALLEL_MAX_COUNT_ALL);
      if (g_random_boolean ())
        new_val |= PARALLEL_SETMASK;
      if (g_random_boolean ())
        new_val |= PARALLEL_TOGGLEMASK;

      g_bit_unlock_and_set (&parallel_atomic, PARALLEL_LOCKBIT, new_val, ((new_val & PARALLEL_SETMASK) && g_random_boolean ()) ? 0 : PARALLEL_SETMASK);

      count_self++;

      if (count_self < PARALLEL_MAX_COUNT_SELF)
        continue;
      if (count_all < PARALLEL_MAX_COUNT_ALL)
        continue;

      break;
    }

  /* To indicate success, we return a pointer to &parallel_atomic. */
  return &parallel_atomic;
}

static void
test_parallel (void)
{
  GThread *threads[PARALLEL_N_THREADS];
  gpointer ptr;
  int i;
  gint val;

  g_atomic_int_or (&parallel_atomic, PARALLEL_SETMASK);

  for (i = 0; i < PARALLEL_N_THREADS; i++)
    {
      threads[i] = g_thread_new ("bitlock-parallel", _test_parallel_run, GINT_TO_POINTER (i));
    }

  for (i = 0; i < PARALLEL_N_THREADS; i++)
    {
      ptr = g_thread_join (threads[i]);
      g_assert_true (ptr == &parallel_atomic);

      /* After we join the first thread, we already expect that the resulting
       * atomic's counter is set to PARALLEL_MAX_COUNT_ALL. This stays until
       * the end. */
      val = g_atomic_int_get (&parallel_atomic);
      if (i == PARALLEL_N_THREADS - 1)
        {
          /* at last, the atomic must be unlocked. */
          g_assert_true (!(val & PARALLEL_LOCKMASK));
        }
      val &= ~(PARALLEL_LOCKMASK | PARALLEL_TOGGLEMASK | PARALLEL_SETMASK);
      g_assert_cmpint (val, ==, PARALLEL_MAX_COUNT_ALL);
    }
}

int
main (int argc, char **argv)
{
  g_test_init (&argc, &argv, NULL);

  g_test_add_func ("/bitlock/performance/uncontended", test_bitlocks);
  g_test_add_func ("/bitlock/performance/parallel", test_parallel);

  return g_test_run ();
}