libfoedus-core
FOEDUS Core Library
sequential_composer_impl.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2014-2015, Hewlett-Packard Development Company, LP.
3  * This program is free software; you can redistribute it and/or modify it
4  * under the terms of the GNU General Public License as published by the Free
5  * Software Foundation; either version 2 of the License, or (at your option)
6  * any later version.
7  *
8  * This program is distributed in the hope that it will be useful, but WITHOUT
9  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11  * more details. You should have received a copy of the GNU General Public
12  * License along with this program; if not, write to the Free Software
13  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
14  *
15  * HP designates this particular file as subject to the "Classpath" exception
16  * as provided by HP in the LICENSE.txt file that accompanied this code.
17  */
19 
20 #include <glog/logging.h>
21 
22 #include <ostream>
23 #include <string>
24 #include <vector>
25 
26 #include "foedus/engine.hpp"
40 
41 namespace foedus {
42 namespace storage {
43 namespace sequential {
44 
46  : engine_(parent->get_engine()), storage_id_(parent->get_storage_id()) {
47 }
48 
52 struct StreamStatus {
54  stream_ = stream;
56  buffer_ = stream->get_buffer();
57  buffer_size_ = stream->get_buffer_size();
62  ended_ = false;
63  read_entry();
65  ended_ = true;
66  } else if (cur_relative_pos_ + cur_length_ > buffer_size_) {
67  LOG(INFO) << "interesting, super unlucky case. wind immediately";
69  read_entry();
70  ASSERT_ND(cur_relative_pos_ + cur_length_ <= buffer_size_);
71  }
72  return kErrorCodeOk;
73  }
75  ASSERT_ND(!ended_);
80  ended_ = true;
81  return kErrorCodeOk;
82  } else if (UNLIKELY(cur_relative_pos_ >= buffer_size_)) {
84  }
85  read_entry();
88  read_entry();
89  }
90 
92  return kErrorCodeOk;
93  }
97  return kErrorCodeOk;
98  }
99  void read_entry() {
100  const SequentialAppendLogType* entry = get_entry();
102  ASSERT_ND(entry->header_.log_length_ > 0);
104  cur_payload_ = entry->payload_;
105  cur_owner_id_ = entry->header_.xct_id_;
106  }
108  return reinterpret_cast<const SequentialAppendLogType*>(buffer_ + cur_relative_pos_);
109  }
110 
112  const char* buffer_;
113  uint64_t buffer_size_;
117  uint32_t cur_length_;
118  const void* cur_payload_;
120  bool ended_;
121 };
122 
123 SequentialPage* SequentialComposer::compose_new_head(snapshot::SnapshotWriter* snapshot_writer) {
124  SnapshotPagePointer head_page_id = snapshot_writer->get_next_page_id();
125  SequentialPage* page = reinterpret_cast<SequentialPage*>(snapshot_writer->get_page_base());
126  page->initialize_snapshot_page(storage_id_, head_page_id);
127  return page;
128 }
129 
130 ErrorStack SequentialComposer::dump_pages(
131  snapshot::SnapshotWriter* snapshot_writer,
132  bool last_dump,
133  uint32_t allocated_pages,
134  uint64_t* total_pages) {
135  SequentialPage* base = reinterpret_cast<SequentialPage*>(snapshot_writer->get_page_base());
136  ASSERT_ND(snapshot_writer->get_next_page_id() == base->header().page_id_);
137 
138  SequentialPage* tail_page = base + allocated_pages - 1;
139  SnapshotPagePointer next_head_page_id = tail_page->header().page_id_ + 1ULL;
140  ASSERT_ND(tail_page->next_page().snapshot_pointer_ == 0);
141  if (!last_dump) {
142  // a bit of trick here. Usually, we can't point to a page that is not written yet,
143  // but in this case we are sure that we are writing out a page contiguously,
144  // so we can set the next-page pointer at this point.
145  tail_page->next_page().snapshot_pointer_ = next_head_page_id;
146  }
147 
148  WRAP_ERROR_CODE(snapshot_writer->dump_pages(0, allocated_pages));
149  *total_pages += allocated_pages;
150  ASSERT_ND(snapshot_writer->get_next_page_id() == next_head_page_id);
151  return kRetOk;
152 }
153 
155  debugging::StopWatch stop_watch;
156 
157  // Everytime it's full, we write out all pages. much simpler than other storage types.
158  // No intermediate pages to track any information.
159  snapshot::SnapshotWriter* snapshot_writer = args.snapshot_writer_;
160  SequentialPage* base = reinterpret_cast<SequentialPage*>(snapshot_writer->get_page_base());
161 
162  SequentialPage* cur_page = compose_new_head(snapshot_writer);
163  SnapshotPagePointer head_page_id = cur_page->header().page_id_;
164  uint32_t allocated_pages = 1;
165  uint64_t total_pages = 0;
166  const uint32_t max_pages = snapshot_writer->get_page_size();
167  VLOG(0) << to_string() << " composing with " << args.log_streams_count_ << " streams.";
168  Epoch min_epoch;
169  Epoch max_epoch;
170  for (uint32_t i = 0; i < args.log_streams_count_; ++i) {
172  WRAP_ERROR_CODE(status.init(args.log_streams_[i]));
173  while (!status.ended_) {
174  const SequentialAppendLogType* entry = status.get_entry();
175  Epoch epoch = entry->header_.xct_id_.get_epoch();
176  min_epoch.store_min(epoch);
177  max_epoch.store_max(epoch);
178 
179  // need to allocate a new page?
180  if (!cur_page->can_insert_record(entry->payload_count_)) {
181  // need to flush the buffer?
182  if (allocated_pages >= max_pages) {
183  // dump everything and allocate a new head page
184  CHECK_ERROR(dump_pages(snapshot_writer, false, allocated_pages, &total_pages));
185  cur_page = compose_new_head(snapshot_writer);
186  allocated_pages = 1;
187  } else {
188  // sequential storage is a bit special. As every page is written-once, we need only
189  // snapshot pointer. No dual page pointers.
190  SequentialPage* next_page = base + allocated_pages;
191  ++allocated_pages;
192  next_page->initialize_snapshot_page(storage_id_, cur_page->header().page_id_ + 1ULL);
193  cur_page->next_page().snapshot_pointer_ = next_page->header().page_id_;
194  cur_page = next_page;
196  == snapshot_writer->get_numa_node());
198  == snapshot_writer->get_snapshot_id());
199  }
200  }
201 
202  ASSERT_ND(cur_page->can_insert_record(entry->payload_count_));
203  cur_page->append_record_nosync(
204  status.cur_owner_id_,
205  entry->payload_count_,
206  status.cur_payload_);
207 
208  // then, read next
209  WRAP_ERROR_CODE(status.next());
210  }
211  }
212  // dump everything
213  CHECK_ERROR(dump_pages(snapshot_writer, true, allocated_pages, &total_pages));
214 
215  // this compose() emits just one pointer to the head page.
216  RootInfoPage* root_info_page_casted = reinterpret_cast<RootInfoPage*>(args.root_info_page_);
217  root_info_page_casted->header_.storage_id_ = storage_id_;
218  root_info_page_casted->pointer_.page_id_ = head_page_id;
219  root_info_page_casted->pointer_.from_epoch_ = min_epoch;
220  root_info_page_casted->pointer_.to_epoch_ = max_epoch.one_more(); // to make it exclusive
221  root_info_page_casted->pointer_.page_count_ = total_pages;
222 
223  stop_watch.stop();
224  LOG(INFO) << to_string() << " compose() done in " << stop_watch.elapsed_ms() << "ms. # pages="
225  << total_pages << ", head_page=" << assorted::Hex(head_page_id, 16)
226  << ", min_epoch=" << min_epoch << ", max_epoch=" << max_epoch;
227  return kRetOk;
228 }
229 
231  debugging::StopWatch stop_watch;
232 
233  snapshot::SnapshotWriter* snapshot_writer = args.snapshot_writer_;
234  std::vector<HeadPagePointer> all_head_pages;
235  SequentialStorage storage(engine_, storage_id_);
236  SnapshotPagePointer previous_root_page_pointer = storage.get_metadata()->root_snapshot_page_id_;
237  for (SnapshotPagePointer page_id = previous_root_page_pointer; page_id != 0;) {
238  // if there already is a root page, read them all.
239  // we have to anyway re-write all of them, at least the next pointer.
240  SequentialRootPage* root_page = reinterpret_cast<SequentialRootPage*>(
242  WRAP_ERROR_CODE(args.previous_snapshot_files_->read_page(page_id, root_page));
243  ASSERT_ND(root_page->header().storage_id_ == storage_id_);
244  ASSERT_ND(root_page->header().page_id_ == page_id);
245  for (uint16_t i = 0; i < root_page->get_pointer_count(); ++i) {
246  all_head_pages.push_back(root_page->get_pointers()[i]);
247  }
248  page_id = root_page->get_next_page();
249  }
250 
251  // each root_info_page contains just one pointer to the head page.
252  for (uint32_t i = 0; i < args.root_info_pages_count_; ++i) {
253  const RootInfoPage* info_page = reinterpret_cast<const RootInfoPage*>(args.root_info_pages_[i]);
254  ASSERT_ND(info_page->pointer_.page_id_ > 0);
255  all_head_pages.push_back(info_page->pointer_);
256  }
257  VLOG(0) << to_string() << " construct_root() total head page pointers=" << all_head_pages.size();
258 
259  // now simply write out root pages that contain these pointers.
260  SequentialRootPage* base = reinterpret_cast<SequentialRootPage*>(
261  snapshot_writer->get_page_base());
262  SequentialRootPage* cur_page = base;
263  uint32_t allocated_pages = 1;
264  SnapshotPagePointer root_of_root_page_id = snapshot_writer->get_next_page_id();
265  cur_page->initialize_snapshot_page(storage_id_, root_of_root_page_id);
266  for (uint32_t written_pointers = 0; written_pointers < all_head_pages.size();) {
267  uint16_t count_in_this_page = std::min<uint64_t>(
268  all_head_pages.size() - written_pointers,
270  cur_page->set_pointers(&all_head_pages[written_pointers], count_in_this_page);
271  written_pointers += count_in_this_page;
272  if (written_pointers < all_head_pages.size()) {
273  // we need next page in root page.
274  SequentialRootPage* new_page = cur_page + 1;
275  new_page->initialize_snapshot_page(storage_id_, cur_page->header().page_id_ + 1);
277  == snapshot_writer->get_numa_node());
279  == snapshot_writer->get_snapshot_id());
280  cur_page->set_next_page(new_page->header().page_id_);
281  cur_page = new_page;
282  ++allocated_pages;
283  } else {
284  ASSERT_ND(written_pointers == all_head_pages.size());
285  }
286  }
287 
288  // write out the new root pages
289  WRAP_ERROR_CODE(snapshot_writer->dump_pages(0, allocated_pages));
290  ASSERT_ND(snapshot_writer->get_next_page_id() == root_of_root_page_id + allocated_pages);
291  *args.new_root_page_pointer_ = root_of_root_page_id;
292 
293  // In sequential, there is only one snapshot pointer to install, the root page.
294  storage.get_control_block()->root_page_pointer_.snapshot_pointer_ = root_of_root_page_id;
295  storage.get_control_block()->meta_.root_snapshot_page_id_ = root_of_root_page_id;
296 
297  stop_watch.stop();
298  VLOG(0) << to_string() << " construct_root() done in " << stop_watch.elapsed_us() << "us."
299  << " total head page pointers=" << all_head_pages.size()
300  << ". new root head page=" << assorted::Hex(*args.new_root_page_pointer_)
301  << ". root_page_count=" << allocated_pages;
302  return kRetOk;
303 }
304 
305 std::string SequentialComposer::to_string() const {
306  return std::string("SequentialComposer-") + std::to_string(storage_id_);
307 }
308 
310  const Composer::DropVolatilesArguments& args) {
311  // In sequential, no need to determine what volatile pages to keep.
312  SequentialStorage storage(engine_, storage_id_);
313  SequentialStoragePimpl pimpl(engine_, storage.get_control_block());
314  uint16_t nodes = engine_->get_options().thread_.group_count_;
315  uint16_t threads_per_node = engine_->get_options().thread_.thread_count_per_group_;
316  for (uint16_t node = 0; node < nodes; ++node) {
317  if (args.partitioned_drop_ && args.my_partition_ != node) {
318  continue;
319  }
320  const memory::LocalPageResolver& resolver
322  for (uint16_t local_ordinal = 0; local_ordinal < threads_per_node; ++local_ordinal) {
323  thread::ThreadId thread_id = thread::compose_thread_id(node, local_ordinal);
324  memory::PagePoolOffset* head_ptr = pimpl.get_head_pointer(thread_id);
325  memory::PagePoolOffset* tail_ptr = pimpl.get_tail_pointer(thread_id);
326  memory::PagePoolOffset tail_offset = *tail_ptr;
327  if ((*head_ptr) == 0) {
328  ASSERT_ND(tail_offset == 0);
329  VLOG(0) << "No volatile pages for thread-" << thread_id << " in sequential-" << storage_id_;
330  continue;
331  }
332 
333  ASSERT_ND(tail_offset != 0);
334  while (true) {
335  memory::PagePoolOffset offset = *head_ptr;
336  ASSERT_ND(offset != 0);
337 
338  // if the page is newer than the snapshot, keep them.
339  // all volatile pages/records are appended in epoch order, so no need to check further.
340  SequentialPage* head = reinterpret_cast<SequentialPage*>(resolver.resolve_offset(offset));
341  ASSERT_ND(head->get_record_count() > 0);
342  if (head->get_record_count() > 0
344  VLOG(0) << "Thread-" << thread_id << " in sequential-" << storage_id_ << " keeps volatile"
345  << " pages at and after epoch-" << head->get_first_record_epoch();
346  break;
347  }
348 
349  // okay, drop this
351  ASSERT_ND(next != offset);
352  ASSERT_ND(next == 0 || head->next_page().volatile_pointer_.get_numa_node() == node);
353  args.drop(engine_, combine_volatile_page_pointer(node, offset));
354  if (next == 0) {
355  // it was the tail
356  ASSERT_ND(tail_offset == offset);
357  VLOG(0) << "Thread-" << thread_id << " in sequential-" << storage_id_ << " dropped all"
358  << " volatile pages";
359  *head_ptr = 0;
360  *tail_ptr = 0;
361  break;
362  } else {
363  // move head
364  *head_ptr = next;
365  DVLOG(2) << "Thread-" << thread_id << " in sequential-" << storage_id_ << " dropped a"
366  << " page.";
367  }
368  }
369  }
370  }
371  return Composer::DropResult(args); // always everything dropped
372 }
373 
374 } // namespace sequential
375 } // namespace storage
376 } // namespace foedus
const Page *const * root_info_pages_
Root info pages output by compose()
Definition: composer.hpp:130
memory::AlignedMemory work_memory_
Working memory to be used in gleaner's construct_root().
snapshot::LogGleanerResource * gleaner_resource_
All pre-allocated resouces to help run construct_root(), such as memory buffers.
Definition: composer.hpp:134
ErrorCode read_page(storage::SnapshotPagePointer page_id, void *out)
Lock-free list of records stored in the volatile part of sequential storage.
Represents a logic to compose a new version of data pages for one storage.
Definition: composer.hpp:86
const SequentialAppendLogType * get_entry() const
Root package of FOEDUS (Fast Optimistic Engine for Data Unification Services).
Definition: assert_nd.hpp:44
bool can_insert_record(uint16_t payload_length) const
Returns if this page has enough room to insert a record with the given payload length.
uint32_t PagePoolOffset
Offset in PagePool that compactly represents the page address (unlike 8 bytes pointer).
Definition: memory_id.hpp:44
SnapshotPagePointer page_id_
ID of the page that begins the linked list.
Epoch get_first_record_epoch() const
Returns the epoch of the fist record in this page (undefined behavior if no record).
const char * get_buffer() const
Returns the buffer memory.
Definition: log_buffer.hpp:108
StorageId storage_id_
ID of the storage this page belongs to.
Definition: page.hpp:196
double elapsed_ms() const
Definition: stop_watch.hpp:48
Epoch valid_until_epoch_
This snapshot contains all the logs until this epoch.
Definition: snapshot.hpp:55
Composer::DropResult drop_volatiles(const Composer::DropVolatilesArguments &args)
virtual ErrorCode wind(uint64_t next_absolute_pos)=0
Loads next data block to be consumed by the caller (composer).
Persistent status part of Transaction ID.
Definition: xct_id.hpp:955
memory::PagePoolOffset get_page_size() const __attribute__((always_inline))
Brings error stacktrace information as return value of functions.
Definition: error_stack.hpp:81
void append_record_nosync(xct::XctId owner_id, uint16_t payload_length, const void *payload)
Appends a record to this page.
Represents a time epoch.
Definition: epoch.hpp:61
Epoch from_epoch_
Inclusive beginning of epochs in the pointed pages.
FileStatus status(const Path &p)
Returns the status of the file.
Definition: filesystem.cpp:45
Represents one input stream of sorted log entries.
Definition: log_buffer.hpp:81
const Metadata * get_metadata() const
Returns the metadata of this storage.
Definition: storage.hpp:162
Represents one data page in Sequential Storage.
const uint16_t kRootPageMaxHeadPointers
Maximum number of head pointers in one root page.
uint32_t log_streams_count_
Number of sorted runs.
Definition: composer.hpp:103
VolatilePagePointer combine_volatile_page_pointer(uint8_t numa_node, memory::PagePoolOffset offset)
Definition: storage_id.hpp:235
const EngineOptions & get_options() const
Definition: engine.cpp:39
ThreadLocalOrdinal thread_count_per_group_
Number of Thread in each ThreadGroup.
uint64_t get_offset() const
Returns the absolute byte position of the buffer's beginning in the entire file.
Definition: log_buffer.hpp:120
Represents an append/scan-only store.
uint64_t get_cur_block_abosulte_begin() const
Current storage block's beginning in absolute byte position in the file.
Definition: log_buffer.hpp:129
VolatilePagePointer volatile_pointer_
Definition: storage_id.hpp:308
0 means no-error.
Definition: error_code.hpp:87
memory::PagePoolOffset get_offset() const
Definition: storage_id.hpp:202
Output of one compose() call, which are then combined in construct_root().
uint64_t SnapshotPagePointer
Page ID of a snapshot page.
Definition: storage_id.hpp:79
Page * root_info_page_
[OUT] Returns pointers and related information that is required to construct the root page...
Definition: composer.hpp:116
uint64_t stop()
Take another current time tick.
Definition: stop_watch.cpp:35
NumaNodeMemoryRef * get_node_memory(foedus::thread::ThreadGroupId group) const
SnapshotPagePointer snapshot_pointer_
Definition: storage_id.hpp:307
ErrorStack compose(const Composer::ComposeArguments &args)
Epoch get_epoch() const __attribute__((always_inline))
Definition: xct_id.hpp:964
0x0026 : foedus::storage::sequential::SequentialAppendLogType .
Definition: log_type.hpp:119
xct::XctId xct_id_
Epoch and in-epoch ordinal of this log.
uint16_t extract_snapshot_id_from_snapshot_pointer(SnapshotPagePointer pointer)
Definition: storage_id.hpp:98
Epoch one_more() const
Definition: epoch.hpp:127
storage::Page * get_page_base() __attribute__((always_inline))
Retrun value of drop_volatiles()
Definition: composer.hpp:171
SnapshotPagePointer * new_root_page_pointer_
[OUT] Returns pointer to new root snapshot page/
Definition: composer.hpp:136
uint64_t to_relative_pos(uint64_t absolute_pos) const
Definition: log_buffer.hpp:96
uint16_t get_pointer_count() const
Returns How many pointers to head pages exist in this page.
void * get_block() const
Returns the memory block.
uint16_t log_length_
Byte size of this log entry including this header itself and everything.
uint8_t extract_numa_node_from_snapshot_pointer(SnapshotPagePointer pointer)
Definition: storage_id.hpp:95
uint16_t group_count_
Number of ThreadGroup in the engine.
Represents one stable root page in Sequential Storage.
storage::Page * resolve_offset(PagePoolOffset offset) const __attribute__((always_inline))
Resolves offset in this pool to storage::Page*.
uint16_t my_partition_
if partitioned_drop_ is true, the partition this thread should drop volatile pages from ...
Definition: composer.hpp:152
cache::SnapshotFileSet * previous_snapshot_files_
To read existing snapshots.
Definition: composer.hpp:128
ErrorStack construct_root(const Composer::ConstructRootArguments &args)
uint64_t page_count_
In a sequential storage, all pages from the head page is guaranteed to be contiguous (that's how Sequ...
ThreadId compose_thread_id(ThreadGroupId node, ThreadLocalOrdinal local_core)
Returns a globally unique ID of Thread (core) for the given node and ordinal in the node...
Definition: thread_id.hpp:123
void store_max(const Epoch &other)
Kind of std::max(this, other).
Definition: epoch.hpp:165
double elapsed_us() const
Definition: stop_watch.hpp:45
void set_pointers(HeadPagePointer *pointers, uint16_t pointer_count)
#define CHECK_ERROR_CODE(x)
This macro calls x and checks its returned error code.
Definition: error_code.hpp:155
thread::ThreadOptions thread_
Declares all log types used in this storage type.
snapshot::SnapshotWriter * snapshot_writer_
Writes out composed pages.
Definition: composer.hpp:126
#define CHECK_ERROR(x)
This macro calls x and checks its returned value.
void initialize_snapshot_page(StorageId storage_id, SnapshotPagePointer page_id)
snapshot::SnapshotWriter * snapshot_writer_
Writes out composed pages.
Definition: composer.hpp:97
uint16_t ThreadId
Typedef for a global ID of Thread (core), which is unique across NUMA nodes.
Definition: thread_id.hpp:80
uint16_t get_record_count() const
Returns how many records in this page placed so far.
uint64_t get_cur_block_abosulte_end() const
Current storage block's end in absolute byte position in the file.
Definition: log_buffer.hpp:131
const ErrorStack kRetOk
Normal return value for no-error case.
Resolves an offset in local (same NUMA node) page pool to a pointer and vice versa.
bool partitioned_drop_
if true, one thread for each partition will invoke drop_volatiles()
Definition: composer.hpp:154
Convenient way of writing hex integers to stream.
LogCode get_type() const __attribute__((always_inline))
Convenience method to cast into LogCode.
ErrorCode init(snapshot::SortedBuffer *stream)
Epoch to_epoch_
Exclusive end of epochs in the pointed pages.
Log type of sequential-storage's append operation.
const LocalPageResolver & get_resolver() const
Gives an object to resolve an offset in this page pool (thus local) to an actual pointer and vice ver...
Definition: page_pool.cpp:146
uint32_t root_info_pages_count_
Number of root info pages.
Definition: composer.hpp:132
#define UNLIKELY(x)
Hints that x is highly likely false.
Definition: compiler.hpp:104
#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
A high-resolution stop watch.
Definition: stop_watch.hpp:30
#define WRAP_ERROR_CODE(x)
Same as CHECK_ERROR(x) except it receives only an error code, thus more efficient.
ErrorCode dump_pages(memory::PagePoolOffset from_page, uint32_t count)
Write out pages that are contiguous in the main page pool.
void initialize_snapshot_page(StorageId storage_id, SnapshotPagePointer page_id)
Called only when this page is initialized.
snapshot::SortedBuffer *const * log_streams_
Sorted runs.
Definition: composer.hpp:101
CONTROL_BLOCK * get_control_block() const
Definition: attachable.hpp:97
snapshot::Snapshot snapshot_
The new snapshot.
Definition: composer.hpp:150
void drop(Engine *engine, VolatilePagePointer pointer) const
Returns (might cache) the given pointer to volatile pool.
Definition: composer.cpp:114
storage::SnapshotPagePointer get_next_page_id() const
memory::EngineMemory * get_memory_manager() const
See Memory Manager.
Definition: engine.cpp:50
ErrorCode
Enum of error codes defined in error_code.xmacro.
Definition: error_code.hpp:85
Unlike other composers, this one doesn't need merge sort.
uint64_t page_id_
Page ID of this page.
Definition: page.hpp:191
Writes out one snapshot file for all data pages in one reducer.
uint64_t get_buffer_size() const
Returns the size of buffer memory.
Definition: log_buffer.hpp:111
SnapshotPagePointer root_snapshot_page_id_
Pointer to a snapshotted page this storage is rooted at.
Definition: metadata.hpp:112
void store_min(const Epoch &other)
Kind of std::min(this, other).
Definition: epoch.hpp:153