我正在从文件中读取大量数据:
//的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) {
}
我尝试使用atoi
,strtok
,但是当数组太大且sscanf
也非常慢时,它们会非常耗时。
如何改善大数据的性能?
我使用strtok
进行解析。我正在寻找快速解析每一行的方法。
我正在读取每一行,然后将每行解析为:
char * ptr;
ptr = strtok (str," ");
while (ptr != NULL)
{
int value1 = atoi(ptr) ;
ptr = strtok (NULL, " ");
}
int
?atoi
将char *
转换为int
。char *
转换为int
吗?答案 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 ++文件读取例程,例如fread
或fstream
,您应该获得缓冲读取,这应该已经非常有效,但您可以尝试使用底层操作系统调用,例如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;
real
和user
之间的区别在于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)
如果您的文件包含int编号,则可以使用operator>>,但这只是c ++解决方案。类似的东西:
std::fstream f("abc.txt");
int value = 0;
f >> value
如果您将文件转换为包含二进制数表示,则可以使用更多选项来提高性能。它不仅可以避免将数字从字符串解析为类型,还可以使用其他选项来访问您的数据(例如使用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处理器。)