如何优化合并排序?

时间:2010-09-28 15:09:32

标签: c++ optimization memory mergesort

我有两个1 GB的文件,每个文件只包含按排序顺序排列的数字。现在我知道如何读取文件的内容并使用合并排序算法对它们进行排序并将其输出到另一个文件但我感兴趣的是如何只使用100MB缓冲区大小(我不担心划痕)空间)。例如,一种方法是从两个文件中读取50 MB块并对其进行排序,并且在排序时我可以读取新元素并继续该过程,直到我到达两个文件的末尾(任何人都可以告诉我如何实现这一点)。

5 个答案:

答案 0 :(得分:6)

听起来你只需合并文件中的数字,而不是对它们进行排序,因为它们已经在每个文件中排序。 merge sortmerge部分就是:

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 。没有必要对任何事情进行排序。

您只需要一个条件来检查其中一个缓冲区是否为空。如果是,请从缓冲区所关联的文件中读取更多内容。

答案 1 :(得分:4)

为什么不使用标准库?

#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);
}

答案 2 :(得分:3)

您可能希望以合理的块读/写以避免I / O开销。 所以可能使用~30M的三个缓冲区,input1,input2和output。

继续前进,直到其中一个输入缓冲区为空或输出缓冲区已满,然后读/写以重新填充/清空空/满缓冲区。

这样你就可以从磁盘写入/读取大块数据。

除此之外,在进行排序时需要异步I / O来读/写数据。但那可能有点过头了。

答案 3 :(得分:0)

由于您只是进行合并,而不是完整的排序,它只是基本的合并循环。纯顺序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,以便在发生I / O时发生的CPU操作,因此基本上没有缓冲区读写之间的延迟,但当CPU慢1000倍时,这是一个更大的问题。)

当然,如果您可以对其进行排列,以便正在读取和写入的文件都在不同的物理磁盘驱动器上,并且驱动器没有碎片太多,那么可以最大限度地减少磁头运动的数量,并且更大的缓冲区可能会有所帮助。但基本上,通过一个简单的程序,你几乎可以期望简单的代码可以像磁盘移动数据一样快,而巨型缓冲区可能有所帮助,但并不多。

答案 4 :(得分:0)

基准。读取值和块读取。感到不同! =)