如何防止在级联运算符中创建中间对象?

Posted

技术标签:

【中文标题】如何防止在级联运算符中创建中间对象?【英文标题】:How to prevent creating intermediate objects in cascading operators? 【发布时间】:2008-09-22 13:27:25 【问题描述】:

我在我的应用程序中使用了一个自定义的 Matrix 类,并且我经常添加多个矩阵:

Matrix result = a + b + c + d; // a, b, c and d are also Matrices

但是,这会为每个加法运算创建一个中间矩阵。由于这是简单的加法,因此可以通过一次添加所有 4 个矩阵的元素来避免中间对象并创建结果。我怎样才能做到这一点?

注意:我知道我可以定义多个函数,例如 Add3Matrices(a, b, c)Add4Matrices(a, b, c, d) 等,但我想保持 result = a + b + c + d 的优雅。

【问题讨论】:

Ian G 的反应非常好,但需要考虑的是为什么要避免创建临时对象?开销很小,GC 会毫无问题地收集它们。 (是的,不这样做效率更高,但生成的代码的可维护性如何?) 【参考方案1】:

您可以使用惰性求值将自己限制在一个小型中间体。类似的东西

public class LazyMatrix

    public static implicit operator Matrix(LazyMatrix l)
    
        Matrix m = new Matrix();
        foreach (Matrix x in l.Pending)
        
            for (int i = 0; i < 2; ++i)
                for (int j = 0; j < 2; ++j)
                    m.Contents[i, j] += x.Contents[i, j];
        

        return m;
    

    public List<Matrix> Pending = new List<Matrix>();


public class Matrix

    public int[,] Contents =   0, 0 ,  0, 0  ;

    public static LazyMatrix operator+(Matrix a, Matrix b)
    
        LazyMatrix l = new LazyMatrix();
        l.Pending.Add(a);
        l.Pending.Add(b);
        return l;
    

    public static LazyMatrix operator+(Matrix a, LazyMatrix b)
    
        b.Pending.Add(a);
        return b;
    


class Program

    static void Main(string[] args)
    
        Matrix a = new Matrix();
        Matrix b = new Matrix();
        Matrix c = new Matrix();
        Matrix d = new Matrix();

        a.Contents[0, 0] = 1;
        b.Contents[1, 0] = 4;
        c.Contents[0, 1] = 9;
        d.Contents[1, 1] = 16;

        Matrix m = a + b + c + d;

        for (int i = 0; i < 2; ++i)
        
            for (int j = 0; j < 2; ++j)
            
                System.Console.Write(m.Contents[i, j]);
                System.Console.Write("  ");
            
            System.Console.WriteLine();
        

        System.Console.ReadLine();
    

【讨论】:

非常好,谢谢。我什至可以看到如何使用它来懒惰地评估更复杂的操作。【参考方案2】:

至少可以避免痛苦的事情

Matrix Add3Matrices(a,b,c) //and so on 

应该是

Matrix AddMatrices(Matrix[] matrices)

【讨论】:

您可以使用“params”关键字来接受尽可能多的对象,而不是传递矩阵对象数组。【参考方案3】:

在 C++ 中,可以使用 Template Metaprograms 和 here,使用模板来做到这一点。但是,模板编程并非易事。我不知道 C# 中是否有类似的技术,很可能没有。

这种技术,在 c++ 中完全符合您的要求。缺点是如果某些地方不太正确,那么编译器错误消息往往会跑到好几页,而且几乎不可能破译。

如果没有这些技术,我怀疑您只能使用 Add3Matrices 等函数。

但对于 C#,此链接可能正是您所需要的:Efficient Matrix Programming in C#,尽管它的工作方式似乎与 C++ 模板表达式略有不同。

【讨论】:

【参考方案4】:

你无法避免创建中间对象。

但是,您可以使用here 中描述的表达式模板来最小化它们并对模板进行花哨的惰性求值。

在最简单的层面上,表达式模板可以是一个对象,它存储对多个矩阵的引用,并在赋值时调用适当的函数,如 Add3Matrices()。在***别,表达式模板会根据请求以惰性方式计算最小信息量。

【讨论】:

【参考方案5】:

这不是最干净的解决方案,但如果你知道评估顺序,你可以这样做:

result = MatrixAdditionCollector() << a + b + c + d

(或具有不同名称的相同事物)。然后 MatrixCollector 将 + 实现为 +=,也就是说,从一个未定义大小的 0 矩阵开始,一旦对第一个 + 求值,然后将所有内容加在一起(或复制第一个矩阵),就会获取一个大小。这会将中间对象的数量减少到 1(甚至 0,如果您以良好的方式实现分配,因为 MatrixCollector 可能会立即/包含结果。) 我不完全确定这到底是丑陋的还是一个人可能会做的更好的黑客行为之一。一个特定的优势是发生的事情很明显。

【讨论】:

【参考方案6】:

我可以建议一个行为很像 StringBuilder 的 MatrixAdder。您将矩阵添加到 MatrixAdder,然后调用 ToMatrix() 方法,该方法将在惰性实现中为您执行添加操作。这会给你你想要的结果,可以扩展到任何类型的 LazyEvaluation,但也不会引入任何可能混淆其他代码维护者的聪明实现。

【讨论】:

【参考方案7】:

我认为您可以将所需的就地添加行为明确:

Matrix result = a;
result += b;
result += c;
result += d;

但正如 Doug 在这篇文章的评论中指出的那样,编译器会将这段代码视为我编写的:

Matrix result = a;
result = result + b;
result = result + c;
result = result + d;

因此仍会创建临时对象。

我只是删除这个答案,但似乎其他人可能有同样的误解,所以把这个当作一个反例。

【讨论】:

根据规范,这样做对临时工没有影响。无法提供 += 运算符的单独重载,因此通过使用临时结果从重载的 + 运算符合成一个。有关详细信息,请参阅第 14.14.2 节。【参考方案8】:

Bjarne Stroustrup 有一篇名为Abstraction, libraries, and efficiency in C++ 的简短论文,其中提到了用于实现您所寻找的技术。具体来说,他提到了库Blitz++,这是一个用于科学计算的库,它也具有高效的矩阵运算,以及其他一些有趣的库。另外,我建议阅读 a conversation with Bjarne Stroustrup on artima.com 关于该主题的内容。

【讨论】:

【参考方案9】:

这是不可能的,使用运算符。

【讨论】:

【参考方案10】:

我的第一个解决方案是这样的(如果可能,添加到 Matrix 类中):

static Matrix AddMatrices(Matrix[] lMatrices) // or List<Matrix> lMatrices

    // Check consistency of matrices

    Matrix m = new Matrix(n, p);

    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            foreach (Maxtrix mat in lMatrices)
                m[i, j] += mat[i, j];

    return m;

我在 Matrix 类中有它,因为您可以依赖私有方法和属性,这些方法和属性可能对您的函数有用,以防矩阵的实现发生变化(非空节点的链表而不是大的双数组,例如)。

当然,你会失去result = a + b + c + d 的优雅。但是你会有类似result = Matrix.AddMatrices(new Matrix[] a, b, c, d ); 的东西。

【讨论】:

【参考方案11】:

有几种方法可以实现惰性求值以实现这一目标。但重要的是要记住,并非总是您的编译器能够获得所有代码中最好的代码。

我已经做出了在 GCC 中运行良好的实现,甚至超越了传统的几个 For unreadable code 的性能,因为它们导致编译器观察到数据段之间没有别名(随着数组的出现而难以掌握)。但其中一些在 MSVC 中完全失败,在其他实现中反之亦然。不幸的是,这些内容太长,无法在此处发布(不要认为此处适合数千行代码)。

用于科学计算的 Blitz++ 库是一个非常复杂的库,其中包含大量嵌入式知识。

【讨论】:

以上是关于如何防止在级联运算符中创建中间对象?的主要内容,如果未能解决你的问题,请参考以下文章

LINQ 到 SQL。如何防止使用 Apply 运算符

如何在 PHP 中创建对象的副本?

如何模拟在函数中创建的类(新运算符)?

我如何在HackerRank上的此算术运算符问题中创建预期的输出?

扩展运算符似乎没有复制完整对象

一文搞定Dart入门(学习笔记)