【问题标题】:What is a good algorithm for compacting records in a blocked file?什么是压缩被阻止文件中记录的好算法?
【发布时间】:2010-09-12 21:56:06
【问题描述】:

假设您有一个由一堆固定大小的块组成的大文件。这些块中的每一个都包含一些可变大小的记录。每个记录必须完全适合单个块,然后根据定义,此类记录永远不会大于完整块。随着时间的推移,记录在这些块中添加和删除,因为记录来自这个“数据库”。

在某些时候,尤其是在可能将许多记录添加到数据库并删除了一些记录之后 - 许多块最终可能只是部分填充。

有什么好的算法可以打乱这个数据库中的记录,通过更好地填充部分填充的块来压缩文件末尾不必要的块?

算法要求:

  • 必须在原始文件的位置进行压缩,而不是临时将文件从其起始大小最多扩展几个块
  • 算法不应不必要地干扰已经基本满的块
  • 必须一次从文件读取或写入整个块,并且应该假设写入操作相对昂贵
  • 如果记录从一个块移动到另一个块,则必须将它们添加到新位置,然后再从其起始位置删除,以便在操作中断时不会因“失败”压缩而丢失记录。 (假设可以在恢复时检测到此类记录的临时重复)。
  • 可用于此操作的内存可能只有几个块的数量级,这仅占整个文件大小的很小百分比
  • 假设记录大约为 10 字节到 1K 字节,平均大小可能为 100 字节。固定大小的块大约为 4K 或 8K,文件大约为 1000 个块。

【问题讨论】:

    标签: algorithm language-agnostic np-complete defragmentation knapsack-problem


    【解决方案1】:

    如果这些记录没有排序,我只需用从最后一个块中提取的记录填充前面的块。这将最大限度地减少数据的移动,相当简单,并且可以很好地紧密打包数据。

    例如:

    // records should be sorted by size in memory (probably in a balanced BST)
    records = read last N blocks on disk;
    
    foreach (block in blocks) // read from disk into memory
    {
        if (block.hasBeenReadFrom())
        {
            // we read from this into records already
            // all remaining records are already in memory
    
            writeAllToNewBlocks(records);
    
            // this will leave some empty blocks on the disk that can either
            // be eliminated programmatically or left alone and filled during
            // normal operation
    
            foreach (record in records)
            {
                record.eraseFromOriginalLocation();
            }
    
            break;
        }
    
        while(!block.full())
        {
            moveRecords = new Array; // list of records we've moved
    
            size = block.availableSpace();
            record = records.extractBestFit(size);
            if (record == null)
            {
                break;
            }
    
            moveRecords.add(record);
            block.add(record);
    
            if (records.gettingLow())
            {
                records.readMoreFromDisk();
            }
        }
    
        if(moveRecords.size() > 0)
        {
            block.writeBackToDisk();
            foreach (record in moveRecords)
            {
                record.eraseFromOriginalLocation();
            }
        }
    }
    

    更新:我忽略了保持 no-blocks-only-in-memory 规则。我已经更新了伪代码来解决这个问题。还修复了我的循环条件中的一个故障。

    【讨论】:

    • 这种方法基本上是我们开始的地方,但事实证明,记录大小的不规则性通常会留下次优压缩块。有了一些搜索更多的意愿,可以找到更好的拟合,但随后它变成了 NP-hard,现在正在寻找更多的启发式..
    • 不客气。我希望一次调整您保留在内存中的块数会有所帮助。如果你在内存中保留十个块的记录,我希望你通常可以很好地填充大部分部分块。
    • 你也可以只在内存中保存较小的记录,然后压缩剩余的大记录(这样你最终不会随着时间的推移用大记录填满整个内存)。
    【解决方案2】:

    这听起来像是bin packing problem 的变体,但是您已经有了想要改进的劣质分配。因此,我建议查看成功解决装箱问题的各种方法。

    首先,您可能希望通过定义您认为“足够满”(块足够满以至于您不想触摸它)和“太空”(其中一个块有太多的空白空间,以至于它必须添加更多的记录)。然后,您可以将所有块分类为足够满、太空或部分满(那些既不够满也不够空)。然后,您将问题重新定义为如何通过创建尽可能多的足够满的块同时最小化部分满的块的数量来消除所有太空的块。

    您还需要弄清楚更重要的事情:将记录放入尽可能少的块中,或者将它们充分打包,但读取和写入的块数量最少。

    我的方法是对所有块进行初始传递,将它们全部分类为上面定义的三个类之一。对于每个块,您还希望跟踪其中的可用空间,对于太空的块,您将需要一个所有记录及其大小的列表。然后,从太空块中的最大记录开始,将它们移动到部分满的块中。如果您想最小化读取和写入,请将它们移动到您当前在内存中的任何块中。如果要尽量减少浪费的空间,请找到仍然保留记录的具有最少空白空间的块,必要时读入该块。如果没有块将保存记录,则创建一个新块。如果内存中的块达到“足够满”阈值,则将其写出。重复直到部分填充块中的所有记录都已放置。

    我跳过了很多细节,但这应该会给你一些想法。请注意,装箱问题是NP-hard,这意味着对于实际应用,您需要决定什么对您来说最重要,因此您可以选择一种在合理时间内为您提供近似最佳解决方案的方法。

    【讨论】:

    • 感谢您指出装箱问题比较。这很有帮助。解决方案的一个棘手部分是在第一次扫描中要扫描的大量记录并保留统计信息是令人望而却步的。此外,由于写入成本很高,从某种意义上说,您只有一两次机会重写给定块
    • 理想情况下,我正在考虑使用一些通行证来针对某些块和记录进行合并。即:找到每次通过的最低挂果以收获并在剩余时间过多或没有显着优化时停止。再次感谢!
    【解决方案3】:

    这是一种您可能可以利用的算法,尽管您在固定大小的块中的记录可能需要更多的工作。

    Heap Defragmentation in Bounded Time

    【讨论】:

    • 谢谢。然而,正如你所提到的,那篇论文没有解决问题的两个动态。 1) 固定大小块的 bin 特性和 (2) 记录大小的线性逐字节可变性。即:堆中“记录”的 2^N 特性是其解决方案的关键要素。
    【解决方案4】:

    在线(一次进行碎片整理)有界空间(内存要求)装箱算法的修改可能在这里起作用。

    参见 Coffman 等人的 "Bin Packing Approximation Algorithms: Combinatorial Analysis"

    【讨论】:

    • 谢谢!供参考装箱问题比较和本文对一些不同方法的方法的分析。
    猜你喜欢
    • 2015-09-25
    • 2012-03-08
    • 2023-04-02
    • 1970-01-01
    • 2020-02-25
    • 1970-01-01
    • 1970-01-01
    • 2011-04-11
    • 1970-01-01
    相关资源
    最近更新 更多