繁体   English   中英

重载operator []从1开始并且性能开销

[英]Overloading operator[] to start at 1 and performance overhead

我正在做一些C ++计算机制(不用担心,这里不需要物理知识)并且有些东西真的困扰我。

假设我想表示一个3D数学向量(与std :: vector无关):

class Vector {
    public:
        Vector(double x=0., double y=0., double z=0.) { 
            coordinates[0] = x;
            coordinates[1] = y;
            coordinates[2] = z;
        }
    private:
        double coordinates[3];
};

到现在为止还挺好。 现在我可以重载operator []来提取坐标:

double& Vector::operator[](int i) {
     return coordinates[i] ;
}

所以我可以输入:

Vector V; 

… //complex computation with V

double x1 = V[0];
V[1] = coord2;

问题是,从0开始索引在这里并不自然。 我的意思是,在排序数组时,我并不介意,但事实是每篇论文,书籍或其他内容中的常规符号总是以1开头的子行程坐标。这可能看起来很狡辩,但事实是在公式中,它总是需要双重理解我们正在采取的措施。 当然,对于矩阵来说,这是最糟糕的。

一个明显的解决方案是稍微不同的重载:

double& Vector::operator[](int i) {
     return coordinates[i-1] ;
}

所以我可以输入

double x1 = V[1];
V[2] = coord2;

它似乎是完美的,除了一件事:这个i-1减法似乎是一个很小的开销的好候选人。 你会说很小,但我正在做计算机制,所以这通常是我们买不起的东西。

所以现在(最后)我的问题是:您认为编译器可以优化它,还是有办法使其优化? (模板,宏,指针或参考kludge ...)

逻辑上,在

double xi = V[i];

大多数时候括号之间的整数是字面值(循环的3次迭代除外),内联运算符[]应该使它成为可能,对吗?

(抱歉这个looong问题)

编辑:

感谢您的所有意见和答案

我有点不同意人们告诉我,我们已经习惯了0索引向量。 从面向对象的角度来看,我认为没有理由将数学Vector作为0索引,因为使用0索引数组实现。 我们不打算关心底层实现。 现在,假设我不关心性能并使用映射来实现Vector类。 然后我会发现将'1'与'1st'坐标映射是很自然的。

也就是说我尝试使用1索引向量和矩阵,经过一些代码编写后,我发现每次使用数组时它都不能很好地交互。 我认为Vector和容器(std :: array,std :: vector ...)不会经常交互(意思是,在彼此之间传输数据),但似乎我错了。

现在我有一个我认为不那么有争议的解决方案(请给我你的意见):每次我在某些物理环境中使用Vector时,我想使用枚举:

enum Coord {
    x = 0,
    y = 1,
    z = 2
};
Vector V;
V[x] = 1;

我认为唯一的缺点是可以重新定义这些x,y和z而不会发出警告......

这个应该通过查看反汇编来测量或验证,但我的猜测是:getter函数很小并且它的参数是常量。 编译器很可能会内联函数并对减法进行常数折叠。 在这种情况下,运行时成本将为零。

为什么不尝试这个:

class Vector {
    public:
        Vector(double x=0., double y=0., double z=0.) { 
            coordinates[1] = x;
            coordinates[2] = y;
            coordinates[3] = z;
        }
    private:
        double coordinates[4];
};

如果您没有以数百万的数量实例化您的物体,那么记忆腰部可能是负担得起的。

您是否真的对其进行了剖析或检查了生成的代码? 这就是这个问题的答案。

如果operator []实现是可见的,那么这可能会被优化以具有零开销。

我建议你在班级(.h)中为你的班级定义。 如果在.cpp中定义它,那么编译器就无法进行优化。 此外,您的索引不应该是可以具有负值的“int”...使其成为size_t:

class Vector {

    // ...

public:
    double& operator[](const size_t i) {
        return coordinates[i-1] ;
    }

};

没有基准测试,你不能说任何关于性能的客观事物。 在x86上,可以使用相对寻址编译此减法,这非常便宜。 如果内联operator[] ,则开销为零 - 您可以使用inline或特定于编译器的指令(例如GCC的__attribute__((always_inline))来鼓励这种情况。

如果你必须保证它,并且偏移是一个编译时常量,那么使用模板是要走的路:

template<size_t I>
double& Vector::get() {
    return coordinates[i - 1];
}

double x = v.get<1>();

出于所有实际目的,由于恒定折叠,保证零开销。 您还可以使用命名访问者:

double Vector::x() const { return coordinates[0]; }
double Vector::y() const { return coordinates[1]; }
double Vector::z() const { return coordinates[2]; }

double& Vector::x() { return coordinates[0]; }
double& Vector::y() { return coordinates[1]; }
double& Vector::z() { return coordinates[2]; }

对于循环,迭代器:

const double* Vector::begin() const { return coordinates; }
const double* Vector::end() const { return coordinates + 3; }

double* Vector::begin() { return coordinates; }
double* Vector::end() { return coordinates + 3; }

// (x, y, z) -> (x + 1, y + 1, z + 1)
for (auto& i : v) ++i;

然而,和其他许多人一样,我不同意你的问题的前提。 你真的应该简单地使用基于0的索引,因为它在C ++领域更自然。 该语言已经非常复杂,您不需要为将来维护代码的人进一步复杂化。

说真的,以三种方式对此进行基准测试(即,将减法和双[4]方法比较为仅在调用者中使用从零开始的索引)。

完全有可能通过在某些缓存架构上强制执行16字节对齐来获得巨大的胜利,同样可能的是,在某些编译器/指令集/代码路径组合中,减法实际上是免费的。

唯一的方法是对现实代码进行基准测试。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM