【问题标题】:Caching policies and techniques for matrices矩阵的缓存策略和技术
【发布时间】:2009-01-15 02:06:44
【问题描述】:

如前所述,我目前正在开发一个用于个人项目的小型线性代数库。矩阵被实现为 C++ 向量,元素赋值 (a(i,j) = v; ) 被委托给向量元素的赋值。对于我的项目,我需要求解大量的方方程系统,为此,我为方阵实现了 LU 分解(高斯消元法)。在当前的实现中,我避免每次通过缓存 L 和 U 矩阵来重新计算 LU 分解,问题是由于我将元素分配委托给向量,我无法找到一种方法来判断矩阵是否正在更改以及是否重新计算分解。关于如何解决这个问题的任何想法?

谢谢

【问题讨论】:

  • 您有关于缓存 LU 分解的矩阵多久更改一次的统计数据吗?这种变化是否足够小以保证部分更新? (单元素更改只需要更新 1 行和 1 列)。
  • 我目前没有这些统计数据,但我打算按照您的建议进行操作。
  • 我认为您的意思是政策,而不是政治。微信

标签: c++ caching


【解决方案1】:
template<class T>
class matrix {
public:
    class accessor {
    public:
        accessor(T& dest, matrix& parent) : dest(dest), parent(parent) { }
        operator T const& () const { return dest; }
        accessor& operator=(T const& t) { dest = t; parent.invalidate_cache(); return *this; }
    private:
        T& dest;
        matrix& parent;
    };

    // replace those with actual implementation..
    accessor operator()(int x, int y) {
        static T t; return accessor(t, *this);
    }
    T const& operator()(int x, int y) const {
        static T t; return t;
    }

private:
    void invalidate_cache() { cout << "Cache invalidated !!\n"; }
    vector<T> impl;
};

感谢##iso-c++ @ irc.freenode.net 进行一些有用的更正

【讨论】:

  • 是:T const& operator()(int x, int y) const { static T t;返回 t; } 真的有必要吗?
  • 使用静态的原理是什么?
  • 这是编写可编译示例的最简单方法 :) 当然,您应该将 operator() 的主体完全替换为正确的实现(y*w+x 或其他)
【解决方案2】:

如果我理解正确,您需要在代码执行期间检查矩阵是否已更改。

好吧,向量不支持这样的功能。但是,您可以做的是编写自己的 Matrix 类,向其中添加此类功能并使用它来代替向量。

一个示例实现可能是:

class Matrix {
public:
    Matrix() : hasChanged(false) {}

    double setElement(int i, int j, double value) {
        innerStorage[i][j] = value;
        hasChanged = true;
    }

    double getElement(int i, int j) {
        return innerStorage[i][j];
    }

    void clearHasChangedFlag() {
        hasChanged = false;
    }

private:
    vector<vector<double> > innerStorage;
    bool hasChanged;
}

【讨论】:

  • 你会想要使用一个向量 并将向量索引计算为 i*ROW+j,这样可以为你提供参考位置。
  • 我排除了这个选项,因为它使用起来太无聊了。
  • 看来您已经编写了一个使用 std::vector 功能的类(矩阵),现在您想要 std::vector 不提供的功能,但是“这太无聊了”说矩阵中的功能?能不能解释一下?
  • 我的意思是“如果我推动用户使用 setElement() 和 getElement(),那么使用我的 Matrix 类太无聊了”,我自己添加这个功能并不无聊;)
  • 函数的名称由您决定。 insert()、assign() 和 push_back() 显然都是用于写访问的。 OTOH,operator[] 可用于读写访问。您可以使用某种带有重载 operator= 的代理对象来表示必须设置 isDirty 标志。
【解决方案3】:

您的描述听起来像是在原地进行 LU 分解。从内存的角度来看,这肯定更有效。这意味着在执行分解时覆盖矩阵值。如果这是真的,那么“被改变以及是否重新计算分解”是一个有争议的问题。当您使用 LU 分解覆盖原始矩阵时,您会丢失原始矩阵。

如果您没有覆盖原始数据,那么每当您给矩阵元素一个新值时,听起来您也想重新计算分解。我建议你不要这样做。对我来说似乎效率低下。如果客户想要更改许多值,他们可能不想在全部完成之前支付另一个 LU 分解的成本。

您可以尝试使用工厂接口进行矩阵转换/分解。这是一个简单的方法,它将接收一个 Matrix 并返回一个(分解的)矩阵。您可以以这种方式保留原始矩阵;返回值是一个新实例。您可以更改原始然后将其传递给工厂以重新计算 LU 分解。它会消耗您的内存,这对于非常大的矩阵来说可能是一个问题。

在 Java 中,我会这样写:

public interface MatrixDecomposition
{  
    Matrix decompose(Matrix original);
}

在 C++ 中,它是一个纯虚函数。 (太长了——我不记得语法了。)

还有其他类型的分解(例如 QR、SVD 等),因此此设计将在您需要时很好地适应这些分解。只需为接口编写另一个实现,Bob 就是你的叔叔。

许多物理问题都以“稀疏”矩阵为特征,这些矩阵具有聚集在对角线周围的非零值带宽和外部零值。如果您使用不在带宽之外存储零值的特殊技术,您可以解决更大的内存问题。

【讨论】:

  • 我没有就地进行 LU 分解。我在两个“内部”缓存矩阵中执行此操作,因此如果我再次需要 L 和 U,我不必重新计算它们。
  • 两个内部?你只需要一个。将 L 存储在下半部分,将 U 存储在上半部分。其他任何东西都是对内存的完全浪费。无需将分解存储在矩阵本身内。我认为将其外部化并制作接口工厂方法是一个更好的设计。
【解决方案4】:

Frederick 建议的 setElement 和 getElement 版本是一个选项,但使用起来太无聊了。我想用相同的语法在读写时访问 a(i,j) ,而不用担心我是否正在修改矩阵。最好的办法可能是让用户修改矩阵,并在他认为有必要时将重新计算 LU 分解的责任委托给他。但是会很无聊。

【讨论】:

  • 为什么你的替代方案如此令人兴奋?
  • 那么“矩阵被实现为 C++ 向量......”有什么意义呢?也就是说,您似乎已经编写了一个使用 std::vector 功能的类(Matrix),现在您想要 std::vector 不提供的功能,但是“太无聊了”将该功能放入 Matrix 中?
  • @Max:我在之前的回复中回复了你的评论。
【解决方案5】:

如何保留与上次 LU 因式分解一起使用的 Matrix 的完整隐藏副本,并在重新执行之前在 O(n*m) 时间内检查它?丑陋但可以工作。

【讨论】:

  • 当在“无聊”(即可维护)解决方案和“丑陋但有趣”(即不可维护)解决方案之间进行选择时,您应该始终选择无聊的解决方案。总是。 (en.wikiquote.org/wiki/Brian_Kernighan)。
  • Kernighan,顺便说一句,是“K&R C”中的“K”。
猜你喜欢
  • 1970-01-01
  • 2011-08-20
  • 2017-07-27
  • 2012-04-29
  • 1970-01-01
  • 2022-11-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多