Bug 1102525 (part 1) - Add SegmentedVector to MFBT.
authorNicholas Nethercote <nnethercote@mozilla.com>
Thu, 20 Nov 2014 20:00:47 -0800
changeset 303227 1532b4d6e9cf0776367e31f046d063b9a153e696
parent 303121 893013d8d71480a18ee3a475ceaff46a1937477b
child 303228 f5a8cb2801d251405060a6a909dea3fbe6f89c32
push id42539
push usernnethercote@mozilla.com
push dateFri, 21 Nov 2014 04:01:01 +0000
treeherdertry@53e19f888ec7 [default view] [failures only]
bugs1102525
milestone36.0a1
Bug 1102525 (part 1) - Add SegmentedVector to MFBT. This is based on the SegmentedArray type from nsCycleCollector.cpp.
mfbt/SegmentedVector.h
mfbt/moz.build
mfbt/tests/TestSegmentedVector.cpp
mfbt/tests/moz.build
new file mode 100644
--- /dev/null
+++ b/mfbt/SegmentedVector.h
@@ -0,0 +1,186 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// A simple segmented vector class. It should be used in preference to
+// mozilla::Vector or nsTArray when you are simply gathering items in order to
+// later iterate over them, because it avoids those types' need to continually
+// allocate and copy the data into increasingly large buffers.
+
+#ifndef mozilla_SegmentedVector_h
+#define mozilla_SegmentedVector_h
+
+#include "mozilla/Array.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Move.h"
+#include "mozilla/TypeTraits.h"
+
+#include <new>  // for placement new
+
+namespace mozilla {
+
+namespace detail {
+
+template<typename T, size_t SegmentCapacity>
+struct Segment : public mozilla::LinkedListElement<Segment<T, SegmentCapacity>>
+{
+  Segment() : mLength(0) {}
+
+  uint32_t Length() const { return mLength; }
+
+  template<typename U>
+  void Append(U&& aU)
+  {
+    MOZ_ASSERT(mLength < SegmentCapacity);
+    new (&mElems[mLength]) T(mozilla::Forward<U>(aU));
+    mLength++;
+  }
+
+  uint32_t mLength;
+  mozilla::Array<T, SegmentCapacity> mElems;
+};
+
+} // namespace detail
+
+// For a given segment size (in bytes), compute the number of T elements that
+// can fit into it. It's a good idea to use a reasonably large power-of-two for
+// IdealSegmentSize, e.g. 4096 or 8192.
+template<typename T, size_t IdealSegmentSize>
+class SegmentedVectorCapacity
+{
+  static const size_t kSingleElementSegmentSize =
+    sizeof(detail::Segment<T, 1>);
+
+public:
+  // See how many we can fit. If IdealSegmentSize is too small, it'll be just
+  // one. The +1 is because kSingleElementSegmentSize already accounts for one
+  // element.
+  static const size_t value =
+    kSingleElementSegmentSize <= IdealSegmentSize
+    ? (IdealSegmentSize - kSingleElementSegmentSize) / sizeof(T) + 1
+    : 1;
+};
+
+template<class T, size_t SegmentCapacity>
+class SegmentedVector
+{
+  typedef detail::Segment<T, SegmentCapacity> Segment;
+
+public:
+  // The |aIdealSegmentSize| is only for sanity checking. If it's specified, we
+  // check that the actual segment size is as close as possible to it. This
+  // serves as a sanity check for SegmentedVectorCapacity's capacity
+  // computation.
+  SegmentedVector(size_t aIdealSegmentSize = 0)
+  {
+    // The difference between the actual segment size and the ideal segment
+    // size should be less than the size of a single element... unless the
+    // ideal size was too small, in which case the capacity should be one.
+    MOZ_ASSERT_IF(
+      aIdealSegmentSize != 0,
+      (sizeof(Segment) > aIdealSegmentSize && SegmentCapacity == 1) ||
+      aIdealSegmentSize - sizeof(Segment) < sizeof(T));
+  }
+
+  ~SegmentedVector() { Clear(); }
+
+  bool IsEmpty() const { return !mSegments.getFirst(); }
+
+  // Note that this is O(n) rather than O(1), but the constant factor is very
+  // small because it only has to do one addition per segment.
+  size_t Length() const
+  {
+    size_t n = 0;
+    for (auto segment = mSegments.getFirst();
+         segment;
+         segment = segment->getNext()) {
+      n += segment->Length();
+    }
+    return n;
+  }
+
+  template<typename U>
+  void Append(U&& aU)
+  {
+    Segment* last = mSegments.getLast();
+    if (!last || last->Length() == SegmentCapacity) {
+      last = new Segment();
+      mSegments.insertBack(last);
+    }
+    last->Append(mozilla::Forward<U>(aU));
+  }
+
+  void Clear()
+  {
+    Segment* segment;
+    while ((segment = mSegments.popFirst())) {
+      delete segment;
+    }
+  }
+
+  // Use this class to iterate over a SegmentedVector, like so:
+  //
+  //  for (auto r = v.All(); !r.IsEmpty(); r.PopFront()) {
+  //    MyElem& elem = r.Front();
+  //    f(elem);
+  //  }
+  //
+  class Range
+  {
+    friend class SegmentedVector;
+
+    Segment* mSegment;
+    size_t mIndex;
+
+    Range(SegmentedVector* aVector)
+      : mSegment(aVector->mSegments.getFirst())
+      , mIndex(0)
+    {}
+
+  public:
+    bool IsEmpty() const { return !mSegment; }
+
+    T& Front() const
+    {
+      MOZ_ASSERT(!IsEmpty());
+      return mSegment->mElems[mIndex];
+    }
+
+    void PopFront()
+    {
+      MOZ_ASSERT(!IsEmpty());
+      mIndex++;
+      if (mIndex == mSegment->Length()) {
+        mSegment = mSegment->getNext();
+        mIndex = 0;
+      }
+    }
+  };
+
+  Range All() { return Range(this); }
+
+  // Measure the memory consumption of the vector excluding |this|. Note that
+  // it only measures the vector itself. If the vector elements contain
+  // pointers to other memory blocks, those blocks must be measured separately
+  // during a subsequent iteration over the vector.
+  size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+  {
+    return mSegments.sizeOfExcludingThis(aMallocSizeOf);
+  }
+
+  // Like sizeOfExcludingThis(), but measures |this| as well.
+  size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
+private:
+  mozilla::LinkedList<Segment> mSegments;
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_SegmentedVector_h */
--- a/mfbt/moz.build
+++ b/mfbt/moz.build
@@ -56,16 +56,17 @@ EXPORTS.mozilla = [
     'Poison.h',
     'Range.h',
     'RangedPtr.h',
     'RefCountType.h',
     'ReentrancyGuard.h',
     'RefPtr.h',
     'RollingMean.h',
     'Scoped.h',
+    'SegmentedVector.h',
     'SHA1.h',
     'SplayTree.h',
     'TaggedAnonymousMemory.h',
     'TemplateLib.h',
     'ThreadLocal.h',
     'ToString.h',
     'TypedEnum.h',
     'TypedEnumBits.h',
new file mode 100644
--- /dev/null
+++ b/mfbt/tests/TestSegmentedVector.cpp
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 9; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This is included first to ensure it doesn't implicitly depend on anything
+// else.
+#include "mozilla/SegmentedVector.h"
+
+#include "mozilla/Assertions.h"
+
+using mozilla::SegmentedVector;
+using mozilla::SegmentedVectorCapacity;
+
+// This tests basic segmented vector construction and iteration.
+void TestBasics()
+{
+  // A SegmentedVector with a POD element type.
+  static const size_t cap = SegmentedVectorCapacity<int, 1024>::value;
+  typedef SegmentedVector<int, cap> MyVector;
+  MyVector v(1024);
+  int i, n;
+
+  MOZ_RELEASE_ASSERT(v.IsEmpty());
+
+  // Add 100 elements, then check various things.
+  i = 0;
+  for ( ; i < 100; i++) {
+    v.Append(mozilla::Move(i));
+  }
+  MOZ_RELEASE_ASSERT(!v.IsEmpty());
+  MOZ_RELEASE_ASSERT(v.Length() == 100);
+
+  n = 0;
+  for (MyVector::Range r = v.All(); !r.IsEmpty(); r.PopFront()) {
+    MOZ_RELEASE_ASSERT(r.Front() == n);
+    n++;
+  }
+  MOZ_RELEASE_ASSERT(n == 100);
+
+  // Add another 900 elements, then re-check.
+  for ( ; i < 1000; i++) {
+    v.Append(mozilla::Move(i));
+  }
+  MOZ_RELEASE_ASSERT(!v.IsEmpty());
+  MOZ_RELEASE_ASSERT(v.Length() == 1000);
+
+  n = 0;
+  for (MyVector::Range r = v.All(); !r.IsEmpty(); r.PopFront()) {
+    MOZ_RELEASE_ASSERT(r.Front() == n);
+    n++;
+  }
+  MOZ_RELEASE_ASSERT(n == 1000);
+
+  v.Clear();
+  MOZ_RELEASE_ASSERT(v.IsEmpty());
+  MOZ_RELEASE_ASSERT(v.Length() == 0);
+}
+
+// This tests how segmented vectors with non-POD elements construct and
+// destruct those elements.
+void TestConstructorsAndDestructors()
+{
+  static int gNumDefaultCtors;
+  static int gNumExplicitCtors;
+  static int gNumCopyCtors;
+  static int gNumMoveCtors;
+  static int gNumDtors;
+
+  struct NonPOD
+  {
+    NonPOD()                { gNumDefaultCtors++; }
+    explicit NonPOD(int x)  { gNumExplicitCtors++; }
+    NonPOD(NonPOD&)         { gNumCopyCtors++; }
+    NonPOD(NonPOD&&)        { gNumMoveCtors++; }
+    ~NonPOD()               { gNumDtors++; }
+  };
+
+  {
+    // A SegmentedVector with a non-POD element type.
+    NonPOD x(1);                      // explicit constructor called
+    SegmentedVector<NonPOD, 10> v;    // default constructor called 10x
+    MOZ_RELEASE_ASSERT(v.IsEmpty());
+    v.Append(x);                      // copy constructor called
+    NonPOD y(1);                      // explicit constructor called
+    v.Append(mozilla::Move(y));       // move constructor called
+    NonPOD z(1);                      // explicit constructor called
+    v.Append(mozilla::Move(z));       // move constructor called
+    v.Clear();                        // destructor called 10x
+
+    MOZ_RELEASE_ASSERT(gNumDefaultCtors  == 10);
+    MOZ_RELEASE_ASSERT(gNumExplicitCtors == 3);
+    MOZ_RELEASE_ASSERT(gNumCopyCtors     == 1);
+    MOZ_RELEASE_ASSERT(gNumMoveCtors     == 2);
+    MOZ_RELEASE_ASSERT(gNumDtors         == 10);
+  }                                   // destructor called for x, y, z
+  MOZ_RELEASE_ASSERT(gNumDtors == 13);
+}
+
+// This tests that SegmentedVectorCapacity gives the right segment capacities
+// for specified segment sizes.
+void TestSegmentSizing()
+{
+  struct A { int mX; int mY; };
+  struct B { int mX; char mY; double mZ; };
+  struct C { A mA; B mB; };
+  struct D { char mBuf[101]; };
+  struct E { };
+
+  // When SegmentedVector's constructor is passed a size, it asserts that the
+  // vector's segment capacity results in a segment size equal to (or very
+  // close to) the passed size.
+  SegmentedVector<double, SegmentedVectorCapacity<double, 512>::value> v1(512);
+  SegmentedVector<A, SegmentedVectorCapacity<A, 1024>::value> v2(1024);
+  SegmentedVector<B, SegmentedVectorCapacity<B, 999>::value> v3(999);
+  SegmentedVector<C, SegmentedVectorCapacity<C, 10>::value> v4(10);
+  SegmentedVector<D, SegmentedVectorCapacity<D, 1234>::value> v5(1234);
+  SegmentedVector<E, SegmentedVectorCapacity<E, 4096>::value> v6(4096);
+}
+
+int main(void)
+{
+  TestBasics();
+  TestConstructorsAndDestructors();
+  TestSegmentSizing();
+
+  return 0;
+}
--- a/mfbt/tests/moz.build
+++ b/mfbt/tests/moz.build
@@ -20,16 +20,17 @@ CppUnitTests([
     'TestIntegerPrintfMacros',
     'TestJSONWriter',
     'TestMacroArgs',
     'TestMacroForEach',
     'TestMaybe',
     'TestPair',
     'TestRefPtr',
     'TestRollingMean',
+    'TestSegmentedVector',
     'TestSHA1',
     'TestSplayTree',
     'TestTypedEnum',
     'TestTypeTraits',
     'TestUniquePtr',
     'TestWeakPtr',
 ])