从文件中读取大量数据并以有效的方式解析日期。如何提高海量数据的性能?

时间:2012-06-18 05:38:45

标签: c++ c performance optimization

我正在从文件中读取大量数据:

//的abc.txt

10  12  14  15  129

-12 14 -18  -900 -1234

145 12 13
12

32 68 51 76 -59 -025 

- - - - etc

fun(char *p, int x, int y, int z) {

}

我尝试使用atoistrtok,但是当数组太大且sscanf也非常慢时,它们会非常耗时。

如何改善大数据的性能?

我使用strtok进行解析。我正在寻找快速解析每一行的方法。

我正在读取每一行,然后将每行解析为:

 char * ptr;
 ptr = strtok (str," ");
 while (ptr != NULL)
 {
    int value1 = atoi(ptr) ;
    ptr = strtok (NULL, " ");
 }
  • 有没有快速的方法将字符串解析为int
  • 是否有任何替代方法比代码更快?我使用atoichar *转换为int
  • 我可以使用其他快速方法将char *转换为int吗?

5 个答案:

答案 0 :(得分:3)

要将ASCII字符串转换为整数值,您的速度要比atoi快得多,但您可以通过实现内联使用的转换函数来加快速度。下面的版本将指针递增超过扫描的数字,因此它与atoi语义不匹配,但它应该有助于提高解析器效率,如下所示。 (显然缺少错误检查,因此如果需要,请添加它。)

static inline int my_parsing_atoi(const char *&s) {
    if (s) {
        bool neg = false;
        int val = 0;
        if (*s == '-') { neg = true; ++s; }
        for (;isdigit(*s);++s) val = 10*val + (*s - '0');
        return neg ? -val : val;
    }
    return 0;
}

const char *p = input_line;
if (p) {
    p += strspn(p, " ");
    while (*p) {
        int value1 = my_parsing_atoi(p);
        p += strspn(p, " ");
    }
}

确保您已正确分析了代码,以便您知道您的例程是计算绑定而不是I / O绑定。大多数情况下,您将受到I / O限制,以下建议是减轻它的方法。

如果您正在使用C或C ++文件读取例程,例如freadfstream,您应该获得缓冲读取,这应该已经非常有效,但您可以尝试使用底层操作系统调用,例如POSIX read,一次读取更大块的文件,以加快文件读取效率。实际上,您可以在处理文件时通过使用线程或使用aio_read对文件执行异步读取。您甚至可以使用mmap,这将删除一些数据复制开销,但如果文件非常大,您将需要管理地图,以便munmap已经存档的文件部分扫描并在要扫描的新部分中mmap

我使用如下所示的代码对上面的解析例程和OP的例程进行了基准测试:

clock_t before_real;
clock_t after_real;
struct tms before;
struct tms after;
std::vector<char *> numbers;
make_numbers(numbers);
before_real = times(&before);
for (int i = 0; i < numbers.size(); ++i) {
    parse(numbers[i]);
}
after_real = times(&after);
std::cout << "user: " << after.tms_utime - before.tms_utime
          << std::endl;
std::cout << "real: " << after_real - before_real
          << std::endl;

realuser之间的区别在于real是挂钟时间,而user是运行该进程的操作系统所花费的实际时间(因此上下文切换是不计入运行时间。)

我的测试使我的例程运行速度几乎是OP的例程的两倍(在64位Linux系统上使用g++ -O3编译)。

答案 1 :(得分:3)

你在找错了地方。除非你正在做一些真正的奇怪的事情,否则问题不在于解析。在现代的N Ghz CPU上,每行所需的周期很小。杀死性能的是物理I / O.纺纱材料往往以10秒/秒的速度运行。

我还怀疑问题是文件的物理读取,因为它将在文件系统缓存中高效缓存。

不,正如 samy.vilar 暗示,问题几乎可以肯定是虚拟内存:

  

......数组太大......

使用系统监视器/ psinfo / top查看您的应用程序。几乎可以肯定,它正在增加一个大型工作集,因为它构建了一个内存阵列,而你的操作系统正在将其分配到磁盘。

所以忘记阅读是一个问题。您真正的问题是如何操作内存中的大量数据集。这里的方法是多种多样的:

  • 别。批量处理数据并操纵批次。
  • 使用节省空间的存储空间(例如紧凑元素)。
  • 分配更多内存资源。

围绕这个问题进行了很多讨论。

答案 2 :(得分:1)

如果你的文件非常庞大,那么IO就是在扼杀你,而不是解析。每次读一行时,您都在执行系统调用,这可能非常昂贵。

更有效的替代方案可能是使用Memory-Mapped File IO。如果您正在使用诸如Linux之类的POSIX系统,则可以使用mmap命令一次性加载文件并返回指向其在内存中的位置的指针。然后,当您通过该指针访问数据时,内存管理器负责读取和交换文件。

这看起来像这样

#include <sys/mman.h>
int fd = open( 'abc.txt' , O_RDONLY );
char *ptr = mmap( NULL , length , PROT_READ , MAP_PRIVATE , fd , 0 );

但我强烈建议您阅读手册页,为自己找到最佳选择。

答案 3 :(得分:0)

  1. 如果您的文件包含int编号,则可以使用operator>>,但这只是c ++解决方案。类似的东西:

    std::fstream f("abc.txt");
    int value = 0;
    f >> value
    
  2. 如果您将文件转换为包含二进制数表示,则可以使用更多选项来提高性能。它不仅可以避免将数字从字符串解析为类型,还可以使用其他选项来访问您的数据(例如使用mmap)。

答案 4 :(得分:0)

首先,一般建议总是使用分析来检查它实际上是转换速度慢,而不是其他东西,例如从磁盘上物理读取文件。

您可以通过编写自己的最小数字解析函数来提高性能。 strtok修改字符串,因此它不会最快,如果你知道所有数字都是十进制整数,并且你不需要任何错误检查,你可以稍微简化一下。

一些没有strtok的代码可以加速一行的处理,如果它实际上是翻译而不是(例如)I / O就是问题。

void handle_one_number(int number) {
    // ....
}

void handle_numbers_in_buffer(char *buffer) {
    while (1) {
        while (*buffer != '\0' && isspace(*buffer))
            ++buffer;
        if (*buffer == '\0')
            return;
        int negative = 0;
        if (*buffer == '-') {
            negative = 1;
            ++buffer;
        }
        int number = 0;
        while (isdigit(*buffer)) {
            number = number * 10 + *buffer - '0';
            ++buffer;
        }
        if (negative)
            number = -number;
        handle_one_number(number);
    }
}

我实际上去了一些基准测试。我原本以为I / O占主导地位,但事实证明(对于“在我的特定系统中,使用我的特定编译器”,通常需要注意)解析数字需要花费很多时间。

通过从strtok版本更改为上面的代码,我设法将翻译1亿个数字(文本已经在内存中)的时间从5.2秒提高到1.1秒左右。当从慢盘(Caviar Green)读取时,我测量了从5.9秒到3.5秒的改进。从SSD读取时,我测量了5.8到1.8秒的改进。

我也尝试使用while (fscanf(f, "%d", ....) == 1) ....直接读取文件,但结果要慢得多(10秒),因为fscanf是线程安全的,而且更多的调用需要更多的锁定。

(UCCntu 11.04上的GCC 4.5.2,其中-O2优化,每个版本执行多次,在运行之间刷新磁盘缓存,i7处理器。)