需要一种按日期对100 GB日志文件进行排序的方法

时间:2010-09-25 18:38:38

标签: c# sorting date-sorting

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

data <date> data data more data

我可以在工作站上访问C#4.0和大约4 GB的RAM。我认为合并排序在这里最好,但是我自己实现这些算法还不够 - 我想问一下我是否可以采用某种捷径。

顺便说一句,用DateTime.Parse()解析日期字符串非常慢,占用了大量的CPU时间 - chugging -rate只有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的免费RAM上对100GB文件进行排序,而无需手动操作。

编辑2 :好的,多亏了我使用windows sort的一些建议,我发现有一个similar tool for Linux。基本上你叫sort,它会为你修复一切。当我们说它正在做某事时,我希望它很快就能完成。我正在使用的命令是

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

-k指定我要对第二行进行排序,第二行是通常YYYY-MM-DD hh:mm:ss.msek格式的日期时间字符串。我必须承认,man-pages缺乏解释所有选项,但我通过运行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

Wraawr!我想我会尽可能多地添加硬盘来加速这个过程。显然,添加USB驱动器是最糟糕的想法。目前我甚至无法判断它是关于FAT / NTFS还是其他一些,因为fdisk告诉我USB驱动器是“错误的设备”......不开玩笑。我会试着再试一次,现在让我们把这个项目放到可能失败的堆中。

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

基准

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

16 个答案:

答案 0 :(得分:18)

这样的代码完全受限于从磁盘上获取数据的速度。该文件根本无法放入文件系统缓存中,因此您始终在磁盘上等待提供数据。你以10 MB /秒的速度做得相当不错,优化代码永远不会有明显的效果。

获得更快的磁盘。将你所拥有的那个碎片整理为中间步骤。

答案 1 :(得分:15)

答案 2 :(得分:13)

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

答案 3 :(得分:9)

为什么不尝试使用名为logparser的微软这个相对未知的工具。它基本上允许您对CSV文件(或任何其他格式化的文本文件)执行SQL查询。

为您节省将数据泵入数据库,进行排序并再次将其重新输出的麻烦

答案 4 :(得分:8)

只是回答关于排序不适合内存的长文件的问题 - 您需要使用一些external sorting算法,例如Merge sort。这个过程大致如下:

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

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

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

您提到数据已经部分排序,因此在这种情况下选择一些内存排序算法(在第一阶段)是个好主意。您可以在this question中看到一些建议(虽然我不确定非常大的数据集的答案是否相同 - 并且它取决于对输入进行了多少部分排序)

答案 5 :(得分:4)

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

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

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

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

答案 6 :(得分:4)

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

  1. 打开输入文件
  2. 逐行阅读文件
  3. 从行
  4. 获取日期字符串
  5. 将行添加到文件<date>.log
  6. 结果将是每天的单独日志文件,或每小时单独的日志文件。选择此选项可以获得可以轻松排序的文件大小。

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

答案 7 :(得分:3)

  

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

在* NIX上,我通常会先将日期转换为简单的日期,适合文本比较,并将其作为字符串的第一个单词。对于日期/时间对象创建来说太早了。我通常的日期演示文稿是YYYYMMDD-hhmmss.millis。确保所有文件都具有相同的日期格式。

  

我仍然不知道如何在4GB的免费RAM上对100GB文件进行排序,而不是手动操作。

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

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

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

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

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

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

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

  6. 一般概念:不要试图找到快速完成的方法,管道100GB无论如何都需要时间;计划一个程序,每个步骤作为一个批次运行过夜,没有你的注意。

    在Linux上,使用shell / sort / awk / Perl都是可行的,我不认为用任何其他编程语言编写它都是一个问题。这可能是4个程序 - 但所有这些程序都很容易编码。

答案 8 :(得分:3)

假设您的日志文件只有1-2%的行无序,您可以通过整个日志进行一次传递,输出两个文件:一个文件是有序的,另一个文件包含1-2个无序的行百分比。然后对内存中的无序行进行排序,并执行以前的无序行与有序行的单个合并。这将比完整的mergesort快得多,后者将进行更多的传递。

假设您的日志文件没有超过N行的行,您可以通过日志进行单次传递,排序队列为N行深。每当遇到乱序的日志行时,只需将其插入队列中的适当位置即可。由于这只需要单次通过日志,因此它将尽可能快地完成。

答案 9 :(得分:2)

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

  1. 日期列中包含索引的数据库(以便在此数据中轻松搜索)。
  2. 要在此基础中插入,请使用批量插入。
  3. 并行读取的一些方法(在思考并行LINQ会很好并且非常容易使用)。
  4. 很多耐心(最重要/最难的事)

答案 10 :(得分: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);
    }
}

答案 11 :(得分:1)

除了你正在做的事情(可能,willw的建议是有帮助的),如果你有多个处理器或处理器核心,你的解析可以在多个线程上完成。

答案 12 :(得分:0)

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

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

编辑:

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

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

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

答案 13 :(得分:0)

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

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

问题是你需要在每次传递中读/写100个文件并进行100次传递以确保数据已排序。

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

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

答案 14 :(得分:0)

哇。首先,这是一个全新的记录模糊程度。

我的实际建议是,尝试考虑这个文件到底有多必要。

关于排序,我不知道这是否有效,但您可能想要尝试构建一个直接从硬盘返回数据的枚举器(不保存任何东西,但可能只有少量指针),然后尝试使用LINQ的OrderBy,它也返回IEnumerator,你希望它可以通过Enamurate直接保存回磁盘。

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

答案 15 :(得分:0)

从USB启动Linux风格 并使用while命令进行读取 文件。利用grep,过滤器和 用于隔离数据的管道。 这一切都可以在 BASH脚本的3行。 Grep将翻阅数据 没时间。我匆匆过去了 在45秒内700万行