簡體   English   中英

C ++:順序訪問元素時,訪問C數組的速度更快

[英]C++: Accessing C-array much faster when accessing elements sequentially

我想在內存中存儲一​​個3D卷。 為此,我使用線性數組,然后從3d索引計算1d索引。 它包裝在名為Volume的類中,該類提供用於訪問數組的數據元素的功能。 這是用於訪問卷的一個數據元素的函數:

template<typename T>
inline T& Volume<T>::at(size_t x, size_t y, size_t z) {
    if (x >= this->xMax || y >= this->yMax || z >= this->zMax) throw std::out_of_range("Volume index out of bounds");
    return this->volume[x * this->yMax*this->zMax + y*this->zMax + z]
}

現在,這將以Z最快的索引順序線性化3d體積。 如果在這樣的循環中訪問該卷,則會在它們位於內存中的卷元素上依次迭代:

Volume<float> volume(10, 20, 30); //parameters define size
for(int x = 0; x < volume.xSize(); ++x) {
    for(int y = 0; y < volume.ySize(); ++y) {
        for int z = 0; z < volume.zSize(); ++z) {
            volume.at(x, y, z);  //do sth with this voxel
        }
    }
}

但是,如果我這樣編寫循環,則不會按順序訪問它們,而是以更“隨機”的順序進行訪問:

Volume<float> volume(10, 20, 30); //parameters define size
for(int z = 0; z < volume.zSize(); ++z) {
    for(int y = 0; y < volume.ySize(); ++y) {
        (for int x = 0; x < volume.zSize(); ++x) {
            volume.at(x, y, z);  //do sth with this voxel
        }
    }
}

現在,第一種情況運行很快,第二種情況運行緩慢。 我的第一個問題是: 為什么? 我想這與緩存有關,但是我不確定。

現在,我可以這樣重寫卷元素的訪問函數:

template<typename T>
inline T& Volume<T>::at(size_t x, size_t y, size_t z) {
    if (x >= this->xMax || y >= this->yMax || z >= this->zMax) throw std::out_of_range("Volume index out of bounds");
    return this->volume[x * this->yMax*this->zMax + y*this->zMax + z]
}

然后,循環順序2會很快(因為訪問順序發生),但是循環順序1會很慢。

現在,由於某種原因,我需要在程序中同時使用兩個索引順序。 而且兩者都應該很快。 想法是,在創建卷時可以定義索引順序,然后將使用該索引順序。 首先,我在at函數中嘗試了一個簡單的if-else語句。 但是,這似乎並沒有解決問題。

因此,在設置訂購模式時,我嘗試了如下操作:

template<typename T>
void Volume<T>::setMemoryLayout(IndexOrder indexOrder) {
    this->mode = indexOrder;
    if (indexOrder == IndexOrder::X_FASTEST) {
        this->accessVoxel = [this](size_t x, size_t y, size_t z)->T& {
            return this->volume[z * this->yMax*this->xMax + y*this->xMax + x];
        };
    } else {
        this->accessVoxel = [this](size_t x, size_t y, size_t z)->T& {
            return this->volume[x * this->yMax* this->zMax + y*this->zMax + z];
        };
    }
}

然后當實際訪問體素時:

template<typename T>
inline T& Volume<T>::at(size_t x, size_t y, size_t z) {
    if (x >= this->xMax || y >= this->yMax || z >= this->zMax) throw std::out_of_range("Volume index out of bounds");
    return this->accessVoxel(x, y, z);
}

因此,我的想法是通過更改當前模式時動態定義一次lambda函數,從而減少at函數內部必需的if語句的開銷。 然后,只有在調用at時才必須調用它。 但是,這沒有達到我想要的。

我的問題是為什么我的嘗試不起作用,以及是否有一種方法可以真正實現我想要的功能:支持X最快和Y最快索引排序並在循環時提供相應性能提升的卷因此。

注意:我的目標是當有數據分配給該卷且仍然正確讀取數據時,不能在兩種模式之間切換。

在我的CPU(可能還有您的CPU)上,我有64個字節的緩存行。 每個高速緩存行包含16個4字節浮點數。 當為第一個浮點數獲取高速緩存行時,在順序訪問時,您無需為隨后的15個重復該工作。

請注意,大約需要240個周期才能從主內存中提取一條緩存行。 從L1緩存中獲取數據大約需要12個周期,如果您可以重復命中L1,則這是一個很大的差異。 (L2花費約40個周期,L3花費150個周期)

順序訪問的第二個緩存優勢是當順序讀取時,CPU將為您預取數據到緩存中。 因此,如果您從數組的開頭開始並順序地遍歷數組,則甚至可以避免讀取高速緩存行的代價。

L1通常是32k的數據(和32k的指令高速緩存),對我來說,這台機器上的L2是256K,而L3是兆字節。 因此,您可以使內存工作集越小,可以在給定緩存中容納的內存就越多。 將其全部裝入L1是最佳選擇。

順序訪問是最優的第三個原因是它使編譯器有機會對指令進行矢量化。 即使用SSE或AVX指令。 AVX寄存器為32個字節,因此可以容納8個浮點數。 潛在地,您可以一次對數組中的8個連續項進行操作,從而使處理速度提高8倍。

您計算機的物理內存不足以容納整個陣列。 解決您的問題的一種方法是增加內存。

您的操作系統可以使用虛擬內存。 每當需要更多內存時,虛擬內存頁面就會移至磁盤。 訪問磁盤非常耗時,這會降低性能。 在最壞的情況下,OS會一直不斷寫入和讀取(或只是讀取)頁面。 因此,另一種解決方案是重新組織數據,以使磁盤訪問幾乎相同,而與掃描像素的方向無關。 我建議將3D區域的大小設為頁面大小(通常為4KB,因此是一個16像素大小的立方體)。 因此,當您沿一個方向掃描時,您只會觸摸其中的幾頁,而當您沿另一方向掃描時,您將會觸摸相同數量的不同頁面集。 運氣好的話(取決於可用的物理內存),沒有頁面會不必要地移入和移出交換文件。

最好和最簡單的解決方案是僅在一個方向上掃描像素。 也許您實際上沒有必要橫向掃描像素。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM