在並發空間中,諸如協調、非同步行為、響應性和延展性等問題會成為關注的焦點。這些都是開發人員在設計應用程式時必須考慮的一些比較深奧的主題。但是,也許是由於缺乏經驗或缺乏合適的效能工具,一些同樣重要的主題卻常常被忽略。高效能演算法就是其中一例。
在企業層級,開發人員會仔細斟酌Distributed File System和緩衝、群集、訊息佇列和資料庫等問題。但是如果最核心的演算法和資料結構效率低下,考慮這些又有什麼用呢?
演算法效率並不像您認為的那樣簡單。單一處理器上設計良好的演算法通常可以勝過多處理器上的低效實現。但是現在,當多處理器已經可用時,設計良好的演算法還要顯示出可衡量的延展性和效率。由於會使問題變得更為複雜,因此針對單一處理器進行最佳化的演算法通常很難並存執行,而效率略低的演算法通常可以在多處理器環境中發揮更好的效能。
為了說明這一點,我將使用 Visual C++ 展示一個非常簡單的演算法的開發過程,但實際上它不簡單,即使乍看起來像是如此。下面是我們需要實現的一些內容:
void MakeGrayscale(BYTE* bitmap,
const int width,
const int height,
const int stride);
位元影像參數,指向一幅 32 位/像素的映像。再次重申,這是本文的重點。跨距的絕對值,指示記憶體中一行像素到下一行像素的位元組數。每行的末尾可能存在填充內容。跨距的符號,指示這些行在記憶體中是自上而下(正跨距)還是自下而上(負跨距)。
讓我們首先確定著手點。我們可以使用下面的結構來表示記憶體中的像素:
typedef unsigned char BYTE; // from windef.h
struct Pixel
{
BYTE Blue;
BYTE Green;
BYTE Red;
BYTE Alpha;
};
通過快速的 Web 搜尋,我們確定對於一個給定顏色的像素可通過混合 30% 的紅色、59% 的綠色和 11% 的藍色來獲得合理的灰階值。下面是將一個像素轉換為灰階級的簡單函數:
void MakeGrayscale(Pixel& pixel)
{
const BYTE scale = static_cast<BYTE>(0.30 * pixel.Red +
0.59 * pixel.Green +
0.11 * pixel.Blue);
pixel.Red = scale;
pixel.Green = scale;
pixel.Blue = scale;
}
要計算位元影像內某個特定像素的位元組位移,可計算其水平位置與像素大小的乘積以及其垂直位置與跨距的乘積,然後將這些值相加:
offset = x * sizeof(Pixel) + y * stride
那麼,該如何? MakeGrayscale 函數呢?如果您跳過這部分而不做其他考慮,您可能會寫出類似於圖 1 所示的演算法行。乍一看這似乎是合理的,採用這樣的方法,似乎可以很好地對小位元影像進行處理。但對於較大的位元影像會怎樣呢?對 20,000 * 20,000 像素的位元影像又會如何?
圖 1 低效的單線程演算法
void MakeGrayscale(BYTE* bitmap,
const int width,
const int height,
const int stride)
{
for (int x = 0; x < width; ++x)
for (int y = 0; y < height; ++y)
{
const int offset = x * sizeof(Pixel) + y * stride;
Pixel& pixel = *reinterpret_cast<Pixel*>(bitmap + offset);
MakeGrayscale(pixel);
}
}
我恰好有一個配有四核 Intel Xeon X3210 處理器的 Dell PowerEdge。這種機器的時脈速度為 2.13GHz,前端匯流排為 1066MHz,二級緩衝為 8MB,此外還具有其他各種超炫的功能。不可否認,它並不是最新的 Intel Xeon 處理器,但它確實值得稱道。它由 64 位元版本的 Windows Server 2008 驅動,非常適合做效能測試。
有了這些支援,我對寬度為 20,000 像素且高度也為 20,000 像素的位元影像運行了圖 1 所示的演算法。平均來說,它在 46 秒鐘內進行了 10 餘次迭代。不可否認,這張位元影像相當大,約佔 1.5GB 的空間。但是這真的是問題所在嗎?我的伺服器有 4GB 記憶體,因此不需要分頁磁碟。但圖 2 顯示了大家都非常熟悉的處理器使用率視圖。
圖 2 低效單線程演算法的處理器使用率