13

我需要用 C 编写一个排序程序,如果可以对文件进行排序以节省磁盘空间,那就太好了。数据很有价值,所以我需要确保如果进程被中断(ctrl-c)文件没有损坏。我可以保证机器上的电源线不会被拉扯。

额外细节:文件约为 40GB,记录为 128 位,机器为 64 位,操作系统为 POSIX

有关完成此操作的任何提示或一般注意事项?

谢谢!

澄清一下:我希望用户会想要 ctrl-c 这个过程。在这种情况下,我想优雅地退出并确保数据安全。所以这个问题是关于处理中断和选择一种可以在需要时快速结束的排序算法。

跟进(2 年后):为了后代,我安装了 SIGINT 处理程序,它工作得很好。这并不能保护我免受电源故障的影响,但这是我可以处理的风险。代码位于https://code.google.com/p/pawnsbfs/source/browse/trunk/hsort.chttps://code.google.com/p/pawnsbfs/source/browse/trunk/qsort.c

4

6 回答 6

12

杰瑞是对的,如果您担心的只是 Ctrl-C,您可以一次忽略 SIGINT 一段时间。如果你想证明一般的进程死亡,你需要某种最小的日志。为了交换两个元素:

1) 在文件末尾或单独的文件中向控制结构中添加一条记录,指示要交换文件的哪两个元素,A 和 B。

2)将A复制到暂存空间,记录你已经这样做了,刷新。

3)将B复制到A上,然后在您这样做的暂存空间中记录,刷新

4) 从 B 上的暂存空间复制。

5) 删除记录。

对于所有实际目的,这是 O(1) 额外空间,因此在大多数定义下仍然算作就地。理论上,如果 n 可以任意大,则记录索引是 O(log n):实际上它是一个非常小的 log n,并且合理的硬件/运行时间将其限制在 64 以上。

在所有情况下,当我说“刷新”时,我的意思是提交“足够远”的更改。有时,您的基本刷新操作仅刷新进程内的缓冲区,但它实际上并不同步物理介质,因为它不会一直刷新缓冲区到操作系统/设备驱动程序/硬件级别。当您担心的只是进程死亡时,这就足够了,但是如果您担心突然的媒体卸载,那么您必须冲过驱动程序。如果您担心电源故障,则必须同步硬件,但事实并非如此。使用 UPS,或者如果您认为停电非常罕见,您不介意丢失数据,那很好。

启动时,检查暂存空间中是否有任何“正在进行的交换”记录。如果您找到了,请计算出您走了多远并从那里完成交换以使数据恢复到正常状态。然后重新开始你的排序。

显然,这里存在性能问题,因为您的记录写入量是以前的两倍,并且刷新/同步可能非常昂贵。在实践中,您的就地排序可能有一些复合的移动内容操作,涉及许多交换,但您可以对其进行优化以避免每个元素都触及暂存空间。您只需要确保在覆盖任何数据之前,您在某处有一份安全的副本,并记录了该副本的去向,以便让您的文件恢复到包含每个元素的一个副本的状态。

Jerry 也说对了,对于大多数实际目的而言,真正的就地排序太难且太慢了。如果您可以将原始文件大小的一些线性部分作为暂存空间,那么使用合并排序您将获得更好的时间。

根据您的说明,即使使用就地排序,您也不需要任何刷新操作。您需要内存中以相同方式工作的暂存空间,并且您的 SIGINT 处理程序可以访问以便在退出之前获得数据安全,而不是在异常退出在启动时恢复,并且您需要在信号中访问该内存 -安全方式(从技术上讲,这意味着使用 asig_atomic_t来标记已进行的更改)。即便如此,与真正的就地排序相比,使用归并排序可能会更好。

于 2010-09-07T13:24:02.753 回答
5

防止 ctrl-c 的部分非常简单:signal(SIGINT, SIG_IGN);.

就排序本身而言,合并排序通常适用于外部排序。基本思想是将尽可能多的记录读入内存,对它们进行排序,然后将它们写回磁盘。到目前为止,处理此问题的最简单方法是将每次运行写入磁盘上的单独文件。然后将它们合并在一起——将每次运行的第一条记录读取到内存中,并将其中最小的一条写入原始文件;从提供该记录的运行中读取另一条记录,然后重复直到完成。最后一个阶段是您修改原始文件的唯一时间,因此这是您真正需要确保不会出现中断等的唯一时间。

另一种可能性是使用选择排序。不好的一点是排序本身很慢。好的一点是它很容易编写它以在几乎任何东西中生存,而无需使用太多额外的空间。总体思路很简单:找到文件中最小的记录,然后将其交换到第一个位置。然后找到剩下的最小记录,并将其交换到第二个位置,依此类推,直到完成。这样做的好处是日志是微不足道的:在进行交换之前,记录要交换的两条记录的值。由于排序从第一条记录到最后一条记录,您唯一需要跟踪的另一件事是在任何给定时间已经排序了多少条记录。

于 2010-09-07T13:31:29.267 回答
5

安装一个处理程序SIGINT,只设置一个“进程应该很快退出”标志。

在您的排序中,在两条记录的每次交换后(或每 N 次交换后)检查标志。如果设置了标志,则退出。

于 2010-09-07T14:42:01.923 回答
3

使用堆排序,并在每次交换操作期间防止中断(例如块信号)。

于 2010-09-07T13:18:51.770 回答
0

备份您计划更改的任何内容。放置一个标记成功排序的标志。如果一切正常,则保留结果,否则恢复备份。

于 2010-09-07T13:20:42.107 回答
-1

假设是 64 位操作系统(您说它是 64 位机器,但仍可能运行 32 位操作系统),您可以使用 mmap 将文件映射到数组,然后在数组上使用 qsort。

为 SIGINT 添加一个处理程序以调用 msync 和 munmap 以允许应用程序响应 Ctrl-C 而不会丢失数据。

于 2010-09-07T15:15:06.377 回答