Blame lib/dynamicsizehash_concurrent.c

Packit 032894
/* Copyright (C) 2000-2019 Red Hat, Inc.
Packit 032894
   This file is part of elfutils.
Packit 032894
   Written by Srdan Milakovic <sm108@rice.edu>, 2019.
Packit 032894
   Derived from Ulrich Drepper <drepper@redhat.com>, 2000.
Packit 032894
Packit 032894
   This file is free software; you can redistribute it and/or modify
Packit 032894
   it under the terms of either
Packit 032894
Packit 032894
     * the GNU Lesser General Public License as published by the Free
Packit 032894
       Software Foundation; either version 3 of the License, or (at
Packit 032894
       your option) any later version
Packit 032894
Packit 032894
   or
Packit 032894
Packit 032894
     * the GNU General Public License as published by the Free
Packit 032894
       Software Foundation; either version 2 of the License, or (at
Packit 032894
       your option) any later version
Packit 032894
Packit 032894
   or both in parallel, as here.
Packit 032894
Packit 032894
   elfutils is distributed in the hope that it will be useful, but
Packit 032894
   WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 032894
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 032894
   General Public License for more details.
Packit 032894
Packit 032894
   You should have received copies of the GNU General Public License and
Packit 032894
   the GNU Lesser General Public License along with this program.  If
Packit 032894
   not, see <http://www.gnu.org/licenses/>.  */
Packit 032894
Packit 032894
#include <assert.h>
Packit 032894
#include <stdlib.h>
Packit 032894
#include <system.h>
Packit 032894
#include <pthread.h>
Packit 032894
Packit 032894
/* Before including this file the following macros must be defined:
Packit 032894
Packit 032894
   NAME      name of the hash table structure.
Packit 032894
   TYPE      data type of the hash table entries
Packit 032894
 */
Packit 032894
Packit 032894
Packit 032894
static size_t
Packit 032894
lookup (NAME *htab, HASHTYPE hval)
Packit 032894
{
Packit 032894
  /* First hash function: simply take the modul but prevent zero.  Small values
Packit 032894
      can skip the division, which helps performance when this is common.  */
Packit 032894
  size_t idx = 1 + (hval < htab->size ? hval : hval % htab->size);
Packit 032894
Packit 032894
  HASHTYPE hash;
Packit 032894
Packit 032894
  hash = atomic_load_explicit(&htab->table[idx].hashval,
Packit 032894
                              memory_order_acquire);
Packit 032894
  if (hash == hval)
Packit 032894
    return idx;
Packit 032894
  else if (hash == 0)
Packit 032894
    return 0;
Packit 032894
Packit 032894
  /* Second hash function as suggested in [Knuth].  */
Packit 032894
  HASHTYPE second_hash = 1 + hval % (htab->size - 2);
Packit 032894
Packit 032894
  for(;;)
Packit 032894
    {
Packit 032894
      if (idx <= second_hash)
Packit 032894
          idx = htab->size + idx - second_hash;
Packit 032894
      else
Packit 032894
          idx -= second_hash;
Packit 032894
Packit 032894
      hash = atomic_load_explicit(&htab->table[idx].hashval,
Packit 032894
                                  memory_order_acquire);
Packit 032894
      if (hash == hval)
Packit 032894
	return idx;
Packit 032894
      else if (hash == 0)
Packit 032894
	return 0;
Packit 032894
    }
Packit 032894
}
Packit 032894
Packit 032894
static int
Packit 032894
insert_helper (NAME *htab, HASHTYPE hval, TYPE val)
Packit 032894
{
Packit 032894
  /* First hash function: simply take the modul but prevent zero.  Small values
Packit 032894
      can skip the division, which helps performance when this is common.  */
Packit 032894
  size_t idx = 1 + (hval < htab->size ? hval : hval % htab->size);
Packit 032894
Packit 032894
  TYPE val_ptr;
Packit 032894
  HASHTYPE hash;
Packit 032894
Packit 032894
  hash = atomic_load_explicit(&htab->table[idx].hashval,
Packit 032894
                              memory_order_acquire);
Packit 032894
  if (hash == hval)
Packit 032894
    return -1;
Packit 032894
  else if (hash == 0)
Packit 032894
    {
Packit 032894
      val_ptr = NULL;
Packit 032894
      atomic_compare_exchange_strong_explicit(&htab->table[idx].val_ptr,
Packit 032894
                                              (uintptr_t *) &val_ptr,
Packit 032894
                                              (uintptr_t) val,
Packit 032894
                                              memory_order_acquire,
Packit 032894
                                              memory_order_acquire);
Packit 032894
Packit 032894
      if (val_ptr == NULL)
Packit 032894
        {
Packit 032894
          atomic_store_explicit(&htab->table[idx].hashval, hval,
Packit 032894
                                memory_order_release);
Packit 032894
          return 0;
Packit 032894
        }
Packit 032894
      else
Packit 032894
        {
Packit 032894
          do
Packit 032894
            {
Packit 032894
              hash = atomic_load_explicit(&htab->table[idx].hashval,
Packit 032894
                                          memory_order_acquire);
Packit 032894
            }
Packit 032894
          while (hash == 0);
Packit 032894
          if (hash == hval)
Packit 032894
            return -1;
Packit 032894
        }
Packit 032894
    }
Packit 032894
Packit 032894
  /* Second hash function as suggested in [Knuth].  */
Packit 032894
  HASHTYPE second_hash = 1 + hval % (htab->size - 2);
Packit 032894
Packit 032894
  for(;;)
Packit 032894
    {
Packit 032894
      if (idx <= second_hash)
Packit 032894
          idx = htab->size + idx - second_hash;
Packit 032894
      else
Packit 032894
          idx -= second_hash;
Packit 032894
Packit 032894
      hash = atomic_load_explicit(&htab->table[idx].hashval,
Packit 032894
                                  memory_order_acquire);
Packit 032894
      if (hash == hval)
Packit 032894
        return -1;
Packit 032894
      else if (hash == 0)
Packit 032894
        {
Packit 032894
          val_ptr = NULL;
Packit 032894
          atomic_compare_exchange_strong_explicit(&htab->table[idx].val_ptr,
Packit 032894
                                                  (uintptr_t *) &val_ptr,
Packit 032894
                                                  (uintptr_t) val,
Packit 032894
                                                  memory_order_acquire,
Packit 032894
                                                  memory_order_acquire);
Packit 032894
Packit 032894
          if (val_ptr == NULL)
Packit 032894
            {
Packit 032894
              atomic_store_explicit(&htab->table[idx].hashval, hval,
Packit 032894
                                    memory_order_release);
Packit 032894
              return 0;
Packit 032894
            }
Packit 032894
          else
Packit 032894
            {
Packit 032894
              do
Packit 032894
                {
Packit 032894
                  hash = atomic_load_explicit(&htab->table[idx].hashval,
Packit 032894
                                              memory_order_acquire);
Packit 032894
                }
Packit 032894
              while (hash == 0);
Packit 032894
              if (hash == hval)
Packit 032894
                return -1;
Packit 032894
            }
Packit 032894
        }
Packit 032894
    }
Packit 032894
}
Packit 032894
Packit 032894
#define NO_RESIZING 0u
Packit 032894
#define ALLOCATING_MEMORY 1u
Packit 032894
#define MOVING_DATA 3u
Packit 032894
#define CLEANING 2u
Packit 032894
Packit 032894
#define STATE_BITS 2u
Packit 032894
#define STATE_INCREMENT (1u << STATE_BITS)
Packit 032894
#define STATE_MASK (STATE_INCREMENT - 1)
Packit 032894
#define GET_STATE(A) ((A) & STATE_MASK)
Packit 032894
Packit 032894
#define IS_NO_RESIZE_OR_CLEANING(A) (((A) & 0x1u) == 0)
Packit 032894
Packit 032894
#define GET_ACTIVE_WORKERS(A) ((A) >> STATE_BITS)
Packit 032894
Packit 032894
#define INITIALIZATION_BLOCK_SIZE 256
Packit 032894
#define MOVE_BLOCK_SIZE 256
Packit 032894
#define CEIL(A, B) (((A) + (B) - 1) / (B))
Packit 032894
Packit 032894
/* Initializes records and copies the data from the old table.
Packit 032894
   It can share work with other threads */
Packit 032894
static void resize_helper(NAME *htab, int blocking)
Packit 032894
{
Packit 032894
  size_t num_old_blocks = CEIL(htab->old_size, MOVE_BLOCK_SIZE);
Packit 032894
  size_t num_new_blocks = CEIL(htab->size, INITIALIZATION_BLOCK_SIZE);
Packit 032894
Packit 032894
  size_t my_block;
Packit 032894
  size_t num_finished_blocks = 0;
Packit 032894
Packit 032894
  while ((my_block = atomic_fetch_add_explicit(&htab->next_init_block, 1,
Packit 032894
                                                memory_order_acquire))
Packit 032894
                                                    < num_new_blocks)
Packit 032894
    {
Packit 032894
      size_t record_it = my_block * INITIALIZATION_BLOCK_SIZE;
Packit 032894
      size_t record_end = (my_block + 1) * INITIALIZATION_BLOCK_SIZE;
Packit 032894
      if (record_end > htab->size)
Packit 032894
          record_end = htab->size;
Packit 032894
Packit 032894
      while (record_it++ != record_end)
Packit 032894
        {
Packit 032894
          atomic_init(&htab->table[record_it].hashval, (uintptr_t) NULL);
Packit 032894
          atomic_init(&htab->table[record_it].val_ptr, (uintptr_t) NULL);
Packit 032894
        }
Packit 032894
Packit 032894
      num_finished_blocks++;
Packit 032894
    }
Packit 032894
Packit 032894
  atomic_fetch_add_explicit(&htab->num_initialized_blocks,
Packit 032894
                            num_finished_blocks, memory_order_release);
Packit 032894
  while (atomic_load_explicit(&htab->num_initialized_blocks,
Packit 032894
                              memory_order_acquire) != num_new_blocks);
Packit 032894
Packit 032894
  /* All block are initialized, start moving */
Packit 032894
  num_finished_blocks = 0;
Packit 032894
  while ((my_block = atomic_fetch_add_explicit(&htab->next_move_block, 1,
Packit 032894
                                                memory_order_acquire))
Packit 032894
                                                    < num_old_blocks)
Packit 032894
    {
Packit 032894
      size_t record_it = my_block * MOVE_BLOCK_SIZE;
Packit 032894
      size_t record_end = (my_block + 1) * MOVE_BLOCK_SIZE;
Packit 032894
      if (record_end > htab->old_size)
Packit 032894
          record_end = htab->old_size;
Packit 032894
Packit 032894
      while (record_it++ != record_end)
Packit 032894
        {
Packit 032894
          TYPE val_ptr = (TYPE) atomic_load_explicit(
Packit 032894
              &htab->old_table[record_it].val_ptr,
Packit 032894
              memory_order_acquire);
Packit 032894
          if (val_ptr == NULL)
Packit 032894
              continue;
Packit 032894
Packit 032894
          HASHTYPE hashval = atomic_load_explicit(
Packit 032894
              &htab->old_table[record_it].hashval,
Packit 032894
              memory_order_acquire);
Packit 032894
          assert(hashval);
Packit 032894
Packit 032894
          insert_helper(htab, hashval, val_ptr);
Packit 032894
        }
Packit 032894
Packit 032894
      num_finished_blocks++;
Packit 032894
    }
Packit 032894
Packit 032894
  atomic_fetch_add_explicit(&htab->num_moved_blocks, num_finished_blocks,
Packit 032894
                            memory_order_release);
Packit 032894
Packit 032894
  if (blocking)
Packit 032894
      while (atomic_load_explicit(&htab->num_moved_blocks,
Packit 032894
                                  memory_order_acquire) != num_old_blocks);
Packit 032894
}
Packit 032894
Packit 032894
static void
Packit 032894
resize_master(NAME *htab)
Packit 032894
{
Packit 032894
  htab->old_size = htab->size;
Packit 032894
  htab->old_table = htab->table;
Packit 032894
Packit 032894
  htab->size = next_prime(htab->size * 2);
Packit 032894
  htab->table = malloc((1 + htab->size) * sizeof(htab->table[0]));
Packit 032894
  assert(htab->table);
Packit 032894
Packit 032894
  /* Change state from ALLOCATING_MEMORY to MOVING_DATA */
Packit 032894
  atomic_fetch_xor_explicit(&htab->resizing_state,
Packit 032894
                            ALLOCATING_MEMORY ^ MOVING_DATA,
Packit 032894
                            memory_order_release);
Packit 032894
Packit 032894
  resize_helper(htab, 1);
Packit 032894
Packit 032894
  /* Change state from MOVING_DATA to CLEANING */
Packit 032894
  size_t resize_state = atomic_fetch_xor_explicit(&htab->resizing_state,
Packit 032894
                                                  MOVING_DATA ^ CLEANING,
Packit 032894
                                                  memory_order_acq_rel);
Packit 032894
  while (GET_ACTIVE_WORKERS(resize_state) != 0)
Packit 032894
      resize_state = atomic_load_explicit(&htab->resizing_state,
Packit 032894
                                          memory_order_acquire);
Packit 032894
Packit 032894
  /* There are no more active workers */
Packit 032894
  atomic_store_explicit(&htab->next_init_block, 0, memory_order_relaxed);
Packit 032894
  atomic_store_explicit(&htab->num_initialized_blocks, 0,
Packit 032894
                        memory_order_relaxed);
Packit 032894
Packit 032894
  atomic_store_explicit(&htab->next_move_block, 0, memory_order_relaxed);
Packit 032894
  atomic_store_explicit(&htab->num_moved_blocks, 0, memory_order_relaxed);
Packit 032894
Packit 032894
  free(htab->old_table);
Packit 032894
Packit 032894
  /* Change state to NO_RESIZING */
Packit 032894
  atomic_fetch_xor_explicit(&htab->resizing_state, CLEANING ^ NO_RESIZING,
Packit 032894
                            memory_order_relaxed);
Packit 032894
Packit 032894
}
Packit 032894
Packit 032894
static void
Packit 032894
resize_worker(NAME *htab)
Packit 032894
{
Packit 032894
  size_t resize_state = atomic_load_explicit(&htab->resizing_state,
Packit 032894
                                              memory_order_acquire);
Packit 032894
Packit 032894
  /* If the resize has finished */
Packit 032894
  if (IS_NO_RESIZE_OR_CLEANING(resize_state))
Packit 032894
      return;
Packit 032894
Packit 032894
  /* Register as worker and check if the resize has finished in the meantime*/
Packit 032894
  resize_state = atomic_fetch_add_explicit(&htab->resizing_state,
Packit 032894
                                            STATE_INCREMENT,
Packit 032894
                                            memory_order_acquire);
Packit 032894
  if (IS_NO_RESIZE_OR_CLEANING(resize_state))
Packit 032894
    {
Packit 032894
      atomic_fetch_sub_explicit(&htab->resizing_state, STATE_INCREMENT,
Packit 032894
                                memory_order_relaxed);
Packit 032894
      return;
Packit 032894
    }
Packit 032894
Packit 032894
  /* Wait while the new table is being allocated. */
Packit 032894
  while (GET_STATE(resize_state) == ALLOCATING_MEMORY)
Packit 032894
      resize_state = atomic_load_explicit(&htab->resizing_state,
Packit 032894
                                          memory_order_acquire);
Packit 032894
Packit 032894
  /* Check if the resize is done */
Packit 032894
  assert(GET_STATE(resize_state) != NO_RESIZING);
Packit 032894
  if (GET_STATE(resize_state) == CLEANING)
Packit 032894
    {
Packit 032894
      atomic_fetch_sub_explicit(&htab->resizing_state, STATE_INCREMENT,
Packit 032894
                                memory_order_relaxed);
Packit 032894
      return;
Packit 032894
    }
Packit 032894
Packit 032894
  resize_helper(htab, 0);
Packit 032894
Packit 032894
  /* Deregister worker */
Packit 032894
  atomic_fetch_sub_explicit(&htab->resizing_state, STATE_INCREMENT,
Packit 032894
                            memory_order_release);
Packit 032894
}
Packit 032894
Packit 032894
Packit 032894
int
Packit 032894
#define INIT(name) _INIT (name)
Packit 032894
#define _INIT(name) \
Packit 032894
  name##_init
Packit 032894
INIT(NAME) (NAME *htab, size_t init_size)
Packit 032894
{
Packit 032894
  /* We need the size to be a prime.  */
Packit 032894
  init_size = next_prime (init_size);
Packit 032894
Packit 032894
  /* Initialize the data structure.  */
Packit 032894
  htab->size = init_size;
Packit 032894
  atomic_init(&htab->filled, 0);
Packit 032894
  atomic_init(&htab->resizing_state, 0);
Packit 032894
Packit 032894
  atomic_init(&htab->next_init_block, 0);
Packit 032894
  atomic_init(&htab->num_initialized_blocks, 0);
Packit 032894
Packit 032894
  atomic_init(&htab->next_move_block, 0);
Packit 032894
  atomic_init(&htab->num_moved_blocks, 0);
Packit 032894
Packit 032894
  pthread_rwlock_init(&htab->resize_rwl, NULL);
Packit 032894
Packit 032894
  htab->table = (void *) malloc ((init_size + 1) * sizeof (htab->table[0]));
Packit 032894
  if (htab->table == NULL)
Packit 032894
      return -1;
Packit 032894
Packit 032894
  for (size_t i = 0; i <= init_size; i++)
Packit 032894
    {
Packit 032894
      atomic_init(&htab->table[i].hashval, (uintptr_t) NULL);
Packit 032894
      atomic_init(&htab->table[i].val_ptr, (uintptr_t) NULL);
Packit 032894
    }
Packit 032894
Packit 032894
  return 0;
Packit 032894
}
Packit 032894
Packit 032894
Packit 032894
int
Packit 032894
#define FREE(name) _FREE (name)
Packit 032894
#define _FREE(name) \
Packit 032894
name##_free
Packit 032894
FREE(NAME) (NAME *htab)
Packit 032894
{
Packit 032894
  pthread_rwlock_destroy(&htab->resize_rwl);
Packit 032894
  free (htab->table);
Packit 032894
  return 0;
Packit 032894
}
Packit 032894
Packit 032894
Packit 032894
int
Packit 032894
#define INSERT(name) _INSERT (name)
Packit 032894
#define _INSERT(name) \
Packit 032894
name##_insert
Packit 032894
INSERT(NAME) (NAME *htab, HASHTYPE hval, TYPE data)
Packit 032894
{
Packit 032894
  int incremented = 0;
Packit 032894
Packit 032894
  for(;;)
Packit 032894
    {
Packit 032894
      while (pthread_rwlock_tryrdlock(&htab->resize_rwl) != 0)
Packit 032894
          resize_worker(htab);
Packit 032894
Packit 032894
      size_t filled;
Packit 032894
      if (!incremented)
Packit 032894
        {
Packit 032894
          filled = atomic_fetch_add_explicit(&htab->filled, 1,
Packit 032894
                                              memory_order_acquire);
Packit 032894
          incremented = 1;
Packit 032894
        }
Packit 032894
      else
Packit 032894
        {
Packit 032894
          filled = atomic_load_explicit(&htab->filled,
Packit 032894
                                        memory_order_acquire);
Packit 032894
        }
Packit 032894
Packit 032894
Packit 032894
      if (100 * filled > 90 * htab->size)
Packit 032894
        {
Packit 032894
          /* Table is filled more than 90%.  Resize the table.  */
Packit 032894
Packit 032894
          size_t resizing_state = atomic_load_explicit(&htab->resizing_state,
Packit 032894
                                                        memory_order_acquire);
Packit 032894
          if (resizing_state == 0 &&
Packit 032894
              atomic_compare_exchange_strong_explicit(&htab->resizing_state,
Packit 032894
                                                      &resizing_state,
Packit 032894
                                                      ALLOCATING_MEMORY,
Packit 032894
                                                      memory_order_acquire,
Packit 032894
                                                      memory_order_acquire))
Packit 032894
            {
Packit 032894
              /* Master thread */
Packit 032894
              pthread_rwlock_unlock(&htab->resize_rwl);
Packit 032894
Packit 032894
              pthread_rwlock_wrlock(&htab->resize_rwl);
Packit 032894
              resize_master(htab);
Packit 032894
              pthread_rwlock_unlock(&htab->resize_rwl);
Packit 032894
Packit 032894
            }
Packit 032894
          else
Packit 032894
            {
Packit 032894
              /* Worker thread */
Packit 032894
              pthread_rwlock_unlock(&htab->resize_rwl);
Packit 032894
              resize_worker(htab);
Packit 032894
            }
Packit 032894
        }
Packit 032894
      else
Packit 032894
        {
Packit 032894
          /* Lock acquired, no need for resize*/
Packit 032894
          break;
Packit 032894
        }
Packit 032894
    }
Packit 032894
Packit 032894
  int ret_val = insert_helper(htab, hval, data);
Packit 032894
  if (ret_val == -1)
Packit 032894
      atomic_fetch_sub_explicit(&htab->filled, 1, memory_order_relaxed);
Packit 032894
  pthread_rwlock_unlock(&htab->resize_rwl);
Packit 032894
  return ret_val;
Packit 032894
}
Packit 032894
Packit 032894
Packit 032894
Packit 032894
TYPE
Packit 032894
#define FIND(name) _FIND (name)
Packit 032894
#define _FIND(name) \
Packit 032894
  name##_find
Packit 032894
FIND(NAME) (NAME *htab, HASHTYPE hval)
Packit 032894
{
Packit 032894
  while (pthread_rwlock_tryrdlock(&htab->resize_rwl) != 0)
Packit 032894
      resize_worker(htab);
Packit 032894
Packit 032894
  size_t idx;
Packit 032894
Packit 032894
  /* Make the hash data nonzero.  */
Packit 032894
  hval = hval ?: 1;
Packit 032894
  idx = lookup(htab, hval);
Packit 032894
Packit 032894
  if (idx == 0)
Packit 032894
    {
Packit 032894
      pthread_rwlock_unlock(&htab->resize_rwl);
Packit 032894
      return NULL;
Packit 032894
    }
Packit 032894
Packit 032894
  /* get a copy before unlocking the lock */
Packit 032894
  TYPE ret_val = (TYPE) atomic_load_explicit(&htab->table[idx].val_ptr,
Packit 032894
                                             memory_order_relaxed);
Packit 032894
Packit 032894
  pthread_rwlock_unlock(&htab->resize_rwl);
Packit 032894
  return ret_val;
Packit 032894
}