зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1491151 - Part 1: Add MruCache. r=smaug,froydnj
This adds a most recently used cache that can be used as a quick lookup table for frequently used entries. --HG-- extra : rebase_source : 571c32f75e985e299113f73b959809d208aad5f3
This commit is contained in:
Родитель
348f0a0020
Коммит
b9e8fd399e
|
@ -0,0 +1,183 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_MruCache_h
|
||||
#define mozilla_MruCache_h
|
||||
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/HashFunctions.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Helper struct for checking if a value is empty.
|
||||
//
|
||||
// `IsNotEmpty` will return true if `Value` is not a pointer type or if the
|
||||
// pointer value is not null.
|
||||
template <typename Value, bool IsPtr = std::is_pointer<Value>::value>
|
||||
struct EmptyChecker
|
||||
{
|
||||
static bool IsNotEmpty(const Value&) { return true; }
|
||||
};
|
||||
// Template specialization for the `IsPtr == true` case.
|
||||
template <typename Value>
|
||||
struct EmptyChecker<Value, true>
|
||||
{
|
||||
static bool IsNotEmpty(const Value& aVal) { return aVal != nullptr; }
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// Provides a most recently used cache that can be used as a layer on top of
|
||||
// a larger container where lookups can be expensive. The default size is 31,
|
||||
// which as a prime number provides a better distrubution of cached entries.
|
||||
//
|
||||
// Users are expected to provide a `Cache` class that defines two required
|
||||
// methods:
|
||||
// - A method for providing the hash of a key:
|
||||
//
|
||||
// static HashNumber Hash(const KeyType& aKey)
|
||||
//
|
||||
// - A method for matching a key to a value, for pointer types the value
|
||||
// is guaranteed not to be null.
|
||||
//
|
||||
// static bool Match(const KeyType& aKey, const ValueType& aVal)
|
||||
//
|
||||
// For example:
|
||||
// class MruExample : public MruCache<void*, PtrInfo*, MruExample>
|
||||
// {
|
||||
// static HashNumber Hash(const KeyType& aKey)
|
||||
// {
|
||||
// return HashGeneric(aKey);
|
||||
// }
|
||||
// static Match(const KeyType& aKey, const ValueType& aVal)
|
||||
// {
|
||||
// return aVal->mPtr == aKey;
|
||||
// }
|
||||
// };
|
||||
template <class Key, class Value, class Cache, size_t Size=31>
|
||||
class MruCache
|
||||
{
|
||||
// Best distribution is achieved with a prime number. Ideally the closest
|
||||
// to a power of two will be the most efficient use of memory. This
|
||||
// assertion is pretty weak, but should catch the common inclination to
|
||||
// use a power-of-two.
|
||||
static_assert(Size % 2 != 0, "Use a prime number");
|
||||
|
||||
// This is a stronger assertion but significantly limits the values to just
|
||||
// those close to a power-of-two value.
|
||||
//static_assert(Size == 7 || Size == 13 || Size == 31 || Size == 61 ||
|
||||
// Size == 127 || Size == 251 || Size == 509 || Size == 1021,
|
||||
// "Use a prime number less than 1024");
|
||||
|
||||
public:
|
||||
using KeyType = Key;
|
||||
using ValueType = Value;
|
||||
|
||||
MruCache() = default;
|
||||
MruCache(const MruCache&) = delete;
|
||||
MruCache(const MruCache&&) = delete;
|
||||
|
||||
// Inserts the given value into the cache. Potentially overwrites an
|
||||
// existing entry.
|
||||
template <typename U>
|
||||
void Put(const KeyType& aKey, U&& aVal)
|
||||
{
|
||||
*RawEntry(aKey) = std::forward<U>(aVal);
|
||||
}
|
||||
|
||||
// Removes the given entry if it is in the cache.
|
||||
void Remove(const KeyType& aKey)
|
||||
{
|
||||
Lookup(aKey).Remove();
|
||||
}
|
||||
|
||||
// Clears all cached entries and resets them to a default value.
|
||||
void Clear()
|
||||
{
|
||||
for (ValueType& val : mCache) {
|
||||
val = ValueType{};
|
||||
}
|
||||
}
|
||||
|
||||
// Helper that holds an entry that matched a lookup key. Usage:
|
||||
//
|
||||
// auto p = mCache.Lookup(aKey);
|
||||
// if (p) {
|
||||
// return p.Data();
|
||||
// }
|
||||
//
|
||||
// auto foo = new Foo();
|
||||
// mTable.Insert(aKey, foo);
|
||||
// p.Set(foo);
|
||||
// return foo;
|
||||
class Entry
|
||||
{
|
||||
public:
|
||||
Entry(ValueType* aEntry, bool aMatch)
|
||||
: mEntry(aEntry)
|
||||
, mMatch(aMatch)
|
||||
{
|
||||
MOZ_ASSERT(mEntry);
|
||||
}
|
||||
|
||||
explicit operator bool() const { return mMatch; }
|
||||
|
||||
ValueType& Data() const
|
||||
{
|
||||
MOZ_ASSERT(mMatch);
|
||||
return *mEntry;
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
void Set(U&& aValue)
|
||||
{
|
||||
mMatch = true;
|
||||
Data() = std::forward<U>(aValue);
|
||||
}
|
||||
|
||||
void Remove()
|
||||
{
|
||||
if (mMatch) {
|
||||
Data() = ValueType{};
|
||||
mMatch = false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
ValueType* mEntry; // Location of the entry in the cache.
|
||||
bool mMatch; // Whether the value matched.
|
||||
};
|
||||
|
||||
// Retrieves an entry from the cache. Can be used to test if an entry is
|
||||
// present, update the entry to a new value, or remove the entry if one was
|
||||
// matched.
|
||||
Entry Lookup(const KeyType& aKey)
|
||||
{
|
||||
using EmptyChecker = detail::EmptyChecker<ValueType>;
|
||||
|
||||
auto entry = RawEntry(aKey);
|
||||
bool match = EmptyChecker::IsNotEmpty(*entry) && Cache::Match(aKey, *entry);
|
||||
return Entry(entry, match);
|
||||
}
|
||||
|
||||
private:
|
||||
MOZ_ALWAYS_INLINE ValueType* RawEntry(const KeyType& aKey)
|
||||
{
|
||||
return &mCache[Cache::Hash(aKey) % Size];
|
||||
}
|
||||
|
||||
ValueType mCache[Size] = {};
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_mrucache_h
|
|
@ -87,6 +87,7 @@ EXPORTS.mozilla += [
|
|||
'AtomArray.h',
|
||||
'Dafsa.h',
|
||||
'IncrementalTokenizer.h',
|
||||
'MruCache.h',
|
||||
'Observer.h',
|
||||
'SimpleEnumerator.h',
|
||||
'StickyTimeDuration.h',
|
||||
|
|
|
@ -0,0 +1,393 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "mozilla/MruCache.h"
|
||||
#include "nsString.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
// A few MruCache implementations to use during testing.
|
||||
struct IntMap : public MruCache<int, int, IntMap>
|
||||
{
|
||||
static HashNumber Hash(const KeyType& aKey) { return aKey - 1; }
|
||||
static bool Match(const KeyType& aKey, const ValueType& aVal) { return aKey == aVal; }
|
||||
};
|
||||
|
||||
struct UintPtrMap : public MruCache<uintptr_t, int*, UintPtrMap>
|
||||
{
|
||||
static HashNumber Hash(const KeyType& aKey) { return aKey - 1; }
|
||||
static bool Match(const KeyType& aKey, const ValueType& aVal) { return aKey == (KeyType)aVal; }
|
||||
};
|
||||
|
||||
struct StringStruct
|
||||
{
|
||||
nsCString mKey;
|
||||
nsCString mOther;
|
||||
};
|
||||
|
||||
struct StringStructMap : public MruCache<nsCString, StringStruct, StringStructMap>
|
||||
{
|
||||
static HashNumber Hash(const KeyType& aKey) { return *aKey.BeginReading() - 1; }
|
||||
static bool Match(const KeyType& aKey, const ValueType& aVal) { return aKey == aVal.mKey; }
|
||||
};
|
||||
|
||||
// Helper for emulating convertable holders such as RefPtr.
|
||||
template <typename T>
|
||||
struct Convertable
|
||||
{
|
||||
T mItem;
|
||||
operator T() const { return mItem; }
|
||||
};
|
||||
|
||||
// Helper to create a StringStructMap key.
|
||||
nsCString MakeStringKey(char aKey)
|
||||
{
|
||||
nsCString key;
|
||||
key.Append(aKey);
|
||||
return key;
|
||||
}
|
||||
|
||||
TEST(MruCache, TestNullChecker)
|
||||
{
|
||||
using mozilla::detail::EmptyChecker;
|
||||
|
||||
{
|
||||
int test = 0;
|
||||
EXPECT_TRUE(EmptyChecker<decltype(test)>::IsNotEmpty(test));
|
||||
|
||||
test = 42;
|
||||
EXPECT_TRUE(EmptyChecker<decltype(test)>::IsNotEmpty(test));
|
||||
}
|
||||
|
||||
{
|
||||
const char* test = "abc";
|
||||
EXPECT_TRUE(EmptyChecker<decltype(test)>::IsNotEmpty(test));
|
||||
|
||||
test = nullptr;
|
||||
EXPECT_FALSE(EmptyChecker<decltype(test)>::IsNotEmpty(test));
|
||||
}
|
||||
|
||||
{
|
||||
int foo = 42;
|
||||
int* test = &foo;
|
||||
EXPECT_TRUE(EmptyChecker<decltype(test)>::IsNotEmpty(test));
|
||||
|
||||
test = nullptr;
|
||||
EXPECT_FALSE(EmptyChecker<decltype(test)>::IsNotEmpty(test));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(MruCache, TestEmptyCache)
|
||||
{
|
||||
{
|
||||
// Test a basic empty cache.
|
||||
IntMap mru;
|
||||
|
||||
// Make sure the default values are set.
|
||||
for (int i = 1; i < 32; i++) {
|
||||
auto p = mru.Lookup(i);
|
||||
|
||||
// Shouldn't be found.
|
||||
EXPECT_FALSE(p);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Test an empty cache with pointer values.
|
||||
UintPtrMap mru;
|
||||
|
||||
// Make sure the default values are set.
|
||||
for (uintptr_t i = 1; i < 32; i++) {
|
||||
auto p = mru.Lookup(i);
|
||||
|
||||
// Shouldn't be found.
|
||||
EXPECT_FALSE(p);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Test an empty cache with more complex structure.
|
||||
StringStructMap mru;
|
||||
|
||||
// Make sure the default values are set.
|
||||
for (char i = 1; i < 32; i++) {
|
||||
const nsCString key = MakeStringKey(i);
|
||||
auto p = mru.Lookup(key);
|
||||
|
||||
// Shouldn't be found.
|
||||
EXPECT_FALSE(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(MruCache, TestPut)
|
||||
{
|
||||
IntMap mru;
|
||||
|
||||
// Fill it up.
|
||||
for (int i = 1; i < 32; i++) {
|
||||
mru.Put(i, i);
|
||||
}
|
||||
|
||||
// Now check each value.
|
||||
for (int i = 1; i < 32; i++) {
|
||||
auto p = mru.Lookup(i);
|
||||
|
||||
// Should be found.
|
||||
EXPECT_TRUE(p);
|
||||
EXPECT_EQ(p.Data(), i);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(MruCache, TestPutConvertable)
|
||||
{
|
||||
UintPtrMap mru;
|
||||
|
||||
// Fill it up.
|
||||
for (uintptr_t i = 1; i < 32; i++) {
|
||||
Convertable<int*> val{(int*)i};
|
||||
mru.Put(i, val);
|
||||
}
|
||||
|
||||
// Now check each value.
|
||||
for (uintptr_t i = 1; i < 32; i++) {
|
||||
auto p = mru.Lookup(i);
|
||||
|
||||
// Should be found.
|
||||
EXPECT_TRUE(p);
|
||||
EXPECT_EQ(p.Data(), (int*)i);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(MruCache, TestOverwriting)
|
||||
{
|
||||
// Test overwrting
|
||||
IntMap mru;
|
||||
|
||||
// 1-31 should be overwritten by 32-63
|
||||
for (int i = 1; i < 63; i++) {
|
||||
mru.Put(i, i);
|
||||
}
|
||||
|
||||
// Look them up.
|
||||
for (int i = 32; i < 63; i++) {
|
||||
auto p = mru.Lookup(i);
|
||||
|
||||
// Should be found.
|
||||
EXPECT_TRUE(p);
|
||||
EXPECT_EQ(p.Data(), i);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(MruCache, TestRemove)
|
||||
{
|
||||
{
|
||||
IntMap mru;
|
||||
|
||||
// Fill it up.
|
||||
for (int i = 1; i < 32; i++) {
|
||||
mru.Put(i, i);
|
||||
}
|
||||
|
||||
// Now remove each value.
|
||||
for (int i = 1; i < 32; i++) {
|
||||
// Should be present.
|
||||
auto p = mru.Lookup(i);
|
||||
EXPECT_TRUE(p);
|
||||
|
||||
mru.Remove(i);
|
||||
|
||||
// Should no longer match.
|
||||
p = mru.Lookup(i);
|
||||
EXPECT_FALSE(p);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
UintPtrMap mru;
|
||||
|
||||
// Fill it up.
|
||||
for (uintptr_t i = 1; i < 32; i++) {
|
||||
mru.Put(i, (int*)i);
|
||||
}
|
||||
|
||||
// Now remove each value.
|
||||
for (uintptr_t i = 1; i < 32; i++) {
|
||||
// Should be present.
|
||||
auto p = mru.Lookup(i);
|
||||
EXPECT_TRUE(p);
|
||||
|
||||
mru.Remove(i);
|
||||
|
||||
// Should no longer match.
|
||||
p = mru.Lookup(i);
|
||||
EXPECT_FALSE(p);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
StringStructMap mru;
|
||||
|
||||
// Fill it up.
|
||||
for (char i = 1; i < 32; i++) {
|
||||
const nsCString key = MakeStringKey(i);
|
||||
mru.Put(key, StringStruct{key, NS_LITERAL_CSTRING("foo")});
|
||||
}
|
||||
|
||||
// Now remove each value.
|
||||
for (char i = 1; i < 32; i++) {
|
||||
const nsCString key = MakeStringKey(i);
|
||||
|
||||
// Should be present.
|
||||
auto p = mru.Lookup(key);
|
||||
EXPECT_TRUE(p);
|
||||
|
||||
mru.Remove(key);
|
||||
|
||||
// Should no longer match.
|
||||
p = mru.Lookup(key);
|
||||
EXPECT_FALSE(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(MruCache, TestClear)
|
||||
{
|
||||
IntMap mru;
|
||||
|
||||
// Fill it up.
|
||||
for (int i = 1; i < 32; i++) {
|
||||
mru.Put(i, i);
|
||||
}
|
||||
|
||||
// Empty it.
|
||||
mru.Clear();
|
||||
|
||||
// Now check each value.
|
||||
for (int i = 1; i < 32; i++) {
|
||||
auto p = mru.Lookup(i);
|
||||
|
||||
// Should not be found.
|
||||
EXPECT_FALSE(p);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(MruCache, TestLookupMissingAndSet)
|
||||
{
|
||||
IntMap mru;
|
||||
|
||||
// Value not found.
|
||||
auto p = mru.Lookup(1);
|
||||
EXPECT_FALSE(p);
|
||||
|
||||
// Set it.
|
||||
p.Set(1);
|
||||
EXPECT_TRUE(p);
|
||||
EXPECT_EQ(p.Data(), 1);
|
||||
|
||||
// Look it up again.
|
||||
p = mru.Lookup(1);
|
||||
EXPECT_TRUE(p);
|
||||
EXPECT_EQ(p.Data(), 1);
|
||||
|
||||
// Test w/ a convertable value.
|
||||
p = mru.Lookup(2);
|
||||
EXPECT_FALSE(p);
|
||||
|
||||
// Set it.
|
||||
Convertable<int> val{2};
|
||||
p.Set(val);
|
||||
EXPECT_TRUE(p);
|
||||
EXPECT_EQ(p.Data(), 2);
|
||||
|
||||
// Look it up again.
|
||||
p = mru.Lookup(2);
|
||||
EXPECT_TRUE(p);
|
||||
EXPECT_EQ(p.Data(), 2);
|
||||
}
|
||||
|
||||
TEST(MruCache, TestLookupAndOverwrite)
|
||||
{
|
||||
IntMap mru;
|
||||
|
||||
// Set 1.
|
||||
mru.Put(1, 1);
|
||||
|
||||
// Lookup a key that maps the 1's entry.
|
||||
auto p = mru.Lookup(32);
|
||||
EXPECT_FALSE(p); // not a match
|
||||
|
||||
// Now overwrite the entry.
|
||||
p.Set(32);
|
||||
EXPECT_TRUE(p);
|
||||
EXPECT_EQ(p.Data(), 32);
|
||||
|
||||
// 1 should be gone now.
|
||||
p = mru.Lookup(1);
|
||||
EXPECT_FALSE(p);
|
||||
|
||||
// 32 should be found.
|
||||
p = mru.Lookup(32);
|
||||
EXPECT_TRUE(p);
|
||||
EXPECT_EQ(p.Data(), 32);
|
||||
}
|
||||
|
||||
TEST(MruCache, TestLookupAndRemove)
|
||||
{
|
||||
IntMap mru;
|
||||
|
||||
// Set 1.
|
||||
mru.Put(1, 1);
|
||||
|
||||
auto p = mru.Lookup(1);
|
||||
EXPECT_TRUE(p);
|
||||
EXPECT_EQ(p.Data(), 1);
|
||||
|
||||
// Now remove it.
|
||||
p.Remove();
|
||||
EXPECT_FALSE(p);
|
||||
|
||||
p = mru.Lookup(1);
|
||||
EXPECT_FALSE(p);
|
||||
}
|
||||
|
||||
TEST(MruCache, TestLookupNotMatchedAndRemove)
|
||||
{
|
||||
IntMap mru;
|
||||
|
||||
// Set 1.
|
||||
mru.Put(1, 1);
|
||||
|
||||
// Lookup a key that matches 1's entry.
|
||||
auto p = mru.Lookup(32);
|
||||
EXPECT_FALSE(p);
|
||||
|
||||
// Now attempt to remove it.
|
||||
p.Remove();
|
||||
|
||||
// Make sure 1 is still there.
|
||||
p = mru.Lookup(1);
|
||||
EXPECT_TRUE(p);
|
||||
EXPECT_EQ(p.Data(), 1);
|
||||
}
|
||||
|
||||
TEST(MruCache, TestLookupAndSetWithMove)
|
||||
{
|
||||
StringStructMap mru;
|
||||
|
||||
const nsCString key = MakeStringKey((char)1);
|
||||
StringStruct val{key, NS_LITERAL_CSTRING("foo")};
|
||||
|
||||
auto p = mru.Lookup(key);
|
||||
EXPECT_FALSE(p);
|
||||
p.Set(std::move(val));
|
||||
|
||||
EXPECT_TRUE(p.Data().mKey == key);
|
||||
EXPECT_TRUE(p.Data().mOther == NS_LITERAL_CSTRING("foo"));
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "nsTObserverArray.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "nsTArray.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ UNIFIED_SOURCES += [
|
|||
'TestLogCommandLineHandler.cpp',
|
||||
'TestMoveString.cpp',
|
||||
'TestMozPromise.cpp',
|
||||
'TestMruCache.cpp',
|
||||
'TestMultiplexInputStream.cpp',
|
||||
'TestNonBlockingAsyncInputStream.cpp',
|
||||
'TestNsDeque.cpp',
|
||||
|
|
Загрузка…
Ссылка в новой задаче