33

因此,出于某种奇怪的原因,我最终得到了一个 100GB 的未排序日志文件(实际上它是部分排序的),而我尝试应用的算法需要排序的数据。日志文件中的一行看起来像这样

data <date> data data more data

我可以在我的工作站上访问 C# 4.0 和大约 4 GB 的 RAM。我想某种类型的合并在这里是最好的,但我自己没有实现这些算法 - 我想问是否有某种捷径可以走。

顺便说一句,用 解析日期字符串DateTime.Parse()非常慢并且占用大量 CPU 时间 - chugging速率仅为 10 MB/秒。有比以下更快的方法吗?

    public static DateTime Parse(string data)
    {            
        int year, month, day;

        int.TryParse(data.Substring(0, 4), out year);
        int.TryParse(data.Substring(5, 2), out month);
        int.TryParse(data.Substring(8, 2), out day);

        return new DateTime(year, month, day);
    }

我写这个是为了加快速度DateTime.Parse(),它实际上运行良好,但仍然需要大量的循环。

请注意,对于当前的日志文件,我也对小时、分钟和秒感兴趣。我知道我可以为 DateTime.Parse() 提供格式,但这似乎并没有加快速度。

我正在寻找正确方向的推动力,在此先感谢。

编辑:有些人建议我使用字符串比较来比较日期。这适用于排序阶段,但我确实需要解析算法的日期。我仍然不知道如何在 4GB 的可用内存上对 100GB 文件进行排序,而无需手动进行。

编辑 2 :好吧,感谢我使用windows sort的几个建议,我发现Linux 有一个类似的工具。基本上,您调用 sort ,它会为您解决所有问题。正如我们所说,它正在做某事,我希望它会尽快完成。我正在使用的命令是

sort -k 2b 2008.log > 2008.sorted.log

-k 指定我要在第二行排序,这是通常YYYY-MM-DD hh:mm:ss.msek格式的日期时间字符串。我必须承认手册页没有解释所有选项,但我通过运行找到了很多示例info coreutils 'sort invocation'

我会报告结果和时间。这部分日志大约为 27GB。我正在考虑分别对 2009 和 2010 进行排序,然后使用 sort -m 选项将结果合并到一个文件中。

编辑 3好吧,检查iotop表明它正在读取数据文件的小块,然后疯狂地做一些事情来处理它们。这个过程似乎很慢。=(

sort不使用任何内存,只使用一个核心。当它从驱动器读取数据时,它不会处理任何东西。难道我做错了什么?

编辑 4三个小时后,它仍然在做同样的事情。现在我正处于我想尝试使用函数参数的那个阶段,但我投入了三个小时......我将在大约 4 小时内中止,并尝试将其用于具有更智能内存的夜间计算和空间参数...

编辑 5在我回家之前,我使用以下命令重新启动了该过程:

sort -k 2b --buffer-size=60% -T ~/temp/ -T "/media/My Passport" 2010.log -o 2010.sorted.log

今天早上它返回了这个:

sort: write failed: /media/My Passport/sortQAUKdT: File too large

哇!我想我会添加尽可能多的硬盘来加快这个过程。显然,添加 USB 驱动器是有史以来最糟糕的主意。目前我什至无法判断它是否与 FAT/NTFS 或类似的有关,因为 fdisk 告诉我 USB 驱动器是一个“错误的设备”......不开玩笑。稍后我会尝试再试一次,现在让我们将这个项目放入可能失败的堆中。

最后通知 这一次它工作了,使用与上面相同的命令,但没有有问题的外部硬盘驱动器。谢谢大家的帮助!

基准测试

在同一个 SATA 控制器上使用 2 个工作站级(至少 70mb/sec 读/写 IO)硬盘,我花了 162 分钟来整理一个 30GB 的日志文件。今晚我需要对另一个 52 GB 文件进行排序,我会发布它是如何进行的。

4

16 回答 16

18

像这样的代码完全取决于从磁盘上获取数据的速度。该文件永远无法放入文件系统缓存中,因此您总是在磁盘上等待提供数据。您以 10 MB/秒的速度做得相当好,优化代码永远不会产生明显的效果。

获得更快的磁盘。碎片整理你有一个中间步骤。

于 2010-09-25T18:42:18.247 回答
16

如果字符串排序对您有用,那么只需使用 Windows SORT 命令。对文件进行排序并完成它。它会很高兴地对您的 100GB 文件进行排序,而且使用起来很简单。

如果您需要过滤和转换文件,特别是日期字段,那么我只需编写一个小型转换程序,将数据字段转换为填充为 0 的整数(如自 1970 年以来的秒数,或任何您喜欢的),并且重写记录。然后,您可以通过管道 (|) 将输出输入到 sort 命令,然后您就有了一个最终的、已排序的文件,该文件更容易被您的实用程序解析。

我认为您所犯的错误只是试图一口气完成所有操作。100GB 的数据很多,复制需要一些时间,但不会花那么长时间。由于您必须对其进行排序,因此您必须在某些时候处理文件的副本(即,您需要机器上的可用空间来处理两个副本),即使使用外部排序例程(如合并排序)也是如此.

编写一个简单的重新格式化程序并将其输入进行排序将节省您多次浏览文件,并节省磁盘空间,因为您不可避免地只需要两个副本。

我还将调整格式化程序以仅提取我真正感兴趣的字段,并在此时进行所有“繁重”的解析,以便您最终得到的基本上是一个格式化文件,您的报告例程可以轻松处理. 这样您就可以在以后可能多次运行报表时节省时间。

如果可能,使用简单的 CSV 或更好的固定长度文件格式进行输出。

如果您选择使用整数,请确保您的日期信息具有相同长度的所有字段。否则 SORT 实用程序将无法正确排序它们(你最终得到 1 10 2 3 而不是 1 2 3 10。你最好有 01 02 03 10。)。

编辑 -

让我们从不同的技巧来处理它。

最大的问题是“你需要所有这些数据吗”。这与先前关于首先进行重解析的建议有关。显然,您可以减少的初始设置越多越好。例如,仅删除 10% 的数据就是 10GB。

我喜欢将其视为经验法则,尤其是在处理大量数据时:“如果您有 100 万个数据,那么每节省一毫秒,就会减少 20 分钟。”

通常,我们真的不会以毫秒为单位来考虑我们的工作,更多的是“就座”,“感觉更快”。但是 1 毫秒 == 20 分钟/百万是掌握您正在处理的数据量以及应该/可能需要多长时间的好方法。

对于你的情况,100GB 的数据。对于每条记录 100 字节的赃物,您将占用 10 亿行。每毫秒 20,000 分钟。-- 5 1/2 小时。gulp(这是一个经验法则,如果你做数学,它并不能完全解决这个问题。)

因此,如果可能的话,您可以理解减少原始数据的愿望。

这就是我推迟到 Windows SORT 命令的原因之一。这是一个基本过程,但会受到细微差别的影响,并且可以使用一些优化。编写 SORT 的人有时间和机会在许多方面使其“最佳”。他们有没有,我不能说。但这是一个公平的假设,他们会在这个过程中投入更多的时间和注意力,以使他们的 SORT 尽可能实用,而不是你在紧迫的期限内。

有用于大型数据集的 3rd 方排序实用程序,这可能(理想情况下)更适合这种情况。但是,这些对你来说是不可用的(你可以得到它们,但我不认为你想马上冲出去得到一些其他的实用程序)。所以,SORT 是我们目前最好的猜测。

也就是说,减少数据集将比任何排序实用程序获得更多收益。

你真的需要多少细节?您真正跟踪了多少信息?例如,如果是网络统计数据,您的网站上可能有 1000 个页面。但即使有一年的小时数,365 * 24 * 1000,也只有 870 万“桶”信息——与 1B 相去甚远。

那么,是否有任何不需要排序的预处理?将信息概括为更粗略的粒度?您可以在不排序的情况下做到这一点,只需使用基于内存的哈希映射。即使您没有“足够的内存”一次性处理所有 100GB 数据,您也可能有足够的块(5 块、10 块)来处理,并写出中间结果。

您也可能有更好的运气来拆分数据。进入每月或每周的文件块。也许这并不容易,因为数据“大部分”是排序的。但是,在这种情况下,如果是按日期,违规者(即不正确的数据)很可能会聚集在文件中,而“不正确”的东西只是混在时间段的障碍上(就像白天的转换一样,也许你有像晚上 11:58、晚上 11:59、早上 00:00、早上 00:01、晚上 11:58、晚上 00:02 这样的行)。您也可以利用该启发式方法。

目标是,如果您可以在某种程度上确定性地确定无序的子集,并将文件分解为“有序数据”和“无序数据”的块,那么您的排序任务可能会小得多。对乱序的几行进行排序,然后你就会遇到合并问题(比排序问题简单得多)。

所以,这些是你可以用来解决问题的策略。总结显然是最好的,因为在任何可测量的情况下减少这种数据负载的任何东西都可能值得麻烦。当然,这一切都归结为您真正想要从数据中得到什么,显然报告将推动这一点。这也是关于“未成熟优化”的一个很好的观点。如果他们没有报告它,请不要处理它:)。

于 2010-09-25T19:33:00.883 回答
13

简短的回答 - 将数据加载到关系数据库(例如 Sql Express)中,创建索引,并使用基于游标的解决方案(例如 DataReader)读取每条记录并将其写入磁盘。

于 2010-09-25T19:53:04.423 回答
9

你为什么不试试这个来自微软的相对不为人知的工具logparser。它基本上允许您对 CSV 文件(或任何其他格式的文本文件)进行 SQL 查询。

省去了将其输入数据库、进行排序并再次将其重新输出的麻烦

于 2010-09-25T21:18:49.353 回答
8

只是为了回答有关对不适合内存的长文件进行排序的问题-您需要使用一些外部排序算法,例如合并排序。流程大致如下:

  • 将输入分成适合内存的几个部分,并且可以使用标准的内存排序算法进行排序(例如 100 MB 或更大 - 您需要一次在内存中保留约 4 个部分)。对所有部分进行排序并将它们写回磁盘。

  • 从磁盘读取两个部分(它们都已排序)并将它们合并,这可以通过同时迭代两个输入来完成。将合并后的数据集写入磁盘中的另一个位置。请注意,您不需要将整个部分读入内存 - 只需在进行时以块的形式读取/写入即可。

  • 重复合并零件,直到您只有一个零件(将使用原始输入数据集中的所有数据对文件进行排序)。

您提到数据已经部分排序,因此最好选择一些在这种情况下有效的内存排序算法(在第一阶段)。您可以在这个问题中看到一些建议(尽管我不确定对于非常大的数据集的答案是否相同 - 这取决于输入部分排序程度)。

于 2010-09-25T19:09:51.140 回答
4

对于排序,您可以实现基于文件的桶排序:

  1. 打开输入文件
  2. 逐行读取文件
  3. 从行获取日期作为字符串
  4. 将行附加到文件<date>.log

结果将是每天单独的日志文件,或每小时单独的日志文件。选择以便您获得可以轻松排序的大小的文件。

剩下的任务是对创建的文件进行排序,并可能再次合并文件。

于 2010-09-27T18:49:25.917 回答
4

优化日期解析的最佳方法是根本不解析它们。

由于日期采用 ISO 8601 格式,您可以将它们作为字符串进行比较。根本不需要解析。

关于排序,您应该能够有效地利用它是部分排序的事实。一种方法是读取文件并写入按时间范围划分的单独文件,例如每天或每小时。如果您使每个文件足够小,则可以将它们读入内存并对其进行排序,然后合并所有文件。

另一种方法可能是读取文件并将按顺序写入的记录写入一个文件,将其他记录写入另一个文件。对第二个文件进行排序(如果它很大,可能会递归地使用此过程)并将两个文件压缩在一起。即修改的归并排序。

于 2010-09-25T18:50:31.177 回答
3

我确实需要解析算法的日期。

在 *NIX 上,我通常会首先将日期转换为简单的、适合文本比较的东西,并使其成为字符串中的第一个单词。创建日期/时间对象还为时过早。我通常的日期演示文稿是YYYYMMDD-hhmmss.millis。使所有文件都具有相同的日期格式。

我仍然不知道如何在 4GB 的可用内存上对 100GB 文件进行排序,而无需手动进行。

正如您已经知道的那样,合并排序是唯一的选择。

所以对我来说,任务分为以下步骤:

  1. 愚蠢的转换,使日期可排序。复杂性:顺序读/写 100GB。

  2. 将数据拆分为可用大小的块,例如 1GB,并在将每个块写入磁盘之前使用普通快速排序对其进行排序。复杂度:顺序读/写100GB;快速排序的内存。

  3. 将小文件合并排序为一个大文件。可以使用一个程序逐步完成,该程序需要两个文件并将它们合并到一个新文件中。复杂性:顺序读/写 100GB log(N) 次(其中 N 是文件数)。硬盘空间要求:2*100GB(最后将 2 x 50GB 文件合并为单个 100GB 文件)。

  4. 自动化上一步的程序:选择两个(例如最小的)文件,启动程序将它们排序合并到一个新文件中,删除两个原始文件。重复直到文件数大于 1。

  5. (可选)将 100GB 的已排序文件拆分为较小的可管理大小的块。毕竟你要和他们做点什么。按顺序编号或将第一个和最后一个时间戳放入文件名中。

一般概念:不要试图找到快速完成的方法,无论如何管道100GB需要时间;计划一个每一步的程序,以便在你不注意的情况下整夜运行。

在 Linux 上,这一切都可以使用 shell/sort/awk/Perl,而且我认为用任何其他编程语言编写它们都不是问题。这可能是 4 个程序 - 但所有这些程序的编码都相当简单。

于 2010-09-25T19:37:36.217 回答
3

假设您的日志文件只有 1-2% 的行乱序,您可以单次遍历完整的日志,输出两个文件:一个文件是有序的,另一个文件包含 1-2% 的行那是不正常的。然后对内存中的乱序行进行排序,并对以前乱序的行与有序行进行一次合并。这将比完整的归并排序要快得多,后者会做更多的传球。

假设您的日志文件中的行不超过 N 行,您可以通过排序队列 N 行深的单次遍历日志。每当您遇到乱序的日志行时,只需将其插入队列中的适当位置即可。由于这只需要一次通过日志,因此它将尽可能快。

于 2010-09-26T17:19:23.773 回答
2

先发制人的评论:我的回答只解决解析日期时间值的子问题。

DateTime.Parse包含对所有可能的日期格式的检查。如果你有一个修复格式,你可以很好地优化解析。一个简单的优化是直接转换字符:

class DateParserYyyyMmDd
{
    static void Main(string[] args)
    {
        string data = "2010-04-22";

        DateTime date = Parse(data);
    }

    struct Date
    {
        public int year;
        public int month;
        public int day;
    }

    static Date MyDate;

    static DateTime Parse2(string data)
    {
        MyDate.year = (data[0] - '0') * 1000 + (data[1] - '0') * 100 
            + (data[2] - '0') * 10 + (data[3] - '0');
        MyDate.month = (data[5] - '0') * 10 + (data[6] - '0');
        MyDate.day = (data[8] - '0') * 10 + (data[9] - '0');

        return new DateTime(MyDate.year, MyDate.month, MyDate.day);
    }
}
于 2010-09-27T18:33:10.457 回答
2

实际上,我对日期转换没有太多想法,但我会尝试使用的方法是:

  1. 一个在日期列中有索引的数据库(之后可以很容易地在这个数据中搜索)。
  2. 要插入此底座,请使用批量插入。
  3. 以及一些并行读取的方法(认为并行 LINQ 会很好并且非常易于使用)。
  4. 足够的耐心(最重要/最困难的事情)
于 2010-09-25T20:23:52.313 回答
1

除了您正在做的任何事情(可能 Willw 的建议很有帮助),您的解析可以在多个线程上完成,前提是您有多个处理器或处理器内核。

于 2010-09-25T18:51:05.013 回答
0

不是真正的解决方案,只是出于兴趣,一种方法可以这样做:

  • 首先将文件分解为 1GB 的文件
  • 然后一次读取 2 个文件,将内容加载到字符串列表中并对其进行排序
  • 将其写回各个文件。

问题是您需要在每次传递时读取/写入 100 个文件并执行 100 次传递以确保数据已排序。

如果我的数学是正确的:即 10 000 GB 读取和 10 000 GB 写入,平均 10MB/秒,即 20 000 000 秒,即231 天

一种可行的方法是扫描文件一次并写入较小的文件,每个时间段一个,例如一天或一小时。然后对这些单独的文件进行排序。

于 2010-09-26T20:24:46.990 回答
0

从 USB 启动 Linux 风格并使用 while 命令读取文件。利用 grep、过滤器和管道来隔离数据。这一切都可以在 3 行 BASH 脚本中完成。Grep 将立即翻阅数据。我在 45 秒内完成了 700 万行

于 2010-12-17T10:49:58.687 回答
0

哇。首先,这是一个全新的文档痴迷水平。

我的实际建议是,尝试考虑该文件的真正必要性。

关于排序,我不知道这是否可行,但您可能想尝试构建一个直接从硬盘返回数据的枚举器(不保存任何内容,但可能只有几个指针),然后尝试使用 LINQ 的 OrderBy ,它也返回 IEnumerator ,希望您可以 Enamurate 并将其直接保存回磁盘。

唯一的问题是 OrderBy 是否在 RAM 中保存任何内容。

于 2010-09-27T16:55:27.563 回答
0

您可以尝试实现基数排序算法。因为 radix 只按顺序扫描整个列表,而且只扫描了几次,所以它可以帮助防止对 100 GB 文件进行大量扫描和查找。

基数排序打算将每次迭代的记录按一个部分分类。这部分可以是数字,也可以是日期时间部分,如年、月、日。在这种情况下,您甚至不需要将字符串转换为 DateTime,您只需将特定部分转换为 int。

编辑:

出于排序目的,您可以创建仅包含 2 列的临时二进制文件:DateTime (DateTime.ToBinary() as Int64) 和源文件中的行地址 (as Int64)。

然后你得到一个具有固定大小记录的小得多的文件,每条记录只有 16 个字节,然后你可以更快地对其进行排序(至少 IO 操作会更快)。

完成对临时文件的排序后,您可以重新创建完整排序的 100 GB 日志文件。

于 2010-09-25T21:49:59.417 回答