簡體   English   中英

如何優化合並排序?

[英]How to optimize merge sort?

我有兩個1 GB的文件,每個文件只包含按排序順序排列的數字。 現在我知道如何讀取文件的內容並使用合並排序算法對它們進行排序並將其輸出到另一個文件但我感興趣的是如何只使用100MB緩沖區大小(我不擔心划痕)空間)。 例如,一種方法是從兩個文件中讀取50 MB塊並對其進行排序,並且在排序時我可以讀取新元素並繼續該過程,直到我到達兩個文件的末尾(任何人都可以告訴我如何實現這個)。

聽起來你只需要合並文件中的數字,而不是對它們進行排序,因為它們已經在每個文件中排序。 合並排序merge部分是這樣的:

function merge(left,right)
    var list result
    while length(left) > 0 or length(right) > 0
        if length(left) > 0 and length(right) > 0
            if first(left) ≤ first(right)
                append first(left) to result
                left = rest(left)
            else
                append first(right) to result
                right = rest(right)
        else if length(left) > 0
            append left to result
            break             
        else if length(right) > 0
            append right to result
            break
    end while
    return result

現在,您可以從兩個緩沖區中的兩個文件中讀取前50 MB的數字,應用合並算法,然后當其中一個緩沖區已用盡(分析了所有數據)時,從所需文件中讀取另外50 MB。 沒有必要對任何東西進行排序。

您只需要一個條件來檢查其中一個緩沖區是否為空。 如果是,請從與緩沖區關聯的文件中讀取更多內容。

為什么不使用標准庫?

#include <fstream>
#include <iterator>
#include <algorithm>

int main()
{
   std::ifstream in1("in1.txt");
   std::ifstream in2("in2.txt");
   std::ofstream ut("ut.txt");
   std::istream_iterator<int> in1_it(in1);
   std::istream_iterator<int> in2_it(in2);
   std::istream_iterator<int> in_end;
   std::ostream_iterator<int> ut_it(ut, "\n");

   std::merge(in1_it, in_end, in2_it, in_end, ut_it);
}

您可能希望以合理的塊讀/寫以避免I / O開銷。 所以可能使用~30M的三個緩沖區,input1,input2和output。

繼續前進,直到其中一個輸入緩沖區為空或輸出緩沖區已滿,然后讀/寫以重新填充/清空空/滿緩沖區。

這樣你就可以從磁盤寫入/讀取大塊數據。

除此之外,在進行排序時需要異步I / O來讀/寫數據。 但這可能是矯枉過正的。

由於您只進行合並,而不是完整的排序,它只是基本的合並循環。 純順序I / O. 無需擔心緩沖區。 想象一件夾克上的拉鏈。 就這么簡單。 (注意:如果文件中的數字是二進制格式,它可能會快得多。不僅文件會更小,而且程序將受I / O限制,而且數字將非常准確。)

double GetNumberFromFile(FILE file){
  if (feof(file)){
    return BIGBIGNUMBER;
  }
  else {
    return ReadADouble(file);
  }
}

double A = GetNumberFromFile(AFILE);
double B = GetNumberFromFile(BFILE);
while (A < BIGBIGNUMBER && B < BIGBIGNUMBER){
  if (A < B){
    write A;
    A = GetNumberFromFile(AFILE);
  }
  else if (B < A){
    write B;
    B = GetNumberFromFile(BFILE);
  }
  else {
    write A;
    write B; // or not, if you want to eliminate duplicates
    A = GetNumberFromFile(AFILE);
    B = GetNumberFromFile(BFILE);
  }
}
while (A < BIGBIGNUMBER){
    write A;
    A = GetNumberFromFile(AFILE);
}
while (B < BIGBIGNUMBER){
    write B;
    B = GetNumberFromFile(BFILE);
}

回答您的問題,考慮一個更簡單的問題,將一個文件復制到另一個文件。 您只進行順序I / O,文件系統非常擅長。 您編寫了一個簡單的循環來從文件中讀取像byte或int這樣的小單元,並將其寫入另一個。 一旦你嘗試讀取一個字節,系統就會分配一個漂亮的大緩沖區,將一大塊文件刷入緩沖區,然后將這個字節從緩沖區中提取出來。 它一直這樣做,直到你需要另一個緩沖區,當它無形地為你創造另一個緩沖區時。 你正在編寫的文件也會發生同樣的事情。 現在CPU非常快,所以它可以迭代輸入字節,將它們復制到輸出,只需要讀取或寫入緩沖區所需的時間的一小部分,因為讀取或寫入不能比外部硬件。 更大緩沖區有用的唯一原因是讀/寫時間的一部分是所謂的“延遲”,基本上是將磁頭移動到所需磁道所需的時間,並等待所需的扇區出現。 大多數文件系統將文件分解為分散在磁盤周圍的塊,因此無論如何頭部都在跳躍。 你可以聽到它。

復制和像你這樣的合並算法之間的唯一區別是它讀取兩個文件,而不是一個。 無論哪種方式,基本時間序列是一系列緩沖區讀取和寫入,散布着少量的CPU動作。 (這是可以做到的重疊 I / O,使CPU動作發生 I / O發生的,所以基本上沒有延遲之間緩沖區的讀取和寫入,但它是一個更大的交易時的CPU是1000倍慢。 )

當然,如果您可以對其進行排列,使得正在讀取和寫入的文件都在不同的物理磁盤驅動器上,並且驅動器不會碎片太多,那么可以最大限度地減少磁頭運動的數量,並且更大的緩沖區可能會有所幫助。 但基本上,通過一個簡單的程序,您幾乎可以期望簡單的代碼能夠像磁盤移動數據一樣快,而巨型緩沖區可能有所幫助,但並不多。

基准。 讀取值和塊讀取。 感到不同! =)

暫無
暫無

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

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