如何快速计算行数?

30

我尝试使用unxutilswc -l,但它在1GB文件上崩溃了。 我尝试了这个C#代码

long count = 0;
using (StreamReader r = new StreamReader(f))
{
    string line;
    while ((line = r.ReadLine()) != null)
    {
        count++;
    }
}

return count;

它在4秒内读取了一个500MB的文件

var size = 256;
var bytes = new byte[size];
var count = 0;
byte query = Convert.ToByte('\n');
using (var stream = File.OpenRead(file))
{
    int many;
    do
    {
        many = stream.Read(bytes, 0, size);
        count += bytes.Where(a => a == query).Count();                    
    } while (many == size);
}

10秒内读取完成

var count = 0;
int query = (int)Convert.ToByte('\n');
using (var stream = File.OpenRead(file))
{
    int current;
    do
    {
        current = stream.ReadByte();
        if (current == query)
        {
            count++;
            continue;
        }
    } while (current!= -1);
}

需要 7 秒

还有什么比这更快的我还没试过吗?


@nCdy被添加为答案。 - Jader Dias
3
你的个人资料运行结果指出每个地区的热点是什么? - Eric Lippert
3
你确定你正在测试行数计算而不是文件系统吗?如果第一次测试加载文件被缓存了,那么后续的测试会运行得更快。确保你正在真正测试你认为要测试的东西。 - Jim Mischel
向Jim Mischel点赞。性能测试比大多数人想象的要棘手! - Cheeso
一些相关讨论请参见:https://bytes.com/topic/c-sharp/answers/830061-count-lines-huge-text-files - Arithmomaniac
6个回答

13

File.ReadLines是在.NET 4.0中引入的。

var count = File.ReadLines(file).Count();

相同的时间为4秒,与第一个代码片段一样


这是因为它基本上做的事情和你的第一个片段一样 ;) - SirViver
请勿使用Count(),而是使用Length(File.ReadAllLines(@“yourfile”)。Length;)//再次检查此解决方案,但使用Length - cnd
7
这是一个非常糟糕的建议(在这种情况下)!要注意区别:他使用的是File.ReadLines(),它实际上返回一个IEnumerable<string>,并且只执行了基本上与他的第一段代码相同的yield returnFile.ReadAllLines()会将__所有行__读入内存,这在性能方面会非常糟糕。话虽如此,如果您已经有一个数组,应该使用Length而不是Count() ;) - SirViver
@SirViver同意。如果他不需要使用所有行,他就不需要加载它们。 - cnd
1
正如SirViver所说,@nCdy出现了“System.OutOfMemoryException”类型的异常。 - Jader Dias
计数和长度仅返回整数吗? - Tim Barrass

12

你的第一种方法看起来已经是最优解了。请记住,你的限制大多数情况下并不是由CPU而是受到硬盘的读取速度所制约,以每4秒500MB为例,即速度已经相当快了,达到了125MB/s。要想获得更快的速度,唯一的方法是通过RAID或使用SSD,而不是通过更好的算法。


我还发现,我可以通过获取文件大小并将其除以前几行的中等大小来估算行数。 - Jader Dias
@JaderDias:没错,但这只是一个估计值,而不是实际计数。而且,根据文件的结构,你的估计可能会相差很远。由于你没有说明行计数的目的或文件的典型外观,因此无法给出更专业的建议。 - SirViver
对于我的CSV文件,估计是足够准确的。 - Jader Dias

2

您是否只是在寻找一种高效计算文件行数的工具?如果是这样,可以尝试使用微软的LogParser

像下面这样的命令将会给出文件的行数:

LogParser "SELECT count(*) FROM file" -i:TEXTLINE

2
如果您真的想要快速执行,考虑使用C代码。如果这是一个命令行实用程序,它将更快,因为它不需要初始化CLR或.NET。此外,它不会为从文件读取的每一行重新分配新字符串,这可能会在吞吐量上节省时间。我没有任何包含1g行的文件,所以无法进行比较。不过您可以尝试一下:
/*
 * LineCount.c
 *
 * count lines...
 *
 * compile with: 
 *
 *  c:\vc10\bin\cl.exe /O2 -Ic:\vc10\Include -I\winsdk\Include 
 *          LineCount.c -link /debug /SUBSYSTEM:CONSOLE /LIBPATH:c:\vc10\Lib
 *          /LIBPATH:\winsdk\Lib /out:LineCount.exe
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>


void Usage(char *appname)
{
    printf("\nLineCount.exe\n");
    printf("  count lines in a text file...\n\n");
    printf("usage:\n");
    printf("  %s <filename>\n\n", appname);
}



int linecnt(char *file)
{
    int sz = 2048;
    char *buf = (char *) malloc(sz);
    FILE *fp = NULL;
    int n= 0;
    errno_t rc = fopen_s(&fp, file, "r");

    if (rc) {
        fprintf(stderr, "%s: fopen(%s) failed: ecode(%d)\n",
                __FILE__, file, rc);
        return -1;
    }

    while (fgets(buf, sz, fp)){
        int r = strlen(buf);
        if (buf[r-1] == '\n')
            n++;
        // could re-alloc here to handle larger lines
    }
    fclose(fp);
    return n;
}

int main(int argc, char **argv)
{
    if (argc==2) {
        int n = linecnt (argv[1]);
        printf("Lines: %d\n", n);
    }
    else {
        Usage(argv[0]);
        exit(1);
    }
}

10秒 =(在VS2010上以调试模式运行,就像所有其他测试一样) - Jader Dias
非常惊讶。我怀疑还有其他问题。 - Cheeso
16
@Jader: 等一下,你在调试模式下运行性能测试?千万不要这样做。调试器故意将程序进行反优化以提高调试体验,因此你会得到完全误导性的结果。虽然在这种情况下你的程序瓶颈在于磁盘而非处理器,但仍然在调试器中测量性能是一种非常糟糕的编程实践。 - Eric Lippert
@Eric 我本来就预料到会有人这么说,但我在“发布”模式下运行了所有测试,如果你将时间舍入到秒,那么什么也不会改变(在调试模式下4秒,在发布模式下仍然是4秒)。 - Jader Dias
1
@Jader:就像我说的那样,那是因为你很幸运,恰好选择了一个受到磁盘硬件速度限制的性能问题。当你试图优化受实际代码速度限制的东西时,情况完全不同。 - Eric Lippert
显示剩余2条评论

1

你试过使用flex吗?

%{
long num_lines = 0;
%}
%option 8bit outfile="scanner.c"
%option nounput nomain noyywrap
%option warn

%%
.+ { }
\n { ++num_lines; }
%%
int main(int argc, char **argv);

int main (argc,argv)
int argc;
char **argv;
{
yylex();
printf( "# of lines = %d\n", num_lines );
return 0;
}

只需编译:

flex -Cf scanner.l 
gcc -O -o lineCount.exe scanner.c

它从标准输入接受输入并输出行数。


1

我认为你的回答看起来很不错。唯一需要补充的是尝试不同的缓冲区大小,因为我感觉它可能会影响性能,具体取决于缓冲区大小。

请参考最佳文件缓冲读取大小?中有关缓冲区大小的内容。


我尝试了不同的值,256以上的任何值都具有相同的性能,而像4这样较低的值则较慢。 - Jader Dias

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接