Arquivos
hhvm/hphp/runtime/vm/tread_hash_map.h
T
Tim Starling 998951619f update copyright date
We did not intend to imply our copyrights last forever

Closes #759
2013-06-03 12:43:56 -07:00

245 linhas
7.2 KiB
C++

/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#ifndef incl_HPHP_VM_TREAD_HASH_MAP_H_
#define incl_HPHP_VM_TREAD_HASH_MAP_H_
#include <boost/noncopyable.hpp>
#include <boost/iterator/iterator_facade.hpp>
#include <boost/type_traits/is_convertible.hpp>
#include <boost/utility/enable_if.hpp>
#include <utility>
#include "hphp/util/util.h"
#include "hphp/util/atomic.h"
namespace HPHP {
//////////////////////////////////////////////////////////////////////
namespace Treadmill { void deferredFree(void*); }
//////////////////////////////////////////////////////////////////////
/*
* A hashtable safe for multiple concurrent readers, even while writes
* are happening, but with at most one concurrent writer. Reads and
* writes both are wait-free, but that there is only at most one
* writer will generally require synchronization outside of this
* class. (E.g. the translator write lease.)
*
* Key must be an atomically loadable/storable type. The Value must
* be a trivially copyable and assignable type, and it must be legal
* to copy from it without synchronization (this table may do this
* during a rehash). Also, assumes Key == 0 is invalid (i.e. the
* empty key).
*
* Insertions must be unique. It is an error to insert the same key
* more than once.
*
* Uses the treadmill to collect garbage.
*/
template<class Key, class Val, class HashFunc>
struct TreadHashMap : private boost::noncopyable {
typedef std::pair<Key,Val> value_type;
private:
struct Table {
size_t capac;
size_t size;
value_type entries[0];
};
public:
explicit TreadHashMap(size_t initialCapacity)
: m_table(allocTable(initialCapacity))
{}
~TreadHashMap() {
free(m_table);
}
template<class IterVal>
struct thm_iterator
: boost::iterator_facade<thm_iterator<IterVal>,IterVal,
boost::forward_traversal_tag>
{
explicit thm_iterator() : m_table(0) {}
// Conversion constructor for interoperability between iterator
// and const_iterator. The enable_if<> magic prevents the
// constructor from existing if we're going in the const_iterator
// to iterator direction.
template<class OtherVal>
thm_iterator(const thm_iterator<OtherVal>& o,
typename boost::enable_if<
boost::is_convertible<OtherVal*,IterVal*>
>::type* = 0)
: m_table(o.m_table)
, m_offset(o.m_offset)
{}
explicit thm_iterator(Table* tab, size_t offset)
: m_table(tab)
, m_offset(offset)
{
advancePastEmpty();
}
private:
friend class TreadHashMap;
friend class boost::iterator_core_access;
void increment() {
++m_offset;
advancePastEmpty();
}
bool equal(const thm_iterator& o) const {
return m_table == o.m_table && m_offset == o.m_offset;
}
IterVal& dereference() const {
return m_table->entries[m_offset];
}
private:
void advancePastEmpty() {
while (m_offset < m_table->capac &&
atomic_acquire_load(&m_table->entries[m_offset].first) == 0) {
++m_offset;
}
}
private:
Table* m_table;
size_t m_offset;
};
typedef thm_iterator<value_type> iterator;
typedef thm_iterator<const value_type> const_iterator;
iterator begin() {
return iterator(atomic_acquire_load(&m_table), 0);
}
iterator end() {
Table* tab = atomic_acquire_load(&m_table);
return iterator(tab, tab->capac);
}
const_iterator begin() const {
return const_cast<TreadHashMap*>(this)->begin();
}
const_iterator end() const {
return const_cast<TreadHashMap*>(this)->end();
}
Val* insert(Key key, Val val) {
assert(key != 0);
return insertImpl(acquireAndGrowIfNeeded(), key, val);
}
Val* find(Key key) const {
assert(key != 0);
Table* tab = atomic_acquire_load(&m_table); // memory_order_consume
assert(tab->capac > tab->size);
size_t idx = project(tab, key);
for (;;) {
Key currentProbe = atomic_acquire_load(&tab->entries[idx].first);
if (currentProbe == key) return &tab->entries[idx].second;
if (currentProbe == 0) return 0;
if (++idx == tab->capac) idx = 0;
}
}
private:
Val* insertImpl(Table* const tab, Key newKey, Val newValue) {
value_type* probe = &tab->entries[project(tab, newKey)];
assert(size_t(probe - tab->entries) < tab->capac);
// Since we're the only thread allowed to write, we're allowed to
// do a relaxed load here. (No need for an acquire/release
// handshake with ourselves.)
while (Key currentProbe = probe->first) {
assert(currentProbe != newKey); // insertions must be unique
assert(probe <= (tab->entries + tab->capac));
// can't loop forever; acquireAndGrowIfNeeded ensures there's
// some slack.
(void)currentProbe;
if (++probe == (tab->entries + tab->capac)) probe = tab->entries;
}
// Copy over the value before we publish.
probe->second = newValue;
// Make it visible.
atomic_release_store(&probe->first, newKey);
// size is only written to by the writer thread, relaxed memory
// ordering is ok.
++tab->size;
return &probe->second;
}
Table* acquireAndGrowIfNeeded() {
// Relaxed load is ok---there's only one writer thread.
Table* old = m_table;
// 75% occupancy, avoiding the FPU.
if (LIKELY(old->size < (old->capac / 4 + old->capac / 2))) {
return old;
}
// Rehash from old to new.
Table* newTable = allocTable(old->capac * 2);
for (size_t i = 0; i < old->capac; ++i) {
value_type* ent = old->entries + i;
if (ent->first) {
insertImpl(newTable, ent->first, ent->second);
}
}
assert(newTable->capac == old->capac * 2);
assert(newTable->size == old->size); // only one writer thread
atomic_release_store(&m_table, newTable); // publish
Treadmill::deferredFree(old);
return newTable;
}
size_t project(Table* tab, Key key) const {
assert(Util::isPowerOfTwo(tab->capac));
return m_hash(key) & (tab->capac - 1);
}
static Table* allocTable(size_t capacity) {
Table* ret = static_cast<Table*>(
calloc(1, capacity * sizeof(value_type) + sizeof(Table)));
ret->capac = capacity;
ret->size = 0;
return ret;
}
private:
HashFunc m_hash;
Table* m_table;
};
//////////////////////////////////////////////////////////////////////
}
#endif