检查基于磁盘的合并与辅助存储的性能
Checking the performance for disk-based merge sort with auxiliary storage
我尝试与辅助存储实现基于磁盘的合并排序。实现如下。
fd-数据集的文件描述符将进行排序
fd2-辅助存储的文件描述符
#define LENGTH 100
#define SEARCH_BEGIN 4
int merge_sort_d(int fd, int fd2, int s, int e) {
int i, m;
int l, r;
char lv[LENGTH], rv[LENGTH];
char buf[LENGTH];
if (s >= e) return 1;
m = (s + e) / 2;
merge_sort_d(fd, fd2, s, m);
merge_sort_d(fd, fd2, m+1, e);
l = s;
r = m+1;
memset(lv, 0, LENGTH);
memset(rv, 0, LENGTH);
lseek(fd2, 0, SEEK_SET);
while (l <= m && r <= e) {
lseek(fd, 1LL*SEARCH_BEGIN + 1LL*l*LENGTH, SEEK_SET);
read(fd, (void *)lv, LENGTH);
lseek(fd, 1LL*SEARCH_BEGIN + 1LL*r*LENGTH, SEEK_SET);
read(fd, (void *)rv, LENGTH);
if (strncmp(lv, rv, LENGTH) < 0) {
write(fd2, (void *)lv, LENGTH);
++l;
} else {
write(fd2, (void *)rv, LENGTH);
++r;
}
}
for (; l <= m; ++l) {
lseek(fd, 1LL*SEARCH_BEGIN + 1LL*l*LENGTH, SEEK_SET);
read(fd, (void *)lv, LENGTH);
write(fd2, (void *)lv, LENGTH);
}
for (; r <= e; ++r) {
lseek(fd, 1LL*SEARCH_BEGIN + 1LL*r*LENGTH, SEEK_SET);
read(fd, (void *)rv, LENGTH);
write(fd2, (void *)rv, LENGTH);
}
lseek(fd, 1LL*SEARCH_BEGIN + 1LL*s*LENGTH, SEEK_SET);
lseek(fd2, 0, SEEK_SET);
memset(buf, 0, LENGTH);
for (i=s; i<=e; ++i) {
read(fd2, (void *)buf, LENGTH);
write(fd, (void *)buf, LENGTH);
}
return 1;
}
实现基于磁盘的合并排序后,我已经测试了一些小型案例以检查其是否正确运行。在很小的情况下,它看起来足够快,但是在使用20克超过20克的大数据集运行时(最终尺寸超过500克)。它需要2个小时,我将它确实在O(nlogn)中运行。当然,基于磁盘的算法和数据结构也存在一些其他时间。
我很好奇它确实在o(nlogn)中运行。
标准内存中合并排序log(n)通过数据传递,每次通过的较大列表将较大的列表融合在一起。在第一个通过中,您合并了包含一个项目的列表。接下来,它的列表每个有两个项目,然后是四个等。使用此方法,您可以使日志(n)通过数据,然后在每次通过时查看n个项目。因此o(n log n)复杂性。
该方法对于内存中非常有效,因为访问项目的成本不是很高。但是,对于基于磁盘的类型而言,它变得非常昂贵,因为访问每个项目的成本非常高。基本上,您正在阅读和编写整个文件日志(n)次。如果您有20 GB的100字节记录,则您会在文件上谈论25个或更多通行证。因此,您的排序时间将由读写文件25次读写时间主导。
一种外部类型是截然不同的动物。这个想法是尽可能减少磁盘I/O。您可以在两次传球中进行。在第一个通过中,您可以将尽可能多的数据读取到内存中,并使用QuickSort,Merge Sort或其他内存排序算法对其进行排序,并将该块写入磁盘。然后,您将文件的下一个块读为内存,对其进行排序并写入磁盘。
完成第一张通行证后,您会在磁盘上进行一些分类的块。如果您有一个20 GB的文件,并且计算机上有4 GB的内存,那么您将有5个块,每个块大约4 GB。(请注意,您实际上可能有五个略小于4 GB的块,而第六个,很小,块)。调用块的数量k
。
请注意,第一次通过后,您已经阅读并写了每次记录。
在第二次通过中,您合并了多个块。这是用一堆k
项目完成的。这个想法是,您可以用每个块的第一项初始化堆。您选择其中最小的k
项目(位于堆的顶部),然后将其写入输出文件。然后,您从包含您刚刚删除的项目的块中获取下一个项目,然后将其添加到堆中。您重复此过程,直到您清空所有块。
第一个通过是o(n log n)。实际上,它是k*((n/k)log(n/k)),它可用于n log n。第二次通过是O(n log K)。
这里重要的部分是,在第二张通过中,您再次阅读并写了每个项目。您将磁盘I/O从读取和编写每个项目日志(n)到阅读和编写每个项目两次。这将比您写的代码快得多。
也必须注意,这两种算法确实被认为是O(n log n)。I/O常数是杀手。第二种算法实际上可以进行更多的计算,但是节省了很多磁盘I/O时间,以至于它比理论上更快的算法。
Wikipedia上的外部排序文章给出了不错的解释,而Geeksforgeeks文章在C 中给出了一个工作示例。
算法是o(n logn),但性能不仅仅是记录的数量。
不断的寻求和文件访问非常慢。您应该在一个片段中阅读多个记录,因为阅读16个记录(或200)的时间与阅读的时间没有太大不同。
在您的主循环中,您已经拥有数据时正在阅读数据。仅在新记录中读取(对应于l
或r
的哪个已更改)会有所帮助,尽管上面提到的多个记录读数会好得多。
您的最后一个循环,如果您使用大块(许多记录),而不是一次复制它们,则将数据从fd2
复制到fd
会更快。同样的适用于中间两个循环,在该循环中您复制一个侧面的残留物,并且在那里r
环是多余的,因为您立即在最后一个循环中复制相同的数据。
有关在磁盘上对大文件进行排序的所有血腥详细信息,请参见Knuth's Art of Computer编程的第5章(在第3卷中)。(第二版中的第5.4节涉及外部排序。)
- 删除一个线程上有数百万个字符串的大型哈希映射会影响另一个线程的性能
- OpenMP阵列性能较差
- 递归列出所有目录中的C++与Python与Ruby的性能
- 使用C++程序合并排序没有得到正确的输出
- 大小相等但成员数量不同的结构之间的性能差异
- 为什么constexpr的性能比正常表达式差
- 在类中使用随机生成器时出现性能问题
- 在main()之外初始化std::vector会导致性能下降(多线程)
- 用于合并排序的合并函数
- 在声明中合并两个常量"std::set"(不是在运行时)
- 如何将一个数组值合并为一个整数c++
- 如何将不同的可执行文件合并到一个窗口框架中进行编码?像浏览器一样
- 将向量的 N 段合并到位C++
- 在C++中合并两个库
- 海湾合作委员会 ARM 性能下降
- GCC 和 Clang 代码性能的巨大差异
- 在容量内调整矢量大小时的性能影响
- 以迭代方式合并标准::unordered_map
- 检查基于磁盘的合并与辅助存储的性能
- 源代码合并真的能提高C或c++程序的性能吗?