Add RecyclingZoneAllocator for ZoneDeque.

A std::deque interacts badly with zone memory in that it allocates chunks
of memory for the back of the queue and frees memory from the front of the
queue. As such we never reuse zone memory for the queue. Implement a very
simple RecyclingZoneAllocator which keeps a single block of memory from
deallocation that can be reused on allocation.

Also clean up zone-allocator a bit and make it use proper Chromium coding
style.

BUG=chromium:700364

Change-Id: I19330a8a9ec6d75fe18d8168d41f1a12030a6c4d
Reviewed-on: https://chromium-review.googlesource.com/458916
Reviewed-by: Michael Starzinger <mstarzinger@chromium.org>
Reviewed-by: Ross McIlroy <rmcilroy@chromium.org>
Commit-Queue: Ross McIlroy <rmcilroy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#44154}
This commit is contained in:
Ross McIlroy 2017-03-24 17:32:08 +00:00 committed by Commit Bot
parent bdb4a8d33d
commit b90a20b2c7
7 changed files with 201 additions and 55 deletions

View File

@ -1469,7 +1469,8 @@ std::ostream& operator<<(std::ostream& os,
typedef ZoneDeque<Constant> ConstantDeque; typedef ZoneDeque<Constant> ConstantDeque;
typedef std::map<int, Constant, std::less<int>, typedef std::map<int, Constant, std::less<int>,
zone_allocator<std::pair<const int, Constant> > > ConstantMap; ZoneAllocator<std::pair<const int, Constant> > >
ConstantMap;
typedef ZoneDeque<Instruction*> InstructionDeque; typedef ZoneDeque<Instruction*> InstructionDeque;
typedef ZoneDeque<ReferenceMap*> ReferenceMapDeque; typedef ZoneDeque<ReferenceMap*> ReferenceMapDeque;

View File

@ -19686,38 +19686,38 @@ struct StringHandleEqual {
class UnorderedStringSet class UnorderedStringSet
: public std::unordered_set<Handle<String>, StringHandleHash, : public std::unordered_set<Handle<String>, StringHandleHash,
StringHandleEqual, StringHandleEqual,
zone_allocator<Handle<String>>> { ZoneAllocator<Handle<String>>> {
public: public:
explicit UnorderedStringSet(Zone* zone) explicit UnorderedStringSet(Zone* zone)
: std::unordered_set<Handle<String>, StringHandleHash, StringHandleEqual, : std::unordered_set<Handle<String>, StringHandleHash, StringHandleEqual,
zone_allocator<Handle<String>>>( ZoneAllocator<Handle<String>>>(
2 /* bucket count */, StringHandleHash(), StringHandleEqual(), 2 /* bucket count */, StringHandleHash(), StringHandleEqual(),
zone_allocator<Handle<String>>(zone)) {} ZoneAllocator<Handle<String>>(zone)) {}
}; };
class UnorderedModuleSet class UnorderedModuleSet
: public std::unordered_set<Handle<Module>, ModuleHandleHash, : public std::unordered_set<Handle<Module>, ModuleHandleHash,
ModuleHandleEqual, ModuleHandleEqual,
zone_allocator<Handle<Module>>> { ZoneAllocator<Handle<Module>>> {
public: public:
explicit UnorderedModuleSet(Zone* zone) explicit UnorderedModuleSet(Zone* zone)
: std::unordered_set<Handle<Module>, ModuleHandleHash, ModuleHandleEqual, : std::unordered_set<Handle<Module>, ModuleHandleHash, ModuleHandleEqual,
zone_allocator<Handle<Module>>>( ZoneAllocator<Handle<Module>>>(
2 /* bucket count */, ModuleHandleHash(), ModuleHandleEqual(), 2 /* bucket count */, ModuleHandleHash(), ModuleHandleEqual(),
zone_allocator<Handle<Module>>(zone)) {} ZoneAllocator<Handle<Module>>(zone)) {}
}; };
class UnorderedStringMap class UnorderedStringMap
: public std::unordered_map< : public std::unordered_map<
Handle<String>, Handle<Object>, StringHandleHash, StringHandleEqual, Handle<String>, Handle<Object>, StringHandleHash, StringHandleEqual,
zone_allocator<std::pair<const Handle<String>, Handle<Object>>>> { ZoneAllocator<std::pair<const Handle<String>, Handle<Object>>>> {
public: public:
explicit UnorderedStringMap(Zone* zone) explicit UnorderedStringMap(Zone* zone)
: std::unordered_map< : std::unordered_map<
Handle<String>, Handle<Object>, StringHandleHash, StringHandleEqual, Handle<String>, Handle<Object>, StringHandleHash, StringHandleEqual,
zone_allocator<std::pair<const Handle<String>, Handle<Object>>>>( ZoneAllocator<std::pair<const Handle<String>, Handle<Object>>>>(
2 /* bucket count */, StringHandleHash(), StringHandleEqual(), 2 /* bucket count */, StringHandleHash(), StringHandleEqual(),
zone_allocator<std::pair<const Handle<String>, Handle<Object>>>( ZoneAllocator<std::pair<const Handle<String>, Handle<Object>>>(
zone)) {} zone)) {}
}; };
@ -19726,17 +19726,17 @@ class UnorderedStringMap
class Module::ResolveSet class Module::ResolveSet
: public std::unordered_map< : public std::unordered_map<
Handle<Module>, UnorderedStringSet*, ModuleHandleHash, Handle<Module>, UnorderedStringSet*, ModuleHandleHash,
ModuleHandleEqual, zone_allocator<std::pair<const Handle<Module>, ModuleHandleEqual,
UnorderedStringSet*>>> { ZoneAllocator<std::pair<const Handle<Module>, UnorderedStringSet*>>> {
public: public:
explicit ResolveSet(Zone* zone) explicit ResolveSet(Zone* zone)
: std::unordered_map<Handle<Module>, UnorderedStringSet*, : std::unordered_map<Handle<Module>, UnorderedStringSet*,
ModuleHandleHash, ModuleHandleEqual, ModuleHandleHash, ModuleHandleEqual,
zone_allocator<std::pair<const Handle<Module>, ZoneAllocator<std::pair<const Handle<Module>,
UnorderedStringSet*>>>( UnorderedStringSet*>>>(
2 /* bucket count */, ModuleHandleHash(), ModuleHandleEqual(), 2 /* bucket count */, ModuleHandleHash(), ModuleHandleEqual(),
zone_allocator< ZoneAllocator<std::pair<const Handle<Module>, UnorderedStringSet*>>(
std::pair<const Handle<Module>, UnorderedStringSet*>>(zone)), zone)),
zone_(zone) {} zone_(zone) {}
Zone* zone() const { return zone_; } Zone* zone() const { return zone_; }

View File

@ -12,7 +12,7 @@ namespace v8 {
namespace internal { namespace internal {
template <typename T> template <typename T>
class zone_allocator { class ZoneAllocator {
public: public:
typedef T* pointer; typedef T* pointer;
typedef const T* const_pointer; typedef const T* const_pointer;
@ -23,33 +23,33 @@ class zone_allocator {
typedef ptrdiff_t difference_type; typedef ptrdiff_t difference_type;
template <class O> template <class O>
struct rebind { struct rebind {
typedef zone_allocator<O> other; typedef ZoneAllocator<O> other;
}; };
#ifdef V8_CC_MSVC #ifdef V8_CC_MSVC
// MSVS unfortunately requires the default constructor to be defined. // MSVS unfortunately requires the default constructor to be defined.
zone_allocator() : zone_(nullptr) { UNREACHABLE(); } ZoneAllocator() : ZoneAllocator(nullptr) { UNREACHABLE(); }
#endif #endif
explicit zone_allocator(Zone* zone) throw() : zone_(zone) {} explicit ZoneAllocator(Zone* zone) throw() : zone_(zone) {}
explicit zone_allocator(const zone_allocator& other) throw() explicit ZoneAllocator(const ZoneAllocator& other) throw()
: zone_(other.zone_) {} : ZoneAllocator<T>(other.zone_) {}
template <typename U> template <typename U>
zone_allocator(const zone_allocator<U>& other) throw() : zone_(other.zone_) {} ZoneAllocator(const ZoneAllocator<U>& other) throw()
: ZoneAllocator<T>(other.zone_) {}
template <typename U> template <typename U>
friend class zone_allocator; friend class ZoneAllocator;
pointer address(reference x) const { return &x; } T* address(T& x) const { return &x; }
const_pointer address(const_reference x) const { return &x; } const T* address(const T& x) const { return &x; }
pointer allocate(size_type n, const void* hint = 0) { T* allocate(size_t n, const void* hint = 0) {
return static_cast<pointer>( return static_cast<T*>(zone_->NewArray<T>(static_cast<int>(n)));
zone_->NewArray<value_type>(static_cast<int>(n)));
} }
void deallocate(pointer p, size_type) { /* noop for Zones */ void deallocate(T* p, size_t) { /* noop for Zones */
} }
size_type max_size() const throw() { size_t max_size() const throw() {
return std::numeric_limits<int>::max() / sizeof(value_type); return std::numeric_limits<int>::max() / sizeof(T);
} }
template <typename U, typename... Args> template <typename U, typename... Args>
void construct(U* p, Args&&... args) { void construct(U* p, Args&&... args) {
@ -61,10 +61,10 @@ class zone_allocator {
p->~U(); p->~U();
} }
bool operator==(zone_allocator const& other) const { bool operator==(ZoneAllocator const& other) const {
return zone_ == other.zone_; return zone_ == other.zone_;
} }
bool operator!=(zone_allocator const& other) const { bool operator!=(ZoneAllocator const& other) const {
return zone_ != other.zone_; return zone_ != other.zone_;
} }
@ -74,8 +74,75 @@ class zone_allocator {
Zone* zone_; Zone* zone_;
}; };
typedef zone_allocator<bool> ZoneBoolAllocator; // A recycling zone allocator maintains a free list of deallocated chunks
typedef zone_allocator<int> ZoneIntAllocator; // to reuse on subsiquent allocations. The free list management is purposely
// very simple and works best for data-structures which regularly allocate and
// free blocks of similar sized memory (such as std::deque).
template <typename T>
class RecyclingZoneAllocator : public ZoneAllocator<T> {
public:
template <class O>
struct rebind {
typedef RecyclingZoneAllocator<O> other;
};
#ifdef V8_CC_MSVC
// MSVS unfortunately requires the default constructor to be defined.
RecyclingZoneAllocator()
: ZoneAllocator(nullptr, nullptr), free_list_(nullptr) {
UNREACHABLE();
}
#endif
explicit RecyclingZoneAllocator(Zone* zone) throw()
: ZoneAllocator<T>(zone), free_list_(nullptr) {}
explicit RecyclingZoneAllocator(const RecyclingZoneAllocator& other) throw()
: ZoneAllocator<T>(other), free_list_(nullptr) {}
template <typename U>
RecyclingZoneAllocator(const RecyclingZoneAllocator<U>& other) throw()
: ZoneAllocator<T>(other), free_list_(nullptr) {}
template <typename U>
friend class RecyclingZoneAllocator;
T* allocate(size_t n, const void* hint = 0) {
// Only check top block in free list, since this will be equal to or larger
// than the other blocks in the free list.
if (free_list_ && free_list_->size >= n) {
T* return_val = reinterpret_cast<T*>(free_list_);
free_list_ = free_list_->next;
return return_val;
} else {
return ZoneAllocator<T>::allocate(n, hint);
}
}
void deallocate(T* p, size_t n) {
if ((sizeof(T) * n < sizeof(FreeBlock))) return;
// Only add block to free_list if it is equal or larger than previous block
// so that allocation stays O(1) only having to look at the top block.
if (!free_list_ || free_list_->size <= n) {
// Store the free-list within the block being deallocated.
DCHECK((sizeof(T) * n >= sizeof(FreeBlock)));
FreeBlock* new_free_block = reinterpret_cast<FreeBlock*>(p);
new_free_block->size = n;
new_free_block->next = free_list_;
free_list_ = new_free_block;
}
}
private:
struct FreeBlock {
FreeBlock* next;
size_t size;
};
FreeBlock* free_list_;
};
typedef ZoneAllocator<bool> ZoneBoolAllocator;
typedef ZoneAllocator<int> ZoneIntAllocator;
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8

View File

@ -21,38 +21,38 @@ namespace internal {
// A wrapper subclass for std::vector to make it easy to construct one // A wrapper subclass for std::vector to make it easy to construct one
// that uses a zone allocator. // that uses a zone allocator.
template <typename T> template <typename T>
class ZoneVector : public std::vector<T, zone_allocator<T>> { class ZoneVector : public std::vector<T, ZoneAllocator<T>> {
public: public:
// Constructs an empty vector. // Constructs an empty vector.
explicit ZoneVector(Zone* zone) explicit ZoneVector(Zone* zone)
: std::vector<T, zone_allocator<T>>(zone_allocator<T>(zone)) {} : std::vector<T, ZoneAllocator<T>>(ZoneAllocator<T>(zone)) {}
// Constructs a new vector and fills it with {size} elements, each // Constructs a new vector and fills it with {size} elements, each
// constructed via the default constructor. // constructed via the default constructor.
ZoneVector(size_t size, Zone* zone) ZoneVector(size_t size, Zone* zone)
: std::vector<T, zone_allocator<T>>(size, T(), zone_allocator<T>(zone)) {} : std::vector<T, ZoneAllocator<T>>(size, T(), ZoneAllocator<T>(zone)) {}
// Constructs a new vector and fills it with {size} elements, each // Constructs a new vector and fills it with {size} elements, each
// having the value {def}. // having the value {def}.
ZoneVector(size_t size, T def, Zone* zone) ZoneVector(size_t size, T def, Zone* zone)
: std::vector<T, zone_allocator<T>>(size, def, zone_allocator<T>(zone)) {} : std::vector<T, ZoneAllocator<T>>(size, def, ZoneAllocator<T>(zone)) {}
// Constructs a new vector and fills it with the contents of the range // Constructs a new vector and fills it with the contents of the range
// [first, last). // [first, last).
template <class InputIt> template <class InputIt>
ZoneVector(InputIt first, InputIt last, Zone* zone) ZoneVector(InputIt first, InputIt last, Zone* zone)
: std::vector<T, zone_allocator<T>>(first, last, : std::vector<T, ZoneAllocator<T>>(first, last, ZoneAllocator<T>(zone)) {}
zone_allocator<T>(zone)) {}
}; };
// A wrapper subclass std::deque to make it easy to construct one // A wrapper subclass std::deque to make it easy to construct one
// that uses a zone allocator. // that uses a zone allocator.
template <typename T> template <typename T>
class ZoneDeque : public std::deque<T, zone_allocator<T>> { class ZoneDeque : public std::deque<T, RecyclingZoneAllocator<T>> {
public: public:
// Constructs an empty deque. // Constructs an empty deque.
explicit ZoneDeque(Zone* zone) explicit ZoneDeque(Zone* zone)
: std::deque<T, zone_allocator<T>>(zone_allocator<T>(zone)) {} : std::deque<T, RecyclingZoneAllocator<T>>(
RecyclingZoneAllocator<T>(zone)) {}
}; };
// A wrapper subclass std::list to make it easy to construct one // A wrapper subclass std::list to make it easy to construct one
@ -60,11 +60,11 @@ class ZoneDeque : public std::deque<T, zone_allocator<T>> {
// TODO(mstarzinger): This should be renamed to ZoneList once we got rid of our // TODO(mstarzinger): This should be renamed to ZoneList once we got rid of our
// own home-grown ZoneList that actually is a ZoneVector. // own home-grown ZoneList that actually is a ZoneVector.
template <typename T> template <typename T>
class ZoneLinkedList : public std::list<T, zone_allocator<T>> { class ZoneLinkedList : public std::list<T, ZoneAllocator<T>> {
public: public:
// Constructs an empty list. // Constructs an empty list.
explicit ZoneLinkedList(Zone* zone) explicit ZoneLinkedList(Zone* zone)
: std::list<T, zone_allocator<T>>(zone_allocator<T>(zone)) {} : std::list<T, ZoneAllocator<T>>(ZoneAllocator<T>(zone)) {}
}; };
// A wrapper subclass std::priority_queue to make it easy to construct one // A wrapper subclass std::priority_queue to make it easy to construct one
@ -102,24 +102,24 @@ class ZoneStack : public std::stack<T, ZoneDeque<T>> {
// A wrapper subclass for std::set to make it easy to construct one that uses // A wrapper subclass for std::set to make it easy to construct one that uses
// a zone allocator. // a zone allocator.
template <typename K, typename Compare = std::less<K>> template <typename K, typename Compare = std::less<K>>
class ZoneSet : public std::set<K, Compare, zone_allocator<K>> { class ZoneSet : public std::set<K, Compare, ZoneAllocator<K>> {
public: public:
// Constructs an empty set. // Constructs an empty set.
explicit ZoneSet(Zone* zone) explicit ZoneSet(Zone* zone)
: std::set<K, Compare, zone_allocator<K>>(Compare(), : std::set<K, Compare, ZoneAllocator<K>>(Compare(),
zone_allocator<K>(zone)) {} ZoneAllocator<K>(zone)) {}
}; };
// A wrapper subclass for std::map to make it easy to construct one that uses // A wrapper subclass for std::map to make it easy to construct one that uses
// a zone allocator. // a zone allocator.
template <typename K, typename V, typename Compare = std::less<K>> template <typename K, typename V, typename Compare = std::less<K>>
class ZoneMap class ZoneMap
: public std::map<K, V, Compare, zone_allocator<std::pair<const K, V>>> { : public std::map<K, V, Compare, ZoneAllocator<std::pair<const K, V>>> {
public: public:
// Constructs an empty map. // Constructs an empty map.
explicit ZoneMap(Zone* zone) explicit ZoneMap(Zone* zone)
: std::map<K, V, Compare, zone_allocator<std::pair<const K, V>>>( : std::map<K, V, Compare, ZoneAllocator<std::pair<const K, V>>>(
Compare(), zone_allocator<std::pair<const K, V>>(zone)) {} Compare(), ZoneAllocator<std::pair<const K, V>>(zone)) {}
}; };
// A wrapper subclass for std::multimap to make it easy to construct one that // A wrapper subclass for std::multimap to make it easy to construct one that
@ -127,12 +127,12 @@ class ZoneMap
template <typename K, typename V, typename Compare = std::less<K>> template <typename K, typename V, typename Compare = std::less<K>>
class ZoneMultimap class ZoneMultimap
: public std::multimap<K, V, Compare, : public std::multimap<K, V, Compare,
zone_allocator<std::pair<const K, V>>> { ZoneAllocator<std::pair<const K, V>>> {
public: public:
// Constructs an empty multimap. // Constructs an empty multimap.
explicit ZoneMultimap(Zone* zone) explicit ZoneMultimap(Zone* zone)
: std::multimap<K, V, Compare, zone_allocator<std::pair<const K, V>>>( : std::multimap<K, V, Compare, ZoneAllocator<std::pair<const K, V>>>(
Compare(), zone_allocator<std::pair<const K, V>>(zone)) {} Compare(), ZoneAllocator<std::pair<const K, V>>(zone)) {}
}; };
// Typedefs to shorten commonly used vectors. // Typedefs to shorten commonly used vectors.

View File

@ -149,6 +149,7 @@ v8_executable("unittests") {
"wasm/wasm-module-builder-unittest.cc", "wasm/wasm-module-builder-unittest.cc",
"wasm/wasm-opcodes-unittest.cc", "wasm/wasm-opcodes-unittest.cc",
"zone/segmentpool-unittest.cc", "zone/segmentpool-unittest.cc",
"zone/zone-allocator-unittest.cc",
"zone/zone-chunk-list-unittest.cc", "zone/zone-chunk-list-unittest.cc",
"zone/zone-unittest.cc", "zone/zone-unittest.cc",
] ]

View File

@ -136,6 +136,7 @@
'unicode-unittest.cc', 'unicode-unittest.cc',
'value-serializer-unittest.cc', 'value-serializer-unittest.cc',
'zone/segmentpool-unittest.cc', 'zone/segmentpool-unittest.cc',
'zone/zone-allocator-unittest.cc',
'zone/zone-chunk-list-unittest.cc', 'zone/zone-chunk-list-unittest.cc',
'zone/zone-unittest.cc', 'zone/zone-unittest.cc',
'wasm/asm-types-unittest.cc', 'wasm/asm-types-unittest.cc',

View File

@ -0,0 +1,76 @@
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/zone/zone-allocator.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
namespace internal {
TEST(RecyclingZoneAllocator, ReuseSameSize) {
AccountingAllocator accounting_allocator;
Zone zone(&accounting_allocator, ZONE_NAME);
RecyclingZoneAllocator<int> zone_allocator(&zone);
int* allocated = zone_allocator.allocate(10);
zone_allocator.deallocate(allocated, 10);
CHECK_EQ(zone_allocator.allocate(10), allocated);
}
TEST(RecyclingZoneAllocator, ReuseSmallerSize) {
AccountingAllocator accounting_allocator;
Zone zone(&accounting_allocator, ZONE_NAME);
RecyclingZoneAllocator<int> zone_allocator(&zone);
int* allocated = zone_allocator.allocate(100);
zone_allocator.deallocate(allocated, 100);
CHECK_EQ(zone_allocator.allocate(10), allocated);
}
TEST(RecyclingZoneAllocator, DontReuseTooSmallSize) {
AccountingAllocator accounting_allocator;
Zone zone(&accounting_allocator, ZONE_NAME);
RecyclingZoneAllocator<int> zone_allocator(&zone);
// The sizeof(FreeBlock) will be larger than a single int, so we can't keep
// store the free list in the deallocated block.
int* allocated = zone_allocator.allocate(1);
zone_allocator.deallocate(allocated, 1);
CHECK_NE(zone_allocator.allocate(1), allocated);
}
TEST(RecyclingZoneAllocator, ReuseMultipleSize) {
AccountingAllocator accounting_allocator;
Zone zone(&accounting_allocator, ZONE_NAME);
RecyclingZoneAllocator<int> zone_allocator(&zone);
int* allocated1 = zone_allocator.allocate(10);
int* allocated2 = zone_allocator.allocate(20);
int* allocated3 = zone_allocator.allocate(30);
zone_allocator.deallocate(allocated1, 10);
zone_allocator.deallocate(allocated2, 20);
zone_allocator.deallocate(allocated3, 30);
CHECK_EQ(zone_allocator.allocate(10), allocated3);
CHECK_EQ(zone_allocator.allocate(10), allocated2);
CHECK_EQ(zone_allocator.allocate(10), allocated1);
}
TEST(RecyclingZoneAllocator, DontChainSmallerSizes) {
AccountingAllocator accounting_allocator;
Zone zone(&accounting_allocator, ZONE_NAME);
RecyclingZoneAllocator<int> zone_allocator(&zone);
int* allocated1 = zone_allocator.allocate(10);
int* allocated2 = zone_allocator.allocate(5);
int* allocated3 = zone_allocator.allocate(10);
zone_allocator.deallocate(allocated1, 10);
zone_allocator.deallocate(allocated2, 5);
zone_allocator.deallocate(allocated3, 10);
CHECK_EQ(zone_allocator.allocate(5), allocated3);
CHECK_EQ(zone_allocator.allocate(5), allocated1);
CHECK_NE(zone_allocator.allocate(5), allocated2);
}
} // namespace internal
} // namespace v8