More work on moving engine into dlmalloc heap

This commit is contained in:
2022-02-25 19:57:23 -05:00
parent 08f6aa2092
commit ff932dba10
52 changed files with 351 additions and 484 deletions

View File

@@ -1,9 +1,53 @@
//
// Two Mallocs:
//
// The purpose of this file is to put all engine data structures into
// a private heap that isn't used by any other library. This helps
// achieve determinism when playing a replay log.
//
// For the engine's heap, we chose Doug Lea's Malloc (dlmalloc). It's
// a good general-purpose single-threaded malloc. It's probably not
// the fastest any more (it was, once), but it's still quite good. It
// may be possible to replace dlmalloc with another malloc, but
// dlmalloc is pretty easy to work with.
//
// In order to get all engine data structures into the dlmalloc heap,
// you need to jump through quite a few hoops:
//
// * When using STL classes, you need to use the 'eng' variant of those
// classes:
//
// use 'eng::string' instead of std::string (include "wrap-string.hpp")
// use 'eng::vector' instead of std::vector (include "wrap-vector.hpp")
// use 'eng::map' instead of std::map (include "wrap-map.hpp")
// etc.
//
// These eng classes allocate memory from dlmalloc instead of malloc.
//
// * All your classes must derive from eng::heap. This adds a custom
// operator new and operator delete to your class.
//
// * Use eng::make_shared and eng::make_unique instead of std::make_shared
// and std::make_unique.
//
// * Simple classes like std::pair, std::string_view, std::less,
// std::hash, and so forth don't allocate memory. Those classes
// are not wrapped. There is no eng::pair, eng:less, etc.
//
// * Be aware that most C++ streams use the malloc heap, and there's no
// way to change that. Fortunately, eng::ostringstream uses the
// dlmalloc heap.
//
// * Failing to jump through all these hoops won't break your code in
// any obvious way - you'll just have some of your data structures in
// the malloc heap instead of the dlmalloc heap. This can break
// determinism of replay.
//
#ifndef TWO_MALLOCS_HPP
#define TWO_MALLOCS_HPP
#include <cstddef>
#include <memory>
// dlmalloc is only used on linux.
extern "C" {
@@ -21,15 +65,35 @@ void* dlrealloc(void *p, size_t x) { return realloc(p,x); }
// Return the current state of the dlmalloc allocator as a 30-bit hash.
extern int dlmalloc_hash();
// EngAllocator: a class meant to be used as an STL Allocator.
// An allocator for lua states that uses dlmalloc and dlfree.
inline void *lalloc_dlmalloc (void *ud, void *ptr, size_t osize, size_t nsize) {
if (nsize == 0) {
dlfree(ptr);
return NULL;
} else {
return dlrealloc(ptr, nsize);
}
}
// An allocator for lua states that uses malloc and free.
inline void *lalloc_malloc (void *ud, void *ptr, size_t osize, size_t nsize) {
if (nsize == 0) {
free(ptr);
return NULL;
} else {
return realloc(ptr, nsize);
}
}
// eng:allocator: a class meant to be used as an STL Allocator.
// Causes objects to be allocated using dlmalloc and dlfree.
template <class T>
class EngAllocator
class dlmalloc_allocator
{
public:
using value_type = T;
EngAllocator() noexcept {}
template <class U> EngAllocator(EngAllocator<U> const&) noexcept {}
dlmalloc_allocator() noexcept {}
template <class U> dlmalloc_allocator(dlmalloc_allocator<U> const&) noexcept {}
value_type* allocate(std::size_t n)
{
@@ -43,49 +107,51 @@ public:
};
template <class T, class U>
bool operator==(EngAllocator<T> const&, EngAllocator<U> const&) noexcept
bool operator==(const dlmalloc_allocator<T> &, const dlmalloc_allocator<U> &) noexcept
{
return true;
}
template <class T, class U>
bool operator!=(EngAllocator<T> const&, EngAllocator<U> const&) noexcept
bool operator!=(const dlmalloc_allocator<T> &, const dlmalloc_allocator<U> &) noexcept
{
return false;
}
// DrvAllocator: a class meant to be used as an STL Allocator.
// Causes objects to be allocated using malloc and free.
template <class T>
class DrvAllocator
{
public:
using value_type = T;
DrvAllocator() noexcept {}
template <class U> DrvAllocator(DrvAllocator<U> const&) noexcept {}
value_type* allocate(std::size_t n)
{
return static_cast<value_type*>(dlmalloc(n*sizeof(value_type)));
}
void deallocate(value_type* p, std::size_t) noexcept
{
dlfree(p);
}
};
template <class T, class U>
bool operator==(DrvAllocator<T> const&, DrvAllocator<U> const&) noexcept
{
return true;
// Another name for dlmalloc_allocator
namespace eng {
template <class T>
using allocator = dlmalloc_allocator<T>;
}
template <class T, class U>
bool operator!=(DrvAllocator<T> const&, DrvAllocator<U> const&) noexcept
{
return false;
}
// eng::heap a class meant to be used as a base class.
// Causes 'new' and 'delete' for this class to use dlmalloc and dlfree.
namespace eng {
class heap {
public:
void *operator new(size_t size)
{
return dlmalloc(size);
}
void operator delete(void *p, size_t size)
{
return dlfree(p);
}
};
} // namespace
// eng::make_shared allocates shared objects in the dlmalloc heap.
namespace eng {
template<class T, class... Args>
inline std::shared_ptr<T> make_shared(Args&&... args) {
static eng::allocator<T> alloc;
return std::allocate_shared<T>(alloc, args...);
}
template<class T, class... Args>
inline std::unique_ptr<T> make_unique(Args&&... args) {
return std::make_unique<T>(args...);
}
} // namespace eng
#endif // TWO_MALLOCS_HPP