我正在使用scanf("%d", &someint)
对正整数进行大量解析。正如我想看看scanf是否是一个瓶颈,我使用fread
实现了一个天真的整数解析函数,就像:
int result;
char c;
while (fread(&c, sizeof c, 1, stdin), c == ' ' || c == '\n')
;
result = c - '0';
while (fread(&c, sizeof c, 1, stdin), c >= '0' || c <= '9') {
result *= 10;
result += c - '0';
}
return result;
但令我惊讶的是,这个功能的表现(即使内联)也差了不到50%。对于特殊情况,是否应该有可能改进scanf?是不是fread
应该是快的(附加提示:整数是(编辑:大多数)1或2位数?)
答案 0 :(得分:7)
我遇到的开销不是解析本身,而是对fread
的许多调用(与fgetc
和朋友相同)。对于每次调用,libc都必须锁定输入流,以确保两个线程不会相互踩踏。锁定是一项非常昂贵的操作。
我们正在寻找的是一个为我们提供缓冲输入的功能(重新实现缓冲只需要太多努力),但避免了fgetc
的巨大锁定开销。
如果我们可以保证只有一个线程使用输入流,我们可以使用unlocked_stdio(3)
中的函数,例如getchar_unlocked(3)
。这是一个例子:
static int parseint(void)
{
int c, n;
n = getchar_unlocked() - '0';
while (isdigit((c = getchar_unlocked())))
n = 10*n + c-'0';
return n;
}
以上版本不会检查错误。但它保证终止。如果需要进行错误处理,最后可能会检查feof(stdin)
和ferror(stdin)
,或让调用者执行此操作。
我最初的目的是在SPOJ上提交编程问题的解决方案,其中输入只是空格和数字。因此,仍有改进的余地,即isdigit
检查。
static int parseint(void)
{
int c, n;
n = getchar_unlocked() - '0';
while ((c = getchar_unlocked()) >= '0')
n = 10*n + c-'0';
return n;
}
在性能方面以及便利性和可维护性方面,非常非常难以击败此解析例程。
答案 1 :(得分:4)
通过缓冲,您将能够显着改善您的示例 - 将大量字符读入内存,然后从内存中解析它们。
如果您正在从磁盘读取数据,则缓冲区可能会因块大小的倍数而增加性能。
编辑:您可以让内核为您处理此问题,方法是使用mmap将文件映射到内存中。
答案 2 :(得分:1)
这是我使用的东西。
#define scan(x) do{while((x=getchar())<'0'); for(x-='0'; '0'<=(_=getchar()); x=(x<<3)+(x<<1)+_-'0');}while(0)
char _;
但是,这仅适用于整数。
答案 3 :(得分:-1)
根据你的说法,我得出以下事实:
在这种情况下,我会使用查找表将字符串转换为数字。给定一个字符串s [2],查找表的索引可以由s[1]*10 + s[0]
计算,交换数字并利用ASCII中'\0'
等于0
的事实。
然后,您可以通过以下方式阅读输入:
// given our lookup method, this table may need padding entries
int lookup_table[] = { /*...*/ };
// no need to call superfluous functions
#define str2int(x) (lookup_table[(x)[1]*10 + (x)[0]])
while(read_token_from_stream(stdin, buf))
next_int = str2int(buf);
在今天的机器上,很难想出更快的技术。我的猜测是,这种方法的运行速度可能比任何基于scanf()
的方法快2到10倍。