libfoedus-core
FOEDUS Core Library
foedus::memory::AlignedMemory Class Referencefinal

Represents one memory block aligned to actual OS/hardware pages. More...

Detailed Description

Represents one memory block aligned to actual OS/hardware pages.

This class is used to allocate and hold memory blocks for objects that must be aligned.

Moveable/Copiable
This object is moveable if C++11 is enabled. This object is NOT copiable, either via constructor or assignment operator because it causes an issue on ownership of the memory. Use move semantics to pass this object around.

Why aligned memory

There are a few cases where objects must be on aligned memory.

Transparent Hugepage

We do not explicitly use hugepages (2MB/1GB OS page sizes) in our program, but we do allocate memories in a way Linux is strongly advised to use transparent hugepages (THP). posix_memalign() or numa_alloc_interleaved() with big allocation size is a clear hint for Linux to use THP. You don't need mmap or madvise with this strong hint.

After allocating the memory, we zero-clear the memory for two reasons.

  • to avoid bugs caused by unintialized data access (i.e. make valgrind happy)
  • to immediately finalize memory allocation, which (with big allocation size) strongly advises Linux to use THP.

To check if THP is actually used, check /proc/meminfo before/after the engine start-up. AnonHugePages tells it. We at least confirmed that THP is used in Fedora 19/20. For more details, see the section in README.markdown.

See also
https://lwn.net/Articles/423584/
Todo:
Support Windows' VirtualAlloc() and VirtualFree().

Definition at line 67 of file aligned_memory.hpp.

#include <aligned_memory.hpp>

Public Types

enum  AllocType { kPosixMemalign = 0, kNumaAllocInterleaved, kNumaAllocOnnode, kNumaMmapOneGbPages }
 Type of new/delete operation for the block. More...
 

Public Member Functions

 AlignedMemory () noexcept
 Empty constructor which allocates nothing. More...
 
 AlignedMemory (uint64_t size, uint64_t alignment, AllocType alloc_type, int numa_node) noexcept
 Allocate an aligned memory of given size and alignment. More...
 
 AlignedMemory (const AlignedMemory &other)=delete
 
AlignedMemoryoperator= (const AlignedMemory &other)=delete
 
 AlignedMemory (AlignedMemory &&other) noexcept
 Move constructor that steals the memory block from other. More...
 
AlignedMemoryoperator= (AlignedMemory &&other) noexcept
 Move assignment operator that steals the memory block from other. More...
 
 ~AlignedMemory ()
 Automatically releases the memory. More...
 
void alloc (uint64_t size, uint64_t alignment, AllocType alloc_type, int numa_node) noexcept
 Allocate a memory, releasing the current memory if exists. More...
 
void alloc_onnode (uint64_t size, uint64_t alignment, int numa_node) noexcept
 Short for alloc(kNumaAllocOnnode) More...
 
ErrorCode assure_capacity (uint64_t required_size, double expand_margin=2.0, bool retain_content=false) noexcept
 If the current size is smaller than the given size, automatically expands. More...
 
void * get_block () const
 Returns the memory block. More...
 
bool is_null () const
 Returns if this object doesn't hold a valid memory block. More...
 
uint64_t get_size () const
 Returns the byte size of the memory block. More...
 
uint64_t get_alignment () const
 Returns the alignment of the memory block. More...
 
AllocType get_alloc_type () const
 Returns type of new/delete operation for the block. More...
 
int get_numa_node () const
 If alloc_type_ is kNumaAllocOnnode, returns the NUMA node this memory was allocated at. More...
 
void release_block ()
 Releases the memory block. More...
 

Friends

std::ostream & operator<< (std::ostream &o, const AlignedMemory &v)
 

Member Enumeration Documentation

Type of new/delete operation for the block.

So far we allow posix_memalign and numa_alloc. numa_alloc implicitly aligns the allocated memory, but we can't specify alignment size. Usually it's 4096 bytes aligned, thus always enough for our usage.

See also
http://stackoverflow.com/questions/8154162/numa-aware-cache-aligned-memory-allocation
Enumerator
kPosixMemalign 

posix_memalign() and free().

kNumaAllocInterleaved 

numa_alloc_interleaved() and numa_free().

Implicit 4096 bytes alignment.

kNumaAllocOnnode 

numa_alloc_onnode() and numa_free().

Implicit 4096 bytes alignment.

kNumaMmapOneGbPages 

Usual new()/delete().

We currently don't use this for aligned memory allocation, but may be the best for portability. But, this is not a strong hint for Linux to use THP. hmm.Windows's VirtualAlloc() and VirtualFree(). This option is for using non-transparent 1G hugepages on NUMA node, which requires a special configuration (OS reboot required), so use this with care. Basically, this invokes numa_set_preferred() on the target node and then mmap() with MAP_HUGETLB and MAP_HUGE_1GB. See the readme about 1G hugepage setup. Unfortunately we can't do it without rebooting the linux.. crap!

Definition at line 77 of file aligned_memory.hpp.

77  {
79  kPosixMemalign = 0,
89  // kNormal,
91  // kVirtualAlloc,
101  };
numa_alloc_onnode() and numa_free().
numa_alloc_interleaved() and numa_free().

Constructor & Destructor Documentation

foedus::memory::AlignedMemory::AlignedMemory ( )
inlinenoexcept

Empty constructor which allocates nothing.

Definition at line 104 of file aligned_memory.hpp.

104  : size_(0), alignment_(0), alloc_type_(kPosixMemalign),
105  numa_node_(0), block_(CXX11_NULLPTR) {}
#define CXX11_NULLPTR
Used in public headers in place of "nullptr" of C++11.
Definition: cxx11.hpp:132
foedus::memory::AlignedMemory::AlignedMemory ( uint64_t  size,
uint64_t  alignment,
AllocType  alloc_type,
int  numa_node 
)
noexcept

Allocate an aligned memory of given size and alignment.

Parameters
[in]sizeByte size of the memory block. Actual allocation is at least of this size.
[in]alignmentAlignment bytes of the memory block. Must be power of two. Ignored for kNumaAllocOnnode and kNumaAllocInterleaved.
[in]alloc_typespecifies type of new/delete
[in]numa_nodeif alloc_type_ is kNumaAllocOnnode, the NUMA node to allocate at. Otherwise ignored.
Attention
When memory allocation fails for some reason (eg Out-Of-Memory), this constructor does NOT fail nor throws an exception. Instead, it sets the block_ NULL. So, the caller is responsible for checking it after construction.

Definition at line 52 of file aligned_memory.cpp.

54  : size_(0), alignment_(0), alloc_type_(kPosixMemalign), numa_node_(0),
55  block_(nullptr) {
56  alloc(size, alignment, alloc_type, numa_node);
57 }
void alloc(uint64_t size, uint64_t alignment, AllocType alloc_type, int numa_node) noexcept
Allocate a memory, releasing the current memory if exists.
foedus::memory::AlignedMemory::AlignedMemory ( const AlignedMemory other)
delete
foedus::memory::AlignedMemory::AlignedMemory ( AlignedMemory &&  other)
noexcept

Move constructor that steals the memory block from other.

Definition at line 222 of file aligned_memory.cpp.

222  : block_(nullptr) {
223  *this = std::move(other);
224 }
foedus::memory::AlignedMemory::~AlignedMemory ( )
inline

Automatically releases the memory.

Definition at line 138 of file aligned_memory.hpp.

References release_block().

138 { release_block(); }
void release_block()
Releases the memory block.

Here is the call graph for this function:

Member Function Documentation

void foedus::memory::AlignedMemory::alloc ( uint64_t  size,
uint64_t  alignment,
AllocType  alloc_type,
int  numa_node 
)
noexcept

Allocate a memory, releasing the current memory if exists.

Definition at line 113 of file aligned_memory.cpp.

References foedus::memory::alloc_mmap(), foedus::memory::alloc_mmap_1gb_pages(), ASSERT_ND, foedus::debugging::StopWatch::elapsed_ns(), foedus::assorted::mod_numa_node(), numa_available(), numa_preferred(), numa_set_preferred(), foedus::assorted::os_error(), and foedus::debugging::StopWatch::stop().

Referenced by alloc_onnode(), foedus::snapshot::LogGleanerResource::allocate(), foedus::memory::NumaNodeMemory::allocate_numa_memory_general(), foedus::cache::CacheHashtable::CacheHashtable(), foedus::storage::StorageManagerPimpl::clone_all_storage_metadata(), foedus::storage::hash::HashTmpBin::create_memory(), foedus::storage::masstree::MasstreeStoragePimpl::debugout_single_thread_follow(), foedus::snapshot::SnapshotManagerPimpl::drop_volatile_pages(), foedus::snapshot::SnapshotManagerPimpl::drop_volatile_pages_parallel(), foedus::storage::hash::HashComposeContext::HashComposeContext(), foedus::storage::hash::TmpSnashotPage::init(), foedus::cache::CacheManagerPimpl::initialize_once(), foedus::memory::NumaNodeMemory::initialize_once(), foedus::snapshot::LogMapper::initialize_once(), foedus::snapshot::LogReducer::initialize_once(), foedus::snapshot::SnapshotMetadata::load(), foedus::restart::RestartManagerPimpl::redo_meta_logs(), and foedus::externalize::Externalizable::save_to_file().

117  {
118  release_block();
119  ASSERT_ND(block_ == nullptr);
120  size_ = size;
121  alignment_ = alignment;
122  alloc_type_ = alloc_type;
123  numa_node_ = numa_node;
124  ASSERT_ND((alignment & (alignment - 1)) == 0); // alignment is power of two
125  if (alloc_type_ == kNumaMmapOneGbPages) {
126  alignment = 1ULL << 30;
127  }
128  if (size_ == 0 || size_ % alignment != 0) {
129  size_ = ((size_ / alignment) + 1) * alignment;
130  }
131 
132  // Use libnuma's numa_set_preferred to initialize the NUMA node of the memory.
133  // We can later do the equivalent with mbind IF the memory is not shared.
134  // mbind does nothing for shared memory. So, this is the only way
135  int original_node = 0;
136  if (::numa_available() >= 0) {
137  original_node = ::numa_preferred();
139  }
140 
141  debugging::StopWatch watch;
142  int posix_memalign_ret;
143  switch (alloc_type_) {
144  case kPosixMemalign:
145  // https://bugzilla.mozilla.org/show_bug.cgi?id=606270
146  // kind of pathetic, but to make sure.
147  posix_memalign_ret = ::posix_memalign(&block_, alignment, size_);
148  if (posix_memalign_ret != 0) {
149  block_ = nullptr;
150  }
151  break;
152  case kNumaAllocInterleaved: // actually we no longer support this.. no reason to use this.
153  case kNumaAllocOnnode:
154  block_ = alloc_mmap(size_, alignment);
155  break;
156  case kNumaMmapOneGbPages:
157  block_ = alloc_mmap_1gb_pages(size_);
158  break;
159  default:
160  ASSERT_ND(false);
161  }
162  watch.stop();
163 
164  if (block_ == nullptr) {
165  LOG(ERROR) << "Aligned memory allocation failed. OS error=" << assorted::os_error() << *this;
166  // also reset the numa_preferred
167  if (::numa_available() >= 0) {
168  ::numa_set_preferred(original_node);
169  }
170  return;
171  }
172 
173  debugging::StopWatch watch2;
174  std::memset(block_, 0, size_); // see class comment for why we do this immediately
175  watch2.stop();
176  if (::numa_available() >= 0) {
177  ::numa_set_preferred(original_node);
178  }
179  LOG(INFO) << "Allocated memory in " << watch.elapsed_ns() << "+"
180  << watch2.elapsed_ns() << " ns (alloc+memset)." << *this;
181 }
numa_alloc_onnode() and numa_free().
void release_block()
Releases the memory block.
void * alloc_mmap_1gb_pages(uint64_t size)
int numa_available(void)
int numa_preferred(void)
std::string os_error()
Thread-safe strerror(errno).
int mod_numa_node(int numa_node)
In order to run even on a non-numa machine or a machine with fewer sockets, we allow specifying arbit...
char * alloc_mmap(uint64_t size, uint64_t alignment)
void numa_set_preferred(int node)
#define ASSERT_ND(x)
A warning-free wrapper macro of assert() that has no performance effect in release mode even when 'x'...
Definition: assert_nd.hpp:72
numa_alloc_interleaved() and numa_free().

Here is the call graph for this function:

Here is the caller graph for this function:

void foedus::memory::AlignedMemory::alloc_onnode ( uint64_t  size,
uint64_t  alignment,
int  numa_node 
)
inlinenoexcept

Short for alloc(kNumaAllocOnnode)

Definition at line 147 of file aligned_memory.hpp.

References alloc(), and kNumaAllocOnnode.

Referenced by foedus::xct::McsMockNode< RW_BLOCK >::init(), and foedus::snapshot::LogGleanerResource::PerNodeResource::PerNodeResource().

147  {
148  alloc(size, alignment, kNumaAllocOnnode, numa_node);
149  }
numa_alloc_onnode() and numa_free().
void alloc(uint64_t size, uint64_t alignment, AllocType alloc_type, int numa_node) noexcept
Allocate a memory, releasing the current memory if exists.

Here is the call graph for this function:

Here is the caller graph for this function:

ErrorCode foedus::memory::AlignedMemory::assure_capacity ( uint64_t  required_size,
double  expand_margin = 2.0,
bool  retain_content = false 
)
noexcept

If the current size is smaller than the given size, automatically expands.

This is useful for temporary work buffer.

Parameters
[in]required_sizeresulting memory will have at least this size
[in]expand_marginwhen expanded, the new size is multiplied with this number to avoid too frequent expansion
[in]retain_contentif specified, copies the current content to the new memory
Precondition
!is_null(), so you have to first alloc(). Because otherwise we don't know have to alloc.
Attention
When expanded, the memory address changes.
Returns
only possible error is out-of-memory

Definition at line 182 of file aligned_memory.cpp.

References ASSERT_ND, is_null(), foedus::kErrorCodeInvalidParameter, foedus::kErrorCodeOk, foedus::kErrorCodeOutofmemory, and release_block().

Referenced by foedus::storage::hash::ComposedBinsBuffer::assure_read_buffer_size(), foedus::storage::masstree::MasstreePartitioner::design_partition(), foedus::snapshot::SnapshotWriter::expand_intermediate_memory(), foedus::snapshot::SnapshotWriter::expand_pool_memory(), foedus::snapshot::MergeSort::initialize_once(), foedus::storage::hash::HashPartitioner::sort_batch(), and foedus::storage::array::ArrayPartitioner::sort_batch().

185  {
186  if (is_null()) {
187  LOG(FATAL) << "Misuse of assure_capacity. Can't extend a null buffer";
189  }
190  if (size_ >= required_size) {
191  return kErrorCodeOk;
192  }
193  if (expand_margin < 1) {
194  expand_margin = 1;
195  }
196  uint64_t expanded = required_size * expand_margin;
197  VLOG(0) << "Expanding work memory from " << size_ << " to " << expanded;
198 
199  // save the current memory
200  AlignedMemory old(std::move(*this));
201  ASSERT_ND(!old.is_null());
202  ASSERT_ND(is_null());
203 
204  alloc(expanded, alignment_, alloc_type_, numa_node_);
205  if (is_null()) {
206  LOG(ERROR) << "Out of memory error while expanding work memory from "
207  << size_ << " to " << expanded;
208  *this = std::move(old); // recover the old one
209  return kErrorCodeOutofmemory;
210  }
211 
212  // copies the old content if specified
213  if (retain_content) {
214  ASSERT_ND(size_ >= old.size_);
215  std::memcpy(block_, old.block_, old.size_);
216  }
217 
218  old.release_block();
219  return kErrorCodeOk;
220 }
0x0001 : "GENERAL: Out of memory" .
Definition: error_code.hpp:105
0x0002 : "GENERAL: Invalid parameter given" .
Definition: error_code.hpp:106
void alloc(uint64_t size, uint64_t alignment, AllocType alloc_type, int numa_node) noexcept
Allocate a memory, releasing the current memory if exists.
0 means no-error.
Definition: error_code.hpp:87
#define ASSERT_ND(x)
A warning-free wrapper macro of assert() that has no performance effect in release mode even when 'x'...
Definition: assert_nd.hpp:72
AlignedMemory() noexcept
Empty constructor which allocates nothing.
bool is_null() const
Returns if this object doesn't hold a valid memory block.

Here is the call graph for this function:

Here is the caller graph for this function:

uint64_t foedus::memory::AlignedMemory::get_alignment ( ) const
inline

Returns the alignment of the memory block.

Definition at line 174 of file aligned_memory.hpp.

Referenced by foedus::log::Logger::initialize_once(), foedus::memory::operator<<(), foedus::fs::DirectIoFile::read(), and foedus::fs::DirectIoFile::write().

174 { return alignment_; }

Here is the caller graph for this function:

AllocType foedus::memory::AlignedMemory::get_alloc_type ( ) const
inline

Returns type of new/delete operation for the block.

Definition at line 176 of file aligned_memory.hpp.

Referenced by foedus::memory::operator<<().

176 { return alloc_type_; }

Here is the caller graph for this function:

void* foedus::memory::AlignedMemory::get_block ( ) const
inline

Returns the memory block.

Definition at line 168 of file aligned_memory.hpp.

Referenced by foedus::cache::CacheHashtable::CacheHashtable(), foedus::storage::StorageManagerPimpl::clone_all_storage_metadata(), foedus::storage::hash::HashComposer::construct_root(), foedus::storage::sequential::SequentialComposer::construct_root(), foedus::storage::masstree::MasstreeStoragePimpl::debugout_single_thread_follow(), foedus::storage::masstree::MasstreePartitioner::design_partition(), foedus::snapshot::SnapshotManagerPimpl::drop_volatile_pages(), foedus::snapshot::SnapshotManagerPimpl::drop_volatile_pages_parallel(), foedus::assorted::UniformRandom::fill_memory(), foedus::memory::AlignedMemorySlice::get_block(), foedus::snapshot::SnapshotWriter::get_intermediate_base(), foedus::memory::NumaCoreMemory::get_local_work_memory(), foedus::snapshot::SnapshotWriter::get_page_base(), foedus::storage::hash::HashComposeContext::HashComposeContext(), foedus::storage::hash::TmpSnashotPage::init(), foedus::xct::McsMockNode< RW_BLOCK >::init(), foedus::storage::hash::ComposedBinsMergedStream::init(), foedus::cache::CacheManagerPimpl::initialize_once(), foedus::memory::NumaNodeMemory::initialize_once(), foedus::memory::NumaCoreMemory::initialize_once(), foedus::snapshot::MergeSort::initialize_once(), foedus::snapshot::SnapshotMetadata::load(), foedus::snapshot::SnapshotWriter::open(), foedus::memory::operator<<(), foedus::restart::RestartManagerPimpl::redo_meta_logs(), foedus::storage::hash::HashPartitioner::sort_batch(), and foedus::storage::array::ArrayPartitioner::sort_batch().

168 { return block_; }

Here is the caller graph for this function:

int foedus::memory::AlignedMemory::get_numa_node ( ) const
inline

If alloc_type_ is kNumaAllocOnnode, returns the NUMA node this memory was allocated at.

Definition at line 178 of file aligned_memory.hpp.

Referenced by foedus::memory::operator<<().

178 { return numa_node_; }

Here is the caller graph for this function:

bool foedus::memory::AlignedMemory::is_null ( ) const
inline
AlignedMemory& foedus::memory::AlignedMemory::operator= ( const AlignedMemory other)
delete
AlignedMemory & foedus::memory::AlignedMemory::operator= ( AlignedMemory &&  other)
noexcept

Move assignment operator that steals the memory block from other.

Definition at line 225 of file aligned_memory.cpp.

References release_block().

225  {
226  release_block();
227  size_ = other.size_;
228  alignment_ = other.alignment_;
229  alloc_type_ = other.alloc_type_;
230  block_ = other.block_;
231  other.block_ = nullptr;
232  return *this;
233 }
void release_block()
Releases the memory block.

Here is the call graph for this function:

void foedus::memory::AlignedMemory::release_block ( )

Releases the memory block.

Definition at line 235 of file aligned_memory.cpp.

References ASSERT_ND, kNumaAllocInterleaved, kNumaAllocOnnode, kNumaMmapOneGbPages, and kPosixMemalign.

Referenced by assure_capacity(), foedus::snapshot::SnapshotMetadata::clear(), foedus::snapshot::LogGleanerResource::deallocate(), foedus::snapshot::SnapshotManagerPimpl::drop_volatile_pages(), foedus::snapshot::SnapshotManagerPimpl::drop_volatile_pages_parallel(), operator=(), foedus::storage::hash::HashTmpBin::release_memory(), foedus::cache::CacheManagerPimpl::uninitialize_once(), foedus::memory::NumaNodeMemory::uninitialize_once(), foedus::memory::NumaCoreMemory::uninitialize_once(), foedus::snapshot::LogMapper::uninitialize_once(), foedus::log::Logger::uninitialize_once(), foedus::snapshot::LogReducer::uninitialize_once(), and ~AlignedMemory().

235  {
236  if (block_ != nullptr) {
237  switch (alloc_type_) {
238  case kPosixMemalign:
239  ::free(block_);
240  break;
242  case kNumaAllocOnnode:
243  case kNumaMmapOneGbPages:
244  ::munmap(block_, size_);
245  break;
246  default:
247  ASSERT_ND(false);
248  }
249  block_ = nullptr;
250  }
251 }
numa_alloc_onnode() and numa_free().
#define ASSERT_ND(x)
A warning-free wrapper macro of assert() that has no performance effect in release mode even when 'x'...
Definition: assert_nd.hpp:72
numa_alloc_interleaved() and numa_free().

Here is the caller graph for this function:

Friends And Related Function Documentation

std::ostream& operator<< ( std::ostream &  o,
const AlignedMemory v 
)
friend

Definition at line 253 of file aligned_memory.cpp.

253  {
254  o << "<AlignedMemory>";
255  o << "<is_null>" << v.is_null() << "</is_null>";
256  o << "<size>" << v.get_size() << "</size>";
257  o << "<alignment>" << v.get_alignment() << "</alignment>";
258  o << "<alloc_type>" << v.get_alloc_type() << " (";
259  switch (v.get_alloc_type()) {
261  o << "kPosixMemalign";
262  break;
264  o << "kNumaAllocInterleaved";
265  break;
267  o << "kNumaAllocOnnode";
268  break;
270  o << "kNumaMmapOneGbPages";
271  break;
272  default:
273  o << "Unknown";
274  }
275  o << ")</alloc_type>";
276  o << "<numa_node>" << static_cast<int>(v.get_numa_node()) << "</numa_node>";
277  o << "<address>" << v.get_block() << "</address>";
278  o << "</AlignedMemory>";
279  return o;
280 }
numa_alloc_onnode() and numa_free().
numa_alloc_interleaved() and numa_free().

The documentation for this class was generated from the following files: