Как вернуть собственное выражение, которое ссылается на локальные переменные? - PullRequest
0 голосов
/ 16 марта 2020

Я хочу создать функцию, которая возвращает выражение Eigen. Некоторые входные данные для этого выражения являются локальными переменными в этой функции. Однако, поскольку функция возвращает эти локальные переменные go вне области видимости. Позже, когда выражение вычисляется, некоторые данные мусора читаются, что приводит к неверному результату.

Простой пример:

template<typename T>
auto f(const T& x){
    Eigen::ArrayXXd temp = exp(x);
    return temp * (1 - temp);
}

живой пример

РЕДАКТИРОВАТЬ 2: Другой Более реалистичный c пример, который является не просто поэлементной операцией:

template <typename T>
auto softmax(const T& x){
    Eigen::ArrayXXd tmp = exp(x - x.maxCoeff());
    return tmp / tmp.sum();
}

Есть ли способ продлить время жизни такой локальной переменной до времени жизни выражения? Или нет другого способа, кроме как оценить результат перед возвратом из функции?

РЕДАКТИРОВАТЬ: Я ищу способы оптимизации существующей библиотеки. Поэтому я не могу слишком сильно изменить сигнатуру функции - тип возвращаемого значения должен быть выражением Eigen.

Ответы [ 2 ]

0 голосов
/ 23 марта 2020

Это можно сделать, разместив эти «локальные переменные» в куче. Они должны принадлежать возвращенному выражению, поэтому память освобождается, как только возвращенное выражение выходит из области видимости. Для этого нам нужно собственное выражение Eigen:

template <class ArgType, typename... Ptrs>
class Holder;

namespace Eigen {
namespace internal {
template <class ArgType, typename... Ptrs>
struct traits<Holder<ArgType, Ptrs...>> {
  typedef typename ArgType::StorageKind StorageKind;
  typedef typename traits<ArgType>::XprKind XprKind;
  typedef typename ArgType::StorageIndex StorageIndex;
  typedef typename ArgType::Scalar Scalar;
  enum {
    Flags = ArgType::Flags & RowMajorBit,
    RowsAtCompileTime = ArgType::RowsAtCompileTime,
    ColsAtCompileTime = ArgType::ColsAtCompileTime,
    MaxRowsAtCompileTime = ArgType::MaxRowsAtCompileTime,
    MaxColsAtCompileTime = ArgType::MaxColsAtCompileTime
  };
};
}  // namespace internal
}  // namespace Eigen

template <typename ArgType, typename... Ptrs>
class Holder
    : public Eigen::internal::dense_xpr_base<Holder<ArgType, Ptrs...>>::type {
 public:
  Holder(const ArgType& arg, Ptrs*... pointers)
      : m_arg(arg), m_unique_ptrs(std::unique_ptr<Ptrs>(pointers)...) {}
  typedef typename Eigen::internal::ref_selector<Holder<ArgType, Ptrs...>>::type
      Nested;
  typedef Eigen::Index Index;
  Index rows() const { return m_arg.rows(); }
  Index cols() const { return m_arg.cols(); }
  typedef typename Eigen::internal::ref_selector<ArgType>::type ArgTypeNested;
  ArgTypeNested m_arg;
  std::tuple<std::unique_ptr<Ptrs>...> m_unique_ptrs;
};

namespace Eigen {
namespace internal {
template <typename ArgType, typename... Ptrs>
struct evaluator<Holder<ArgType, Ptrs...>> : evaluator_base<Holder<ArgType>> {
  typedef Holder<ArgType, Ptrs...> XprType;
  typedef typename nested_eval<ArgType, 1>::type ArgTypeNested;
  typedef typename remove_all<ArgTypeNested>::type ArgTypeNestedCleaned;
  typedef typename XprType::CoeffReturnType CoeffReturnType;
  enum {
    CoeffReadCost = evaluator<ArgTypeNestedCleaned>::CoeffReadCost,
    Flags = evaluator<ArgTypeNestedCleaned>::Flags
            & (HereditaryBits | LinearAccessBit | PacketAccessBit),
    Alignment = Unaligned & evaluator<ArgTypeNestedCleaned>::Alignment,
  };

  evaluator<ArgTypeNestedCleaned> m_argImpl;

  evaluator(const XprType& xpr) : m_argImpl(xpr.m_arg) {}

  EIGEN_STRONG_INLINE CoeffReturnType coeff(Index row, Index col) const {
    CoeffReturnType val = m_argImpl.coeff(row, col);
    return val;
  }

  EIGEN_STRONG_INLINE CoeffReturnType coeff(Index index) const {
    CoeffReturnType val = m_argImpl.coeff(index);
    return val;
  }

  template <int LoadMode, typename PacketType>
  EIGEN_STRONG_INLINE PacketType packet(Index row, Index col) const {
    return m_argImpl.template packet<LoadMode, PacketType>(row, col);
  }

  template <int LoadMode, typename PacketType>
  EIGEN_STRONG_INLINE PacketType packet(Index index) const {
    return m_argImpl.template packet<LoadMode, PacketType>(index);
  }
};
}  // namespace internal
}  // namespace Eigen

template <typename T, typename... Ptrs>
Holder<T, Ptrs...> makeHolder(const T& arg, Ptrs*... pointers) {
  return Holder<T, Ptrs...>(arg, pointers...);
}

Таким образом, рассматриваемая функция может быть реализована следующим образом:

template<typename T>
auto f(const T& x){
    auto* temp = new Eigen::ArrayXXd(exp(x));
    return makeHolder(*temp * (1 - *temp), temp);
}

Это довольно сложно и имеет еще одну обратную сторону дополнительных динамик c выделения памяти. Но это решает проблему.

0 голосов
/ 16 марта 2020

Начиная с C ++ 14, вы можете использовать список инициализации для хранения temp в качестве члена данных замыкания, сгенерированного лямбда-выражением:

template<typename T>
auto f2(const T& x) {
    return [temp = exp(x).eval()](){ return temp * (1-temp); };
}

синтаксис вызова забавно:

Eigen::ArrayXXd y = f2(x)();

но работает как положено, потому что временный объект, созданный f2(x), уничтожается в конце полного выражения, поэтому результат может быть правильно оценен с помощью y = .., потому что temp все еще живы.

Демо

...