Skip to content

Conversation

@artagnon
Copy link
Contributor

@artagnon artagnon commented Jul 15, 2024

ConstraintSystem currently stores constraints in a vector-of-vectors, performing inefficient memory operations on it, as part of its operation. Replace this data structure with the newly-minted Matrix, using JaggedArrayView to eliminate row-swaps and truncation of the underlying storage.

Since Matrix requires knowing an upper bounds on the number of columns ahead-of-time, use the newly-added dry-run routine to do the entire allocation upfront.

-- 8< --
Based on #98893 and #99670.

@llvmbot llvmbot added llvm:analysis Includes value tracking, cost tables and constant folding llvm:transforms llvm:adt labels Jul 15, 2024
@llvmbot
Copy link
Member

llvmbot commented Jul 15, 2024

@llvm/pr-subscribers-llvm-transforms
@llvm/pr-subscribers-llvm-adt

@llvm/pr-subscribers-llvm-analysis

Author: Ramkumar Ramachandra (artagnon)

Changes

ConstraintSystem currently stores constraints in a vector-of-vectors, performing inefficient memory operations on it, as part of its
operation. Replace this data structure with the newly-minted Matrix, using MatrixView to eliminate row-swaps and truncation of the underlying storage.

Since Matrix requires knowing an upper bounds on the number of columns ahead-of-time, add a cl::opt to ConstraintElimination upper-bounding this, and change addVariableRowFill to not add constraints whose length exceeds this upper bound.

-- 8< --
Based on #98893.


Patch is 44.36 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/98895.diff

7 Files Affected:

  • (modified) llvm/include/llvm/ADT/ArrayRef.h (+56-44)
  • (added) llvm/include/llvm/ADT/Matrix.h (+339)
  • (modified) llvm/include/llvm/Analysis/ConstraintSystem.h (+47-19)
  • (modified) llvm/lib/Analysis/ConstraintSystem.cpp (+22-20)
  • (modified) llvm/lib/Transforms/Scalar/ConstraintElimination.cpp (+7-3)
  • (modified) llvm/unittests/ADT/CMakeLists.txt (+1)
  • (added) llvm/unittests/ADT/MatrixTest.cpp (+325)
diff --git a/llvm/include/llvm/ADT/ArrayRef.h b/llvm/include/llvm/ADT/ArrayRef.h
index 1c6799f1c56ed..6fc7acff7ba8c 100644
--- a/llvm/include/llvm/ADT/ArrayRef.h
+++ b/llvm/include/llvm/ADT/ArrayRef.h
@@ -25,6 +25,7 @@
 
 namespace llvm {
   template<typename T> class [[nodiscard]] MutableArrayRef;
+  template <typename T> struct [[nodiscard]] MatrixRowView;
 
   /// ArrayRef - Represent a constant reference to an array (0 or more elements
   /// consecutively in memory), i.e. a start pointer and a length.  It allows
@@ -59,19 +60,21 @@ namespace llvm {
     /// The number of elements.
     size_type Length = 0;
 
+    friend MatrixRowView<T>;
+
   public:
     /// @name Constructors
     /// @{
 
     /// Construct an empty ArrayRef.
-    /*implicit*/ ArrayRef() = default;
+    /*implicit*/ constexpr ArrayRef() = default;
 
     /// Construct an empty ArrayRef from std::nullopt.
-    /*implicit*/ ArrayRef(std::nullopt_t) {}
+    /*implicit*/ constexpr ArrayRef(std::nullopt_t) {}
 
     /// Construct an ArrayRef from a single element.
-    /*implicit*/ ArrayRef(const T &OneElt)
-      : Data(&OneElt), Length(1) {}
+    /*implicit*/ constexpr ArrayRef(const T &OneElt)
+        : Data(&OneElt), Length(1) {}
 
     /// Construct an ArrayRef from a pointer and length.
     constexpr /*implicit*/ ArrayRef(const T *data, size_t length)
@@ -123,9 +126,10 @@ namespace llvm {
     /// Construct an ArrayRef<const T*> from ArrayRef<T*>. This uses SFINAE to
     /// ensure that only ArrayRefs of pointers can be converted.
     template <typename U>
-    ArrayRef(const ArrayRef<U *> &A,
-             std::enable_if_t<std::is_convertible<U *const *, T const *>::value>
-                 * = nullptr)
+    constexpr ArrayRef(
+        const ArrayRef<U *> &A,
+        std::enable_if_t<std::is_convertible<U *const *, T const *>::value> * =
+            nullptr)
         : Data(A.data()), Length(A.size()) {}
 
     /// Construct an ArrayRef<const T*> from a SmallVector<T*>. This is
@@ -150,28 +154,32 @@ namespace llvm {
     /// @name Simple Operations
     /// @{
 
-    iterator begin() const { return Data; }
-    iterator end() const { return Data + Length; }
+    constexpr iterator begin() const { return Data; }
+    constexpr iterator end() const { return Data + Length; }
 
-    reverse_iterator rbegin() const { return reverse_iterator(end()); }
-    reverse_iterator rend() const { return reverse_iterator(begin()); }
+    constexpr reverse_iterator rbegin() const {
+      return reverse_iterator(end());
+    }
+    constexpr reverse_iterator rend() const {
+      return reverse_iterator(begin());
+    }
 
     /// empty - Check if the array is empty.
-    bool empty() const { return Length == 0; }
+    constexpr bool empty() const { return Length == 0; }
 
-    const T *data() const { return Data; }
+    constexpr const T *data() const { return Data; }
 
     /// size - Get the array size.
-    size_t size() const { return Length; }
+    constexpr size_t size() const { return Length; }
 
     /// front - Get the first element.
-    const T &front() const {
+    constexpr const T &front() const {
       assert(!empty());
       return Data[0];
     }
 
     /// back - Get the last element.
-    const T &back() const {
+    constexpr const T &back() const {
       assert(!empty());
       return Data[Length-1];
     }
@@ -184,7 +192,7 @@ namespace llvm {
     }
 
     /// equals - Check for element-wise equality.
-    bool equals(ArrayRef RHS) const {
+    constexpr bool equals(ArrayRef RHS) const {
       if (Length != RHS.Length)
         return false;
       return std::equal(begin(), end(), RHS.begin());
@@ -192,22 +200,22 @@ namespace llvm {
 
     /// slice(n, m) - Chop off the first N elements of the array, and keep M
     /// elements in the array.
-    ArrayRef<T> slice(size_t N, size_t M) const {
+    constexpr ArrayRef<T> slice(size_t N, size_t M) const {
       assert(N+M <= size() && "Invalid specifier");
       return ArrayRef<T>(data()+N, M);
     }
 
     /// slice(n) - Chop off the first N elements of the array.
-    ArrayRef<T> slice(size_t N) const { return slice(N, size() - N); }
+    constexpr ArrayRef<T> slice(size_t N) const { return slice(N, size() - N); }
 
     /// Drop the first \p N elements of the array.
-    ArrayRef<T> drop_front(size_t N = 1) const {
+    constexpr ArrayRef<T> drop_front(size_t N = 1) const {
       assert(size() >= N && "Dropping more elements than exist");
       return slice(N, size() - N);
     }
 
     /// Drop the last \p N elements of the array.
-    ArrayRef<T> drop_back(size_t N = 1) const {
+    constexpr ArrayRef<T> drop_back(size_t N = 1) const {
       assert(size() >= N && "Dropping more elements than exist");
       return slice(0, size() - N);
     }
@@ -225,14 +233,14 @@ namespace llvm {
     }
 
     /// Return a copy of *this with only the first \p N elements.
-    ArrayRef<T> take_front(size_t N = 1) const {
+    constexpr ArrayRef<T> take_front(size_t N = 1) const {
       if (N >= size())
         return *this;
       return drop_back(size() - N);
     }
 
     /// Return a copy of *this with only the last \p N elements.
-    ArrayRef<T> take_back(size_t N = 1) const {
+    constexpr ArrayRef<T> take_back(size_t N = 1) const {
       if (N >= size())
         return *this;
       return drop_front(size() - N);
@@ -253,7 +261,7 @@ namespace llvm {
     /// @}
     /// @name Operator Overloads
     /// @{
-    const T &operator[](size_t Index) const {
+    constexpr const T &operator[](size_t Index) const {
       assert(Index < Length && "Invalid index!");
       return Data[Index];
     }
@@ -319,20 +327,20 @@ namespace llvm {
     using difference_type = ptrdiff_t;
 
     /// Construct an empty MutableArrayRef.
-    /*implicit*/ MutableArrayRef() = default;
+    /*implicit*/ constexpr MutableArrayRef() = default;
 
     /// Construct an empty MutableArrayRef from std::nullopt.
-    /*implicit*/ MutableArrayRef(std::nullopt_t) : ArrayRef<T>() {}
+    /*implicit*/ constexpr MutableArrayRef(std::nullopt_t) : ArrayRef<T>() {}
 
     /// Construct a MutableArrayRef from a single element.
-    /*implicit*/ MutableArrayRef(T &OneElt) : ArrayRef<T>(OneElt) {}
+    /*implicit*/ constexpr MutableArrayRef(T &OneElt) : ArrayRef<T>(OneElt) {}
 
     /// Construct a MutableArrayRef from a pointer and length.
-    /*implicit*/ MutableArrayRef(T *data, size_t length)
-      : ArrayRef<T>(data, length) {}
+    /*implicit*/ constexpr MutableArrayRef(T *data, size_t length)
+        : ArrayRef<T>(data, length) {}
 
     /// Construct a MutableArrayRef from a range.
-    MutableArrayRef(T *begin, T *end) : ArrayRef<T>(begin, end) {}
+    constexpr MutableArrayRef(T *begin, T *end) : ArrayRef<T>(begin, end) {}
 
     /// Construct a MutableArrayRef from a SmallVector.
     /*implicit*/ MutableArrayRef(SmallVectorImpl<T> &Vec)
@@ -351,45 +359,49 @@ namespace llvm {
     template <size_t N>
     /*implicit*/ constexpr MutableArrayRef(T (&Arr)[N]) : ArrayRef<T>(Arr) {}
 
-    T *data() const { return const_cast<T*>(ArrayRef<T>::data()); }
+    constexpr T *data() const { return const_cast<T *>(ArrayRef<T>::data()); }
 
-    iterator begin() const { return data(); }
-    iterator end() const { return data() + this->size(); }
+    constexpr iterator begin() const { return data(); }
+    constexpr iterator end() const { return data() + this->size(); }
 
-    reverse_iterator rbegin() const { return reverse_iterator(end()); }
-    reverse_iterator rend() const { return reverse_iterator(begin()); }
+    constexpr reverse_iterator rbegin() const {
+      return reverse_iterator(end());
+    }
+    constexpr reverse_iterator rend() const {
+      return reverse_iterator(begin());
+    }
 
     /// front - Get the first element.
-    T &front() const {
+    constexpr T &front() const {
       assert(!this->empty());
       return data()[0];
     }
 
     /// back - Get the last element.
-    T &back() const {
+    constexpr T &back() const {
       assert(!this->empty());
       return data()[this->size()-1];
     }
 
     /// slice(n, m) - Chop off the first N elements of the array, and keep M
     /// elements in the array.
-    MutableArrayRef<T> slice(size_t N, size_t M) const {
+    constexpr MutableArrayRef<T> slice(size_t N, size_t M) const {
       assert(N + M <= this->size() && "Invalid specifier");
       return MutableArrayRef<T>(this->data() + N, M);
     }
 
     /// slice(n) - Chop off the first N elements of the array.
-    MutableArrayRef<T> slice(size_t N) const {
+    constexpr MutableArrayRef<T> slice(size_t N) const {
       return slice(N, this->size() - N);
     }
 
     /// Drop the first \p N elements of the array.
-    MutableArrayRef<T> drop_front(size_t N = 1) const {
+    constexpr MutableArrayRef<T> drop_front(size_t N = 1) const {
       assert(this->size() >= N && "Dropping more elements than exist");
       return slice(N, this->size() - N);
     }
 
-    MutableArrayRef<T> drop_back(size_t N = 1) const {
+    constexpr MutableArrayRef<T> drop_back(size_t N = 1) const {
       assert(this->size() >= N && "Dropping more elements than exist");
       return slice(0, this->size() - N);
     }
@@ -409,14 +421,14 @@ namespace llvm {
     }
 
     /// Return a copy of *this with only the first \p N elements.
-    MutableArrayRef<T> take_front(size_t N = 1) const {
+    constexpr MutableArrayRef<T> take_front(size_t N = 1) const {
       if (N >= this->size())
         return *this;
       return drop_back(this->size() - N);
     }
 
     /// Return a copy of *this with only the last \p N elements.
-    MutableArrayRef<T> take_back(size_t N = 1) const {
+    constexpr MutableArrayRef<T> take_back(size_t N = 1) const {
       if (N >= this->size())
         return *this;
       return drop_front(this->size() - N);
@@ -439,7 +451,7 @@ namespace llvm {
     /// @}
     /// @name Operator Overloads
     /// @{
-    T &operator[](size_t Index) const {
+    constexpr T &operator[](size_t Index) const {
       assert(Index < this->size() && "Invalid index!");
       return data()[Index];
     }
diff --git a/llvm/include/llvm/ADT/Matrix.h b/llvm/include/llvm/ADT/Matrix.h
new file mode 100644
index 0000000000000..eeb9702772738
--- /dev/null
+++ b/llvm/include/llvm/ADT/Matrix.h
@@ -0,0 +1,339 @@
+//===- Matrix.h - Two-dimensional Container with View -----------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_ADT_MATRIX_H
+#define LLVM_ADT_MATRIX_H
+
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/SmallVector.h"
+
+namespace llvm {
+template <typename T, size_t M, size_t NStorageInline> class MatrixView;
+
+/// Due to the SmallVector infrastructure using SmallVectorAlignmentAndOffset
+/// that depends on the exact data layout, no derived classes can have extra
+/// members.
+template <typename T, size_t N>
+struct MatrixStorageBase : public SmallVectorImpl<T>, SmallVectorStorage<T, N> {
+  MatrixStorageBase() : SmallVectorImpl<T>(N) {}
+  MatrixStorageBase(size_t Size) : SmallVectorImpl<T>(N) { resize(Size); }
+  ~MatrixStorageBase() { destroy_range(this->begin(), this->end()); }
+  MatrixStorageBase(const MatrixStorageBase &RHS) : SmallVectorImpl<T>(N) {
+    if (!RHS.empty())
+      SmallVectorImpl<T>::operator=(RHS);
+  }
+  MatrixStorageBase(MatrixStorageBase &&RHS) : SmallVectorImpl<T>(N) {
+    if (!RHS.empty())
+      SmallVectorImpl<T>::operator=(::std::move(RHS));
+  }
+  using SmallVectorImpl<T>::size;
+  using SmallVectorImpl<T>::resize;
+  using SmallVectorImpl<T>::append;
+  using SmallVectorImpl<T>::erase;
+  using SmallVectorImpl<T>::destroy_range;
+
+  T *begin() const { return const_cast<T *>(SmallVectorImpl<T>::begin()); }
+  T *end() const { return const_cast<T *>(SmallVectorImpl<T>::end()); }
+};
+
+/// A two-dimensional container storage, whose upper bound on the number of
+/// columns should be known ahead of time. Not menat to be used directly: the
+/// primary usage API is MatrixView.
+template <typename T,
+          size_t N = CalculateSmallVectorDefaultInlinedElements<T>::value>
+class MatrixStorage {
+public:
+  MatrixStorage() = delete;
+  MatrixStorage(size_t NRows, size_t NCols)
+      : Base(NRows * NCols), NCols(NCols) {}
+  MatrixStorage(size_t NCols) : Base(), NCols(NCols) {}
+
+  size_t size() const { return Base.size(); }
+  bool empty() const { return !size(); }
+  size_t getNumRows() const { return size() / NCols; }
+  size_t getNumCols() const { return NCols; }
+  void setNumCols(size_t NCols) {
+    assert(empty() && "Column-resizing a non-empty MatrixStorage");
+    this->NCols = NCols;
+  }
+  void resize(size_t NRows) { Base.resize(NCols * NRows); }
+
+protected:
+  template <typename U, size_t M, size_t NStorageInline>
+  friend class MatrixView;
+
+  T *begin() const { return Base.begin(); }
+  T *rowFromIdx(size_t RowIdx, size_t Offset = 0) const {
+    return begin() + RowIdx * NCols + Offset;
+  }
+  std::pair<size_t, size_t> idxFromRow(T *Ptr) const {
+    assert(Ptr >= begin() && "Internal error");
+    size_t Offset = (Ptr - begin()) % NCols;
+    return {(Ptr - begin()) / NCols, Offset};
+  }
+
+  // If Arg.size() < NCols, the number of columns won't be changed, and the
+  // difference is default-constructed.
+  void addRow(const SmallVectorImpl<T> &Arg) {
+    assert(Arg.size() <= NCols &&
+           "MatrixStorage has insufficient number of columns");
+    size_t Diff = NCols - Arg.size();
+    Base.append(Arg.begin(), Arg.end());
+    Base.append(Diff, T());
+  }
+
+private:
+  MatrixStorageBase<T, N> Base;
+  size_t NCols;
+};
+
+/// MutableArrayRef with a copy-assign, and extra APIs.
+template <typename T>
+struct [[nodiscard]] MatrixRowView : public MutableArrayRef<T> {
+  using pointer = typename MutableArrayRef<T>::pointer;
+  using iterator = typename MutableArrayRef<T>::iterator;
+  using const_iterator = typename MutableArrayRef<T>::const_iterator;
+
+  constexpr MatrixRowView() = delete;
+  constexpr MatrixRowView(pointer Data, size_t Length)
+      : MutableArrayRef<T>(Data, Length) {}
+  constexpr MatrixRowView(iterator Begin, iterator End)
+      : MutableArrayRef<T>(Begin, End) {}
+  constexpr MatrixRowView(const_iterator Begin, const_iterator End)
+      : MutableArrayRef<T>(Begin, End) {}
+  constexpr MatrixRowView(MutableArrayRef<T> Other)
+      : MutableArrayRef<T>(Other.data(), Other.size()) {}
+  MatrixRowView(const SmallVectorImpl<T> &Vec) : MutableArrayRef<T>(Vec) {}
+
+  using MutableArrayRef<T>::size;
+  using MutableArrayRef<T>::data;
+
+  constexpr T &back() const { return MutableArrayRef<T>::back(); }
+  constexpr T &front() const { return MutableArrayRef<T>::front(); }
+  constexpr MatrixRowView<T> drop_back(size_t N = 1) const { // NOLINT
+    return MutableArrayRef<T>::drop_back(N);
+  }
+  constexpr MatrixRowView<T> drop_front(size_t N = 1) const { // NOLINT
+    return MutableArrayRef<T>::drop_front(N);
+  }
+  // This slice is different from the MutableArrayRef slice, and specifies a
+  // Begin and End index, instead of a Begin and Length.
+  constexpr MatrixRowView<T> slice(size_t Begin, size_t End) {
+    return MutableArrayRef<T>::slice(Begin, End - Begin);
+  }
+  constexpr void pop_back(size_t N = 1) { // NOLINT
+    this->Length -= N;
+  }
+  constexpr void pop_front(size_t N = 1) { // NOLINT
+    this->Data += N;
+    this->Length -= N;
+  }
+
+  MatrixRowView &operator=(const SmallVectorImpl<T> &Vec) {
+    copy_assign(Vec.begin(), Vec.end());
+    return *this;
+  }
+  MatrixRowView &operator=(std::initializer_list<T> IL) {
+    copy_assign(IL.begin(), IL.end());
+    return *this;
+  }
+
+  void swap(MatrixRowView<T> &Other) {
+    std::swap(this->Data, Other.Data);
+    std::swap(this->Length, Other.Length);
+  }
+
+protected:
+  void copy_assign(iterator Begin, iterator End) { // NOLINT
+    std::uninitialized_copy(Begin, End, data());
+    this->Length = End - Begin;
+  }
+  void copy_assign(const_iterator Begin, const_iterator End) { // NOLINT
+    std::uninitialized_copy(Begin, End, data());
+    this->Length = End - Begin;
+  }
+};
+
+/// The primary usage API of MatrixStorage. Abstracts out indexing-arithmetic,
+/// eliminating memory operations on the underlying data. Supports
+/// variable-length columns.
+template <typename T,
+          size_t N = CalculateSmallVectorDefaultInlinedElements<T>::value,
+          size_t NStorageInline =
+              CalculateSmallVectorDefaultInlinedElements<T>::value>
+class [[nodiscard]] MatrixView {
+public:
+  using row_type = MatrixRowView<T>;
+  using container_type = SmallVector<row_type, N>;
+  using iterator = typename container_type::iterator;
+  using const_iterator = typename container_type::const_iterator;
+
+  constexpr MatrixView(MatrixStorage<T, NStorageInline> &Mat, size_t RowSpan,
+                       size_t ColSpan)
+      : Mat(Mat) {
+    RowView.reserve(RowSpan);
+    for (size_t RowIdx = 0; RowIdx < RowSpan; ++RowIdx) {
+      auto RangeBegin = Mat.begin() + RowIdx * ColSpan;
+      RowView.emplace_back(RangeBegin, RangeBegin + ColSpan);
+    }
+  }
+
+  // Constructor with a full View of the underlying MatrixStorage, if
+  // MatrixStorage has a non-zero number of Columns. Otherwise, creates an empty
+  // view.
+  constexpr MatrixView(MatrixStorage<T, NStorageInline> &Mat)
+      : MatrixView(Mat, Mat.getNumRows(), Mat.getNumCols()) {}
+
+  // Obvious copy-construator is deleted, since the underlying storage could
+  // have changed.
+  constexpr MatrixView(const MatrixView &) = delete;
+
+  // Copy-assignment operator should not be used when the underlying storage
+  // changes.
+  constexpr MatrixView &operator=(const MatrixView &Other) {
+    assert(Mat.begin() == Other.Mat.begin() &&
+           "Underlying storage has changed: use custom copy-constructor");
+    RowView = Other.RowView;
+    return *this;
+  }
+
+  // The actual copy-constructor: to be used when the underlying storage is
+  // copy-constructed.
+  MatrixView(const MatrixView &OldView,
+             MatrixStorage<T, NStorageInline> &NewMat)
+      : Mat(NewMat) {
+    assert(OldView.Mat.size() == Mat.size() &&
+           "Custom copy-constructor called on non-copied storage");
+
+    // The underlying storage will change. Construct a new RowView by performing
+    // pointer-arithmetic on the underlying storage of OldView, using pointers
+    // from OldVie.
+    for (const auto &R : OldView.RowView) {
+      auto [StorageIdx, StartOffset] = OldView.Mat.idxFromRow(R.data());
+      RowView.emplace_back(Mat.rowFromIdx(StorageIdx, StartOffset), R.size());
+    }
+  }
+
+  void addRow(const SmallVectorImpl<T> &Row) {
+    // The underlying storage may be resized, performing reallocations. The
+    // pointers in RowView will no longer be valid, so save and restore the
+    // data. Construct RestoreData by performing pointer-arithmetic on the
+    // underlying storgge.
+    SmallVector<std::tuple<size_t, size_t, size_t>> RestoreData;
+    RestoreData.reserve(RowView.size());
+    for (const auto &R : RowView) {
+      auto [StorageIdx, StartOffset] = Mat.idxFromRow(R.data());
+      RestoreData.emplace_back(StorageIdx, StartOffset, R.size());
+    }
+
+    Mat.addRow(Row);
+
+    // Restore the RowView by performing pointer-arithmetic on the
+    // possibly-reallocated storage, using information from RestoreData.
+    RowView.clear();
+    for (const auto &[StorageIdx, StartOffset, Len] : RestoreData)
+      RowView.emplace_back(Mat.rowFromIdx(StorageIdx, StartOffset), Len);
+
+    // Finally, add the new row to the VRowView.
+    RowView.emplace_back(Mat.rowFromIdx(Mat.getNumRows() - 1), Row.size());
+  }
+
+  // To support addRow(View[Idx]).
+  void addRow(const row_type &Row) { addRow(SmallVector<T>{Row}); }
+
+  void addRow(std::initializer_list<T> Row) { addRow(SmallVector<T>{Row}); }
+
+  constexpr row_type &operator[](size_t RowIdx) {
+    assert(RowIdx < RowView.size() && "Indexing out of bounds");
+    return RowView[RowIdx];
+  }
+
+  constexpr T *data() const {
+    assert(!empty() && "Non-empty view expected");
+    return RowView.front().data();
+  }
+  size_t size() const { return getRowSpan() * getMaxColSpan(); }
+...
[truncated]

@artagnon
Copy link
Contributor Author

artagnon commented Jul 15, 2024

@artagnon artagnon force-pushed the perf/constraintsystem-matrix branch from b1c870b to f78bc60 Compare July 15, 2024 18:29
@fhahn
Copy link
Contributor

fhahn commented Jul 16, 2024

Thanks for working on improving the representation used!

Since Matrix requires knowing an upper bounds on the number of columns ahead-of-time, add a cl::opt to ConstraintElimination upper-bounding this, and change addVariableRowFill to not add constraints whose length exceeds this upper bound.

I think the current cutoff is probably too low, I think in practice we often have systems with more than 16 columns/variables (there are a number of binary changes in CTMark due to this I think https://llvm-compile-time-tracker.com/compare.php?from=ef1851a4daa5a9b48cdff9bac12a116c6518d39b&to=f78bc6008c7dea8acce3a4171dd3ece15103419f&stat=size-text).

To compare apples to apples compile-time wise, it would be good to pick the default so we handle all cases handled without this change (might pessimize this change due to large up-front allocations though) or possibly resize the matrix on demand (which would need to repack the existing data)

@artagnon
Copy link
Contributor Author

I think the current cutoff is probably too low, I think in practice we often have systems with more than 16 columns/variables (there are a number of binary changes in CTMark due to this I think https://llvm-compile-time-tracker.com/compare.php?from=ef1851a4daa5a9b48cdff9bac12a116c6518d39b&to=f78bc6008c7dea8acce3a4171dd3ece15103419f&stat=size-text).

I see. Should I attempt to change it to 32 or 64? What's a good upper-bound on the number of columns, if the number of rows are upper-bounded at 500?

To compare apples to apples compile-time wise, it would be good to pick the default so we handle all cases handled without this change (might pessimize this change due to large up-front allocations though) or possibly resize the matrix on demand (which would need to repack the existing data)

I think one large upfront allocation wouldn't hurt as much as repeatedly calling malloc/free to resize the underlying storage. I think things can be improved by doing a dry-run, upper-bounding the number of rows and columns, and performing the entire allocation upfront.

@artagnon
Copy link
Contributor Author

I think one large upfront allocation wouldn't hurt as much as repeatedly calling malloc/free to resize the underlying storage. I think things can be improved by doing a dry-run, upper-bounding the number of rows and columns, and performing the entire allocation upfront.

Another thing I noticed was a call to a copy-constructor that copies the underlying storage. Is it really necessary to copy the underlying storage?

@artagnon artagnon force-pushed the perf/constraintsystem-matrix branch 4 times, most recently from 4f19a2b to 94bdfca Compare July 16, 2024 18:55
@fhahn
Copy link
Contributor

fhahn commented Jul 16, 2024

Looks like Clang crashes with the dry-run version on CTmark https://llvm-compile-time-tracker.com/show_error.php?commit=94bdfca2db58d6e6ff507d073e407acba1046701

@artagnon artagnon force-pushed the perf/constraintsystem-matrix branch from 94bdfca to 82ddf54 Compare July 16, 2024 21:23
@artagnon
Copy link
Contributor Author

Looks like Clang crashes with the dry-run version on CTmark https://llvm-compile-time-tracker.com/show_error.php?commit=94bdfca2db58d6e6ff507d073e407acba1046701

Should be fixed now. However, LLVM Compile Time Tracker times out now, and I have no idea how to go about debugging that: https://llvm-compile-time-tracker.com/show_error.php?commit=82ddf545ab4688a550881a7a7404d40fc585143e.

@artagnon artagnon force-pushed the perf/constraintsystem-matrix branch from 82ddf54 to bab070c Compare July 16, 2024 23:23
@fhahn
Copy link
Contributor

fhahn commented Jul 17, 2024

Looks like Clang crashes with the dry-run version on CTmark https://llvm-compile-time-tracker.com/show_error.php?commit=94bdfca2db58d6e6ff507d073e407acba1046701

Should be fixed now. However, LLVM Compile Time Tracker times out now, and I have no idea how to go about debugging that: https://llvm-compile-time-tracker.com/show_error.php?commit=82ddf545ab4688a550881a7a7404d40fc585143e.

This looks like a hang while building clang in stage 2 (while building LLVM/Clang with a clang that contains 82ddf545ab4688a550881a7a7404d40fc585143e ). Doing a stage2 build should hopefully reproduce the issue locally

@artagnon artagnon force-pushed the perf/constraintsystem-matrix branch from 9d14061 to 9683576 Compare July 17, 2024 10:28
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to first have this code live somewhere local to ConstraintSystem and once it matures / gains more usage, promote it to ADT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possible, since it's header-only. I would like some other opinions as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd strongly prefer this option then. Also, please do not click 'resolve' on conversations that haven't been resolved.

@kuhar kuhar requested a review from dwblaikie July 17, 2024 14:39
@artagnon artagnon force-pushed the perf/constraintsystem-matrix branch 3 times, most recently from 9e59d0e to 28aa6d4 Compare July 18, 2024 20:10
artagnon added 3 commits July 19, 2024 17:21
Leverage the excellent SmallVector infrastructure to write a
two-dimensional container, along with a View that abstracts out
indexing-arithmetic, eliminating memory operations on the underlying
storage. The immediate applicability of Matrix is to replace uses of the
vector-of-vectors idiom, with one caveat: an upper bound on the number
of columns should be known ahead of time.
Add a dry-run routine that computes a conservative estimate of the
number of rows and columns that the transform will require, and fail
early if the estimates exceed the upper bounds. This patch has a small
overhead, but improves compile-time on one benchmark significantly. The
overhead will be compensated for in a follow-up patch, where
ConstraintSystem is ported to use a Matrix data structure, performing
the full allocation ahead-of-time using these estimates.
ConstraintSystem currently stores constraints in a vector-of-vectors,
performing inefficient memory operations on it, as part of its
operation. Replace this data structure with the newly-minted Matrix,
using MatrixView to eliminate row-swaps and truncation of the underlying
storage.

Since Matrix requires knowing an upper bounds on the number of columns
ahead-of-time, add a cl::opt to ConstraintElimination upper-bounding
this, and change addVariableRowFill to not add constraints whose length
exceeds this upper bound.
@artagnon artagnon force-pushed the perf/constraintsystem-matrix branch from 28aa6d4 to 15dd685 Compare July 19, 2024 16:49
@fhahn
Copy link
Contributor

fhahn commented Jul 19, 2024

@artagnon let me know once there's a stable point for the patches, then I can run them on a larger set of programs to check the wider impact

@artagnon
Copy link
Contributor Author

@artagnon let me know once there's a stable point for the patches, then I can run them on a larger set of programs to check the wider impact

Will do, thanks! Sorry about the heavy load of notifications from pushes, while this is still in development: I didn't know to check size-text in advance.

@artagnon
Copy link
Contributor Author

Okay, the final results from LLVM Compile Time Tracker are back, and it looks like this change has a significant impact on one benchmark, and negligible regressions in ReleaseLTO: http://llvm-compile-time-tracker.com/compare.php?from=c5ee3c05ca61f3fae11337c5853aee7b450a9dc6&to=15dd68506d479cc50349de1cf1e70bd896b9c82e&stat=instructions%3Au.

@fhahn Could you check it on some more programs, when you have the time?

@artagnon
Copy link
Contributor Author

It looks like, due to the small Matrix size, the gains were all observed in the dry-run cutoff. Subtracting the impact of the dry-run patch, switching the data structure to Matrix has no impact according to LLVM Compile Time Tracker: http://llvm-compile-time-tracker.com/compare.php?from=47c8510fd2749c1f44cc33db75f7eac142919e71&to=15dd68506d479cc50349de1cf1e70bd896b9c82e&stat=instructions:u.

@artagnon artagnon closed this Nov 18, 2024
@artagnon artagnon deleted the perf/constraintsystem-matrix branch November 18, 2024 10:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

llvm:adt llvm:analysis Includes value tracking, cost tables and constant folding llvm:transforms

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants