]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
common: tiny_vector – CPU-friendly container for mutexes & co.
authorRadoslaw Zarzynski <rzarzyns@redhat.com>
Tue, 30 Jan 2018 00:01:00 +0000 (01:01 +0100)
committerRadoslaw Zarzynski <rzarzyns@redhat.com>
Mon, 15 Jul 2019 14:53:45 +0000 (10:53 -0400)
Signed-off-by: Radoslaw Zarzynski <rzarzyns@redhat.com>
src/common/containers.h [new file with mode: 0644]

diff --git a/src/common/containers.h b/src/common/containers.h
new file mode 100644 (file)
index 0000000..a52f43f
--- /dev/null
@@ -0,0 +1,180 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- 
+// vim: ts=8 sw=2 smarttab
+//
+// Ceph - scalable distributed file system
+//
+// Copyright (C) 2018 Red Hat, Inc.
+//
+// This is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License version 2.1, as published by the Free Software
+// Foundation.  See file COPYING.
+//
+
+#ifndef CEPH_COMMON_CONTAINERS_H
+#define CEPH_COMMON_CONTAINERS_H
+
+#include <cstdint>
+#include <type_traits>
+
+namespace ceph::containers {
+
+// tiny_vector – CPU friendly, small_vector-like container for mutexes,
+// atomics and other non-movable things.
+//
+// The purpose of the container is to store arbitrary number of objects
+// with absolutely minimal requirements regarding constructibility
+// and assignability while minimizing memory indirection.
+// There is no obligation for MoveConstructibility, CopyConstructibility,
+// MoveAssignability, CopyAssignability nor even DefaultConstructibility
+// which allows to handle std::mutexes, std::atomics or any type embedding
+// them.
+//
+// Few requirements translate into tiny interface. The container isn't
+// Copy- nor MoveConstructible. Although it does offer random access
+// iterator, insertion in the middle is not allowed. The maximum number
+// of elements must be known at run-time. This shouldn't be an issue in
+// the intended use case: sharding.
+//
+// For the special case of no internal slots (InternalCapacity eq 0),
+// tiny_vector doesn't require moving any elements (changing pointers
+// is enough), and thus should be MoveConstructibile.
+//
+// Alternatives:
+//  1. std::vector<boost::optional<ValueT>> initialized with the known
+//     size and emplace_backed(). boost::optional inside provides
+//     the DefaultConstructibility. Imposes extra memory indirection.
+//  2. boost::container::small_vector + boost::optional always
+//     requires MoveConstructibility.
+//  3. boost::container::static_vector feed via emplace_back().
+//     Good for performance but enforces upper limit on elements count.
+//     For sharding this means we can't handle arbitrary number of
+//     shards (weird configs).
+//  4. std::unique_ptr<ValueT>: extra indirection together with memory
+//     fragmentation.
+
+template<typename Value, std::size_t InternalCapacity>
+class tiny_vector {
+  // NOTE: to avoid false sharing consider aligning to cache line
+  using storage_unit_t = \
+    std::aligned_storage_t<sizeof(Value), alignof(Value)>;
+
+  std::size_t _size = 0;
+  storage_unit_t* const data = nullptr;
+  storage_unit_t internal[InternalCapacity];
+
+public:
+  typedef std::size_t size_type;
+  typedef std::add_lvalue_reference_t<Value> reference;
+  typedef std::add_const_t<reference> const_reference;
+  typedef std::add_pointer_t<Value> pointer;
+
+  // emplacer is the piece of weirdness that comes from handling
+  // unmovable-and-uncopyable things. The only way to instantiate
+  // such types I know is to create instances in-place perfectly
+  // forwarding necessary data to constructor.
+  // Abstracting that is the exact purpose of emplacer.
+  //
+  // The usage scenario is:
+  //   1. The tiny_vector's ctor is provided with a) maximum number
+  //      of instances and b) a callable taking emplacer.
+  //   2. The callable can (but isn't obliged to!) use emplacer to
+  //      construct an instance without knowing at which address
+  //      in memory it will be put. Callable is also supplied with
+  //      an unique integer from the range <0, maximum number of
+  //      instances).
+  //   3. If callable decides to instantiate, it calls ::emplace
+  //      of emplacer passing all arguments required by the type
+  //      hold in tiny_vector.
+  //
+  // Example:
+  // ```
+  //   static constexpr const num_internally_allocated_slots = 32;
+  //   tiny_vector<T, num_internally_allocated_slots> mytinyvec {
+  //     num_of_instances,
+  //     [](const size_t i, auto emplacer) {
+  //       emplacer.emplace(argument_for_T_ctor);
+  //     }
+  //   }
+  // ```
+
+  class emplacer {
+    friend class tiny_vector;
+
+    tiny_vector* parent;
+    emplacer(tiny_vector* const parent)
+      : parent(parent) {
+    }
+
+  public:
+    template<class... Args>
+    void emplace(Args&&... args) {
+      if (!parent) {
+        return;
+      }
+      new (&parent->data[parent->_size++]) Value(std::forward<Args>(args)...);
+      parent = nullptr;
+    }
+  };
+
+  template<typename F>
+  tiny_vector(const std::size_t count, F&& f)
+    : data(count <= InternalCapacity ? internal
+                                     : new storage_unit_t[count]) {
+    for (std::size_t i = 0; i < count; ++i) {
+      // caller MAY emplace up to `count` elements but it IS NOT
+      // obliged to do so. The emplacer guarantees that the limit
+      // will never be exceeded.
+      f(i, emplacer(this));
+    }
+  }
+
+  ~tiny_vector() {
+    for (auto& elem : *this) {
+      elem.~Value();
+    }
+
+    const auto data_addr = reinterpret_cast<std::uintptr_t>(data);
+    const auto this_addr = reinterpret_cast<std::uintptr_t>(this);
+    if (data_addr < this_addr ||
+        data_addr >= this_addr + sizeof(*this)) {
+      delete[] data;
+    }
+  }
+
+  reference       operator[](size_type pos) {
+    return reinterpret_cast<reference>(data[pos]);
+  }
+  const_reference operator[](size_type pos) const {
+    return reinterpret_cast<const_reference>(data[pos]);
+  }
+
+  size_type size() const {
+    return _size;
+  }
+
+  pointer begin() {
+    return reinterpret_cast<pointer>(&data[0]);
+  }
+  pointer end() {
+    return reinterpret_cast<pointer>(&data[_size]);
+  }
+
+  const pointer begin() const {
+    return reinterpret_cast<pointer>(&data[0]);
+  }
+  const pointer end() const {
+    return reinterpret_cast<pointer>(&data[_size]);
+  }
+
+  const pointer cbegin() const {
+    return reinterpret_cast<pointer>(&data[0]);
+  }
+  const pointer cend() const {
+    return reinterpret_cast<pointer>(&data[_size]);
+  }
+};
+
+} // namespace ceph::containers
+
+#endif // CEPH_COMMON_CONTAINERS_H