簡體   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