簡體   English   中英

如何設置重復數據,以便大多數可以優化?

[英]How to set up repetitive data so that most can be optimized away?

我需要在32 kbit寬的數據上執行按位AND。 其中一個值是固定位掩碼。

我一次執行這個AND 32位。 簡化后,我的算法看起來像這樣:

(我正在從此示例中刪除內存管理,可變范圍問題等)

#include <stdint.h>

const uint32_t mask[1024] = {
            0b00110110100101100111001011000111,
            0b10001110100101111010010100100100,
            0b11101010010000110001101010010101,
            0b10001110100101111010010100100100,
            (...) // 1019 more lines!
            0b00110110100101100111001011000111};

uint32_t answer[1024] = {0};
uint32_t workingdata = 0;
uint16_t i = 0;

int main(void)
{
    for (i=0; i<1024; i++)
    {
        workingdata = getnextdatachunk();
        answer[i] = workingdata & mask[i];
    }

    do_something_with_answer();

    return 0;
}

這是事情:如果你看一下示例位掩碼,掩碼[1] ==掩碼[3]和掩碼[0] ==掩碼[1023]。

在我的實際位掩碼中,大多數值都是重復的; 整個1024值數組中只有20個不同的值。 此外,在我的最終應用程序中, 我有16個不同的位掩碼 ,每個位掩碼具有相似的內部重復。

我正在尋找一種好的方法來避免必須存儲和迭代這么多不必要的數據。

我考慮過的一種方法類似於查找表,其中我的數組只包含每個所需的位掩碼塊的單個實例:

const uint32_t mask[20] = {
            0b00110110100101100111001011000111,
            0b10001110100101111010010100100100,
            (...) // only 17 more lines!
            0b11101010010000110001101010010101};

uint32_t answer[1024] = {0};
uint32_t workingdata = 0;
uint16_t i = 0;

int main(void)
{
    for (i=0; i<1024; i++)
    {
        workingdata = getnextdata();

        switch(i)
        {
            // the mask indexes are precalculated:

            case 0:
                answer[i] = workingdata & mask[5];
                break;
            case 1:
                answer[i] = workingdata & mask[2];
                break;
            case 2:
                answer[i] = workingdata & mask[2];
                break;
            case 3:
                answer[i] = workingdata & mask[0];
                break;
            case (...): // 1020 more cases!
                (...);
                break;
            default:
        }
    }

    do_something_with_answer();

    return 0;
}

或者,使用更緊湊的switch語句:

switch(i)
{
    // the mask indexes are precalculated:

    case 0,3,4,5,18,35,67,(...),1019:
        answer[i] = workingdata & mask[0];
        break;
    case 1,15,16,55,89,91,(...),1004:
        answer[i] = workingdata & mask[1];
        break;
    case (...): // Only 18 more cases!
        (...);
        break;
    default:
}

這兩種解決方案都讓人不清楚發生了什么,我真的想避免這種情況發生。

理想情況下,我想保留原始結構並讓gcc的優化器消除所有不必要的數據。 如何保持我的代碼編寫良好並且仍然有效?

讓我們發明一個點系統,並假裝從L1緩存中獲取數據需要花費4個點,從L2緩存中獲取需要花費8個點,而不可預測的分支需要花費12個點。 請注意,選擇這些點粗略地表示“平均但未知的80x86 CPU的周期”。

具有單個1024條目表的原始代碼每次迭代將具有4個點的總成本(假設其經常足以使性能變得重要並且因此假設數據經常被足夠用於在L1高速緩存中)。

使用switch語句,編譯器將(希望 - 如果分支是性能噩夢的系列)將其轉換為跳轉表並執行類似goto table[i]; 所以它可能算作從一個表(4分)中取出,然后是一個不可預測的分支(12分); 或每次迭代總共16個點。

注意,對於64位代碼,編譯器生成的跳轉表將是1024個條目,其中每個條目是64位; 並且該表將是第一個選項的表的兩倍(這是1024個條目,其中每個條目是32位)。 但是,許多CPU中的L1數據緩存是64 KiB,因此64 KiB跳轉表意味着進入L1數據緩存的任何其他內容(源數據是AND,生成的“答案”數據,CPU堆棧上的任何內容)導致(64字節或8個條目)跳轉表從緩存中逐出以騰出空間。 這意味着有時你會支付“L1 miss,L2 hit”。 讓我們假設這種情況發生在5%的時間,因此實際成本最終為每次迭代的“(95 *(4 + 12)+ 5 *(8 + 12))/ 100 = 16.2”點。

鑒於您希望第一個選項的性能更好(“每次迭代16.2點”遠遠大於“每次迭代4點”),並且您希望第一個選項的可執行文件大小更好(甚至在不考慮switch每種case的任何代碼的case ,32 KiB表的大小是64 KiB表的一半),並且假設第一個選項具有更簡單(更易維護)的代碼; 我看不出你想要使用第二個選項的一個原因。

為了優化這段代碼,我會嘗試處理更大的代碼。 舉個簡單的例子,你可以這樣做:

    uint64_t mask[512] = { ....

    uint64_t workingdata;
    uint64_t temp;

    for (i=0; i<512; i++)
    {
        workingdata = getnextdatachunk() << 32 | getnextdatachunk();
        temp = workingdata & mask[i];
        answer[i*2] = temp;
        answer[i*2+1] = temp >> 32;
    }

如果你可以做這樣的事情,那么它可能(充其量)使性能翻倍; 但是如果你可以做“每次迭代64位,迭代次數減半”,那么你也可以使用SIMD內在函數來做“每次迭代128位,四分之一迭代”或“每次迭代256位”迭代“,並且可能使它快8倍。

當然,除此之外的步驟是緩沖足夠的源數據以使多個線程(多個CPU)有效使用(例如,以便可以有效地分攤同步成本)。 每個迭代中有4個CPU並行執行256位,每個迭代的速度為“理論上最佳情況”,比單CPU版本的每次迭代32位的原始1024次迭代快32倍。

我個人認為你的方法實際上取決於你的用例。 您有2種不同的模式:

  • 如果運行速度很重要,請將數組整體保留在內存中(考慮到數組不會變得太大而無法使用緩存)。
  • 如果代碼大小很重要,請使用您所考慮的方法或PSkocik建議的方法。

要選擇合適的代碼設計,您需要考慮很多不同的因素。 例如,如果您的代碼將在嵌入式設備上運行,我可能會采用較小的代碼大小方法。 但如果您是普通PC的代碼,我可能會選擇第一個。

暫無
暫無

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

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