Skip to content

Commit

Permalink
Finish Release-0.2
Browse files Browse the repository at this point in the history
  • Loading branch information
foonathan committed May 9, 2015
2 parents e317677 + e540faa commit ea0203d
Show file tree
Hide file tree
Showing 39 changed files with 2,081 additions and 479 deletions.
16 changes: 16 additions & 0 deletions CHANGELOG.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
0.2
---
* added temporary_allocator as portable alloca
* added small_node_pool type optimized for low-overhead small object allocations
* added various allocator adapters including a thread_safe_allocator for locking
* better compiler support
* many internal changes and bugfixes

0.1-1
-----
* critical bugfix in memory_stack
* added smart pointer example

0.1
---
* first beta version
71 changes: 65 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,49 @@ else()
project(foonathan_memory)
endif()

include(CheckCXXSourceCompiles)
include(CheckCXXCompilerFlag)
CHECK_CXX_COMPILER_FLAG(-std=c++11 cpp11_flag)
if (cpp11_flag)
set(CMAKE_REQUIRED_FLAGS "-std=c++11")
else()
CHECK_CXX_COMPILER_FLAG(-std=c++0x cpp0x_flag)
if (cpp0x_flag)
set(CMAKE_REQUIRED_FLAGS "-std=c++0x")
endif(cpp0x_flag)
endif(cpp11_flag)

CHECK_CXX_SOURCE_COMPILES("int main() {int i = alignof(int);}" comp_alignof)
CHECK_CXX_SOURCE_COMPILES("#include <cstddef>
using namespace std;
int main() {max_align_t val;}" comp_max_align)
CHECK_CXX_SOURCE_COMPILES("#include <new>
int main() {auto handler = std::get_new_handler();}" comp_new_handler)
CHECK_CXX_SOURCE_COMPILES("thread_local int i; int main() {}" comp_thread_local)
CHECK_CXX_SOURCE_COMPILES("constexpr auto foo = 1; int main(){}" comp_constexpr)
CHECK_CXX_SOURCE_COMPILES("void foo() noexcept {} int main(){}" comp_noexcept)

option(FOONATHAN_IMPL_HAS_ALIGNOF "whether or not alignof is available" ${comp_alignof})
option(FOONATHAN_IMPL_HAS_MAX_ALIGN "whether or not std::max_align_t is available" ${comp_max_align})
option(FOONATHAN_IMPL_HAS_GET_NEW_HANDLER "whether or not std::get_new_handler() is available" ${comp_new_handler})
option(FOONATHAN_IMPL_HAS_THREAD_LOCAL "whether or not thread_local is available" ${comp_thread_local})
option(FOONATHAN_IMPL_HAS_CONSTEXPR "whether or not constexpr is available" ${comp_constexpr})
option(FOONATHAN_IMPL_HAS_NOEXCEPT "whether or not noexcept is available" ${comp_noexcept})

set(version_major 0 CACHE INTERNAL "")
set(version_minor 2 CACHE INTERNAL "")

set(FOONATHAN_MEMORY_DEFAULT_ALLOCATOR heap_allocator CACHE STRING
"the default implementation allocator for higher-level ones")
option(FOONATHAN_MEMORY_THREAD_SAFE_ADAPTER "whether or not raw_allocator_adapter is thread safe by default" ON)

configure_file("${CMAKE_CURRENT_SOURCE_DIR}/config.hpp.in"
"${CMAKE_CURRENT_BINARY_DIR}/config_impl.hpp")

if (BIICODE)
ADD_BIICODE_TARGETS()
set(targets ${BII_BLOCK_TARGETS} CACHE INTERNAL "")

ACTIVATE_CPP11()
target_include_directories(${BII_BLOCK_TARGET} INTERFACE ${CMAKE_CURRENT_BINARY_DIR})
else()
set(src
detail/align.hpp
Expand All @@ -19,8 +57,13 @@ else()
detail/free_list.cpp
detail/free_list.hpp
detail/memory_stack.hpp
detail/small_free_list.cpp
detail/small_free_list.hpp
aligned_allocator.hpp
allocator_adapter.hpp
allocator_traits.hpp
config.hpp
default_allocator.hpp
heap_allocator.cpp
heap_allocator.hpp
new_allocator.cpp
Expand All @@ -29,19 +72,35 @@ else()
pool_collection.cpp
pool_collection.hpp
pool_type.hpp
pool_type.cpp
raw_allocator_base.hpp
smart_ptr.hpp
stack_allocator.hpp
std_allocator_base.hpp
temporary_allocator.cpp
temporary_allocator.hpp
threading.hpp
tracking.hpp
CACHE INTERNAL "")

add_library(foonathan_memory ${src})
add_executable(foonathan_memory_example_allocator ${src} example/allocator.cpp)
add_executable(foonathan_memory_example_smart_ptr ${src} example/smart_ptr.cpp)
add_executable(foonathan_memory_example_allocator example/allocator.cpp)
add_executable(foonathan_memory_example_smart_ptr example/smart_ptr.cpp)
add_executable(foonathan_memory_example_temporary example/temporary.cpp)

set(targets foonathan_memory foonathan_memory_example_allocator foonathan_memory_example_smart_ptr CACHE INTERNAL "")
target_link_libraries(foonathan_memory_example_allocator PUBLIC foonathan_memory)
target_link_libraries(foonathan_memory_example_smart_ptr PUBLIC foonathan_memory)
target_link_libraries(foonathan_memory_example_temporary PUBLIC foonathan_memory)

set(targets foonathan_memory
foonathan_memory_example_allocator
foonathan_memory_example_smart_ptr
foonathan_memory_example_temporary
CACHE INTERNAL "")

set_target_properties(${targets} PROPERTIES CXX_STANDARD 11)
set_target_properties(${targets} PROPERTIES CXX_STANDARD_REQUIRED ON)

foreach(target ${targets})
target_include_directories(${target} PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
endforeach()
endif()
44 changes: 42 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
memory
======
This library provides various memory allocators for high-performance allocation and deallocation. These allocators are provided in the form of a new allocator concept: RawAllocator. A RawAllocator is an improved version over the classical STL-Allocator. There are various wrapper classes and traits to convert between the two types. Each RawAllocator has the following interface or an appropriate specialization of the raw_allocator_traits:
The C++ STL allocator model has various flaws. For example, they are fixed to a certain type, because they are almost necessarily required to be templates. So you can't easily share a single allocator for multiple types. In addition, you can only get a copy from the containers and not the original allocator object. At least with C++11 they are allowed to be stateful and so can be made object not instance based. But still, the model has many flaws.
Over the course of the years many solutions have been proposed. for example EASTL[1]. This library is another. But instead of trying to change the STL, it works with the current implementation.

RawAllocator
------------
This library provides a new allocator concept, a *RawAllocator*. Where the classic *Allocator* works with types and similar to new/delete, a *RawAllocator* works with raw memory (hence the name) and is similar to ::operator new/::operator delete or malloc/free. This allows it to be decoupled from concrete types and stops the need for templates (still, almost all allocators in this library are templates to allow maximum flexibility, but they are not required).
Another difference is the seperation between node and array allocations. A node is a single object, like an element of std::list. An array is a collection of such nodes. This is useful for memory pools, where there need to be a different approach depending on whether it is an array or node allocation.
In addition, the *RawAllocator* support alignment requirement. The memory can be aligned to certain boundaries. This allows fine tuned allocation and efficiency. The required interface for *RawAllocator* is as follows:

// A raw allocator, only supports raw memory allocation.
// Similar to ::operator new/malloc. This avoids the need to be templated and to use one allocator for multiple types.
Expand All @@ -10,6 +17,7 @@ This library provides various memory allocators for high-performance allocation
public:
// Whether or not the allocator is stateful.
// Non-stateful allocators don't need to be stored and can be default constructed on the fly.
// Thus, it is probably an empty type.
using is_stateful = std::true_type/std::false_type;
// The allocator is required to be moveable
Expand All @@ -27,9 +35,11 @@ This library provides various memory allocators for high-performance allocation
void* allocate_array(std::size_t count, std::size_t size, std::size_t alignment);
// Deallocates memory for a node. Must not throw.
// Precondition: node must come from allocate_node with the same parameters and must not be null.
void deallocate_node(void *node, std::size_t size, std::size_t alignment) noexcept;
// Deallocates memory for an array of nodes. Must not throw.
// Precondition: array must come from allocate_array with the same paramters and must not be null.
void deallocate_array(void *array, std::size_t count, std::size_t size, std::size_t alignment) noexcept;
// Returns the maximum size of a node, inclusive. Should not throw.
Expand All @@ -40,4 +50,34 @@ This library provides various memory allocators for high-performance allocation
// Returns the maximum supported alignment, inclusive. Should not throw.
std::size_t max_alignment() const;
};
};
Of course, there is a traits class for classes that do not support this interface directly - like STL-Allocators!
There are currently the following classes that model *RawAllocator* or have specialized the traits in this library:
* heap_allocator - Allocates memory using malloc/free
* new_allocator - Allocates memory using ::operator new/::operator delete
* memory_stack - Allocates huge blocks of memory, then can be used in a stack manner, deallocation only via unwinding
* memory_pool - Allocates huge blocks of memory and seperates them into nodes of given size, great if you have multiple objects of the same size
* memory_pool_collection - Maintains multiple memory_pools at once to allow different sized allocation.

The last three are special allocators. They allocate a big block of memory and give it out one by one. If the block is exhausted, a new is one allocated. The block allocation can be controlled via a template parameter, they use a given implementation *RawAllocator* for it (default is heap_allocator).

Adapters
--------
A new allocator model just by itself would be useless, because it can't be used with the existing model. For this case, there are adapters. The engine is the allocator_reference. This class stores a pointer to a *RawAllocator*. It allows copying of allocator classes as it is required by the STL containers. raw_allocator_allocator is a normal *Allocator* that stores one such allocator_reference. It forwards all allocation requests to it and thus to a user defined *RawAllocator*. Since the get_allocator() function returns a copy of the *Allocator* but allocator_reference stores a pointer, you can still access the original used allocator from a container. The new propagate_on_XXX members in raw_allocator_allocator have all been set to std::true_type. This ensure that an allocator always stays with its memory and allows fast moving. The raw_allocator_allocator thus allows that *RawAllocator* classes can be used with STL containers.

The new smart pointer classes don't use *Allocator* classes, they use *Deleter*. But there are also adapters for those and new raw_allocate_unique/shared function to easily create smart pointers whose memory is managed by *RawAllocator*.

There are also tracking adapters. A *Tracker* provides functions that are called on certain events, such as memory allocation or allocator growth (when they allocate new blocks from the implementation allocator). tracked_allocator takes a *RawAllocator* and a *Tracker* and combines them. This allows easily monitoring of memory usage. Due to the power of templates, tracked_allocator works with all classes modelling the concept of *RawAllocator* including user defined ones.

Other adapters include a thread safe wrapper, that locks a mutex prior to accessing, or another ensuring a certain minimum alignment.

Compiler Support
----------------
This library has been successfully compiled under the following compilers:
* GCC 4.7-4.9 on Linux
* clang 3.4-3.5 on Linux
* Visual Studio 12 on Windows

There are compatibility options and replacement macros for alignof, thread_local, constexpr and noexcept and workarounds for missing std::max_align_t and std::get_new_handler().

[1] EASTL - http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2271.html
124 changes: 124 additions & 0 deletions aligned_allocator.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Copyright (C) 2015 Jonathan Müller <[email protected]>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.

#ifndef FOONATHAN_MEMORY_ALIGNED_ALLOCATOR_HPP_INCLUDED
#define FOONATHAN_MEMORY_ALIGNED_ALLOCATOR_HPP_INCLUDED

/// \file
/// \brief An allocator ensuring a certain alignment

#include <algorithm>
#include <cassert>

#include "allocator_traits.hpp"

namespace foonathan { namespace memory
{
/// \brief A \c RawAllocator adapter that ensures a minimum alignment.
/// \details It changes the alignment requirements passed to the allocation function if necessary
/// and forwards to the wrapped allocator.
/// \ingroup memory
template <class RawAllocator>
class aligned_allocator : RawAllocator
{
using traits = allocator_traits<RawAllocator>;
public:
using raw_allocator = RawAllocator;
using is_stateful = std::true_type;

/// \brief Creates it passing it the minimum alignment requirement.
/// \details It must be less than the maximum supported alignment.
explicit aligned_allocator(std::size_t min_alignment, raw_allocator &&alloc = {})
: raw_allocator(std::move(alloc)), min_alignment_(min_alignment)
{
assert(min_alignment_ <= max_alignment());
}

/// @{
/// \brief (De-)Allocation functions ensure the given minimum alignemnt.
/// \details If the alignment requirement is higher, it is unchanged.
void* allocate_node(std::size_t size, std::size_t alignment)
{
alignment = std::max(min_alignment_, alignment);
return traits::allocate_node(get_allocator(), size, alignment);
}

void* allocate_array(std::size_t count, std::size_t size, std::size_t alignment)
{
alignment = std::min(min_alignment_, alignment);
return traits::allocate_array(get_allocator(), count, size, alignment);
}

void deallocate_node(void *ptr, std::size_t size, std::size_t alignment) FOONATHAN_NOEXCEPT
{
alignment = std::max(min_alignment_, alignment);
traits::deallocate_node(get_allocator(), ptr, size, alignment);
}

void deallocate_array(void *ptr, std::size_t count,
std::size_t size, std::size_t alignment) FOONATHAN_NOEXCEPT
{
alignment = std::max(min_alignment_, alignment);
traits::deallocate_array(get_allocator(), ptr, count, size, alignment);
}
/// @}

std::size_t max_node_size() const
{
return traits::max_node_size(get_allocator());
}

std::size_t max_array_size() const
{
return traits::max_array_size(get_allocator());
}

std::size_t max_alignment() const
{
return traits::max_alignment(get_allocator());
}

/// @{
/// \brief Returns a reference to the actual allocator.
raw_allocator& get_allocator() FOONATHAN_NOEXCEPT
{
return *this;
}

const raw_allocator& get_allocator() const FOONATHAN_NOEXCEPT
{
return *this;
}
/// @}

/// @{
/// \brief Get/set the minimum alignment.
std::size_t min_alignment() const FOONATHAN_NOEXCEPT
{
return min_alignment_;
}

void set_min_alignment(std::size_t min_alignment)
{
assert(min_alignment <= max_alignment());
min_alignment_ = min_alignment;
}
/// @}

private:
std::size_t min_alignment_;
};

/// \brief Creates an \ref aligned_allocator.
/// \relates aligned_allocator
template <class RawAllocator>
auto make_aligned_allocator(std::size_t min_alignment, RawAllocator &&allocator) FOONATHAN_NOEXCEPT
-> aligned_allocator<typename std::decay<RawAllocator>::type>
{
return aligned_allocator<typename std::decay<RawAllocator>::type>
{min_alignment, std::forward<RawAllocator>(allocator)};
}
}} // namespace foonathan::memory

#endif // FOONATHAN_MEMORY_ALIGNED_ALLOCATOR_HPP_INCLUDED
Loading

0 comments on commit ea0203d

Please sign in to comment.