我试图通过在`_IOFBF~模式中使用stdin
来有效地从setvbuf
读取。我是新来的缓冲。我正在寻找工作的例子。
输入以两个整数(n
,k
)开头。下一行n
行包含1个整数。目的是打印k
可以整除的整数数。
#define BUFSIZE 32
int main(){
int n, k, tmp, ans=0, i, j;
char buf[BUFSIZE+1] = {'0'};
setvbuf(stdin, (char*)NULL, _IONBF, 0);
scanf("%d%d\n", &n, &k);
while(n>0 && fread(buf, (size_t)1, (size_t)BUFSIZE, stdin)){
i=0; j=0;
while(n>0 && sscanf(buf+j, "%d%n", &tmp, &i)){
//printf("tmp %d - scan %d\n",tmp,i); //for debugging
if(tmp%k==0) ++ans;
j += i; //increment the position where sscanf should read from
--n;
}
}
printf("%d", ans);
return 0;
}
问题是如果数字在边界处,缓冲区 buf
将从23
读取2354\n
,当它应该是{{1} (它不能)或根本没有。
我该如何解决这个问题?
答案 0 :(得分:3)
我建议您使用setvbuf
尝试完全缓冲并放弃fread
。如果规范是每行有一个数字,我会认为这是理所当然的,使用fgets
读取一整行并将其传递给strtoul
解析应该在该数字上的数字线。
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define INITIAL_BUFFER_SIZE 2 /* for testing */
int main(void) {
int n;
int divisor;
int answer = 0;
int current_buffer_size = INITIAL_BUFFER_SIZE;
char *line = malloc(current_buffer_size);
if ( line == NULL ) {
return EXIT_FAILURE;
}
setvbuf(stdin, (char*)NULL, _IOFBF, 0);
scanf("%d%d\n", &n, &divisor);
while ( n > 0 ) {
unsigned long dividend;
char *endp;
int offset = 0;
while ( fgets(line + offset, current_buffer_size, stdin) ) {
if ( line[strlen(line) - 1] == '\n' ) {
break;
}
else {
int new_buffer_size = 2 * current_buffer_size;
char *tmp = realloc(line, new_buffer_size);
if ( tmp ) {
line = tmp;
offset = current_buffer_size - 1;
current_buffer_size = new_buffer_size;
}
else {
break;
}
}
}
errno = 0;
dividend = strtoul(line, &endp, 10);
if ( !( (endp == line) || errno ) ) {
if ( dividend % divisor == 0 ) {
answer += 1;
}
}
n -= 1;
}
printf("%d\n", answer);
return 0;
}
我使用Perl脚本生成1,000,000个0到1,000,000之间的随机整数,并在我的Windows XP笔记本电脑上使用gcc version 3.4.5 (mingw-vista special r3)
编译该程序后检查它们是否可被5整除。整件事花了不到0.8秒。
当我使用setvbuf(stdin, (char*)NULL, _IONBF, 0);
关闭缓冲时,时间上升到大约15秒。
答案 1 :(得分:2)
我觉得有些困惑的一点是,为什么你要通过调用setvbuf
在流对象中启用完全缓冲,并通过将完整缓冲区读入buf
来进行自己的缓冲。
我理解需要做缓冲,但这有点矫枉过正。
我建议你坚持使用setvbuf
并删除自己的缓冲。原因是实现自己的缓冲可能很棘手。问题是当一个令牌(在你的情况下是一个数字)跨越缓冲区边界时会发生什么。例如,假设你的缓冲区是8个字节(总共9个字节用于尾随NULL),你的输入流看起来像
12345 12345
第一次填充缓冲区时,您会得到:
"12345 12"
当你第二次填充缓冲区时,你会得到:
"345"
正确缓冲需要您处理该情况,因此您将缓冲区视为两个数字{12345,12345}而不是三个数字{12345,12,234}。
由于stdio已经为您处理,只需使用它。继续致电setvbuf
,摆脱fread
并使用scanf
从输入流中读取个别数字。
答案 2 :(得分:2)
版本1:按照Samuel Klatchko的建议使用getchar_unlocked
(见评论)
#define BUFSIZE 32*1024
int main(){
int lines, number=0, dividend, ans=0;
char c;
setvbuf(stdin, (char*)NULL, _IOFBF, 0);// full buffering mode
scanf("%d%d\n", &lines, ÷nd);
while(lines>0){
c = getchar_unlocked();
//parse the number using characters
//each number is on a separate line
if(c=='\n'){
if(number % dividend == 0) ans += 1;
lines -= 1;
number = 0;
}
else
number = c - '0' + 10*number;
}
printf("%d are divisible by %d \n", ans, dividend);
return 0;
}
版本2:使用fread
读取块并从中解析数字。
#define BUFSIZE 32*1024
int main(){
int lines, number=0, dividend, ans=0, i, chars_read;
char buf[BUFSIZE+1] = {0}; //initialise all elements to 0
scanf("%d%d\n",&lines, ÷nd);
while((chars_read = fread(buf, 1, BUFSIZE, stdin)) > 0){
//read the chars from buf
for(i=0; i < chars_read; i++){
//parse the number using characters
//each number is on a separate line
if(buf[i] != '\n')
number = buf[i] - '0' + 10*number;
else{
if(number%dividend==0) ans += 1;
lines -= 1;
number = 0;
}
}
if(lines==0) break;
}
printf("%d are divisible by %d \n", ans, dividend);
return 0;
}
结果:(1000万个数字的可分性测试结果为11)
运行1 :(没有setvbuf的版本1)0.782秒
运行2 :(带有setvbuf的版本1)0.684秒
运行3 :(版本2)0.534
P.S。 - 每次使用-O1标志用GCC编译的运行
答案 3 :(得分:1)
不使用重定向时的问题是您没有导致EOF。
由于这似乎是Posix(基于您使用gcc的事实),只需键入ctrl-D
(即按下控制按钮,按下/释放d),这将导致EOF到达。
如果您使用的是Windows,我相信您会使用ctrl-Z
。
答案 4 :(得分:1)
如果您在完成速度并且在POSIX-ish平台上工作,请考虑使用内存映射。我使用标准I / O获取了Sinan的答案并定时,并使用内存映射创建了下面的程序。请注意,如果数据源是终端或管道而不是文件,则内存映射将不起作用。
有一百万个值在0到十亿之间(固定除数为17),这两个程序的平均时间是:
粗略地说,内存映射I / O的速度是标准I / O的两倍。
在每种情况下,在忽略预热运行后,时间重复6次。命令行是:
time fbf < data.file # Standard I/O (full buffering)
time mmf < data.file # Memory mapped file I/O
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
static const char *arg0 = "**unset**";
static void error(const char *fmt, ...)
{
va_list args;
fprintf(stderr, "%s: ", arg0);
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
exit(EXIT_FAILURE);
}
static unsigned long read_integer(char *src, char **end)
{
unsigned long v;
errno = 0;
v = strtoul(src, end, 0);
if (v == ULONG_MAX && errno == ERANGE)
error("integer too big for unsigned long at %.20s", src);
if (v == 0 && errno == EINVAL)
error("failed to convert integer at %.20s", src);
if (**end != '\0' && !isspace((unsigned char)**end))
error("dubious conversion at %.20s", src);
return(v);
}
static void *memory_map(int fd)
{
void *data;
struct stat sb;
if (fstat(fd, &sb) != 0)
error("failed to fstat file descriptor %d (%d: %s)\n",
fd, errno, strerror(errno));
if (!S_ISREG(sb.st_mode))
error("file descriptor %d is not a regular file (%o)\n", fd, sb.st_mode);
data = mmap(0, sb.st_size, PROT_READ, MAP_PRIVATE, fileno(stdin), 0);
if (data == MAP_FAILED)
error("failed to memory map file descriptor %d (%d: %s)\n",
fd, errno, strerror(errno));
return(data);
}
int main(int argc, char **argv)
{
char *data;
char *src;
char *end;
unsigned long k;
unsigned long n;
unsigned long answer = 0;
size_t i;
arg0 = argv[0];
data = memory_map(0);
src = data;
/* Read control data */
n = read_integer(src, &end);
src = end;
k = read_integer(src, &end);
src = end;
for (i = 0; i < n; i++, src = end)
{
unsigned long v = read_integer(src, &end);
if (v % k == 0)
answer++;
}
printf("%lu\n", answer);
return(0);
}
答案 5 :(得分:0)
您可以使用n
的值在看到n
整数后停止阅读输入。
将外部while
循环的条件更改为:
while(n > 0 && fread(buf, sizeof('1'), BUFSIZE, stdin))
并将内部的主体更改为:
{
n--;
if(tmp%k == 0) ++ans;
}
您继续遇到的问题是因为您从未在内部buf
循环中调整while
,sscanf
会一遍又一遍地读取相同的数字。
如果您切换到strtol()
intead sscanf()
,那么您可以使用endptr
输出参数在读取数字时移动缓冲区。
答案 6 :(得分:0)
好吧,从顶部开始,scanf(“%d%d”,&amp; n,&amp; k)只会将一个值推入n并静默地保持k未设置 - 如果你检查了返回,你会看到这个scanf()的值,它告诉你它填充了多少变量。我想你想要scanf(“%d%d”,&amp; n,&amp; k)和空格。
其次,n是要运行的迭代次数,但是您测试“n&gt; 0”但从未递减它。因此,n> 0始终为真且循环不会退出。
正如其他人提到的那样,在管道上输入stdin会导致循环退出,因为stdin的结尾有一个EOF,导致fread()返回NULL,退出循环。你可能想在那里的某处添加一个“n = n-1”或“n--”。
接下来,在你的sscanf中,%n并不是一个标准的东西;我不确定这是做什么的,但它可能什么都不做:scanf()通常会停止解析第一个无法识别的格式标识符,这里没有任何作用(因为你已经获得了数据),但这是不好的做法。
最后,如果性能很重要,那么最好不要使用fread()等,因为它们的性能并不高。查看isdigit(3)和iscntrl(3)并考虑如何解析用read(2)读取的原始数据缓冲区中的数字。
答案 7 :(得分:-1)
最外面的while()
循环只有在stdin
的读取返回EOF
时才会退出。这只能在到达输入文件的实际文件结束时,或者如果写入输入管道的进程退出时才会发生。因此,永远不会执行printf()
语句。我认为这与调用setvbuf()
没有任何关系。
答案 8 :(得分:-1)
Mabe还看一下这个getline实现:
http://www.cpax.org.uk/prg/portable/c/libs/sosman/index.php
(用于从流中获取一行数据,长度未知的ISO C例程。)
答案 9 :(得分:-2)
所有这些过早优化对运行时具有可忽略影响的原因是,在* nix和Windows类型的操作系统中,OS处理与文件系统之间的所有I / O,并实现了30年的研究,欺骗和狡猾这样做非常有效。
您尝试控制的缓冲仅仅是程序使用的内存块。所以速度的任何增加都是最小的(做1个大'mov'对6或7个'mov'指令的效果)。
如果你真的想加快速度,请尝试“mmap”,它允许你直接访问文件系统缓冲区中的数据。
答案 10 :(得分:-2)
这是我对逐字节的看法:
/*
Buffered reading from stdin using fread in C,
http://stackoverflow.com/questions/2371292/buffered-reading-from-stdin-for-performance
compile with:
gcc -Wall -O3 fread-stdin.c
create numbers.txt:
echo 1000000 5 > numbers.txt
jot -r 1000000 1 1000000 $RANDOM >> numbers.txt
time -p cat numbers.txt | ./a.out
*/
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#define BUFSIZE 32
int main() {
int n, k, tmp, ans=0, i=0, countNL=0;
char *endp = 0;
setvbuf(stdin, (char*)NULL, _IOFBF, 0); // turn buffering mode on
//setvbuf(stdin, (char*)NULL, _IONBF, 0); // turn buffering mode off
scanf("%d%d\n", &n, &k);
char singlechar = 0;
char intbuf[BUFSIZE + 1] = {0};
while(fread(&singlechar, 1, 1, stdin)) // fread byte-by-byte
{
if (singlechar == '\n')
{
countNL++;
intbuf[i] = '\0';
tmp = strtoul(intbuf, &endp, 10);
if( tmp % k == 0) ++ans;
i = 0;
} else {
intbuf[i] = singlechar;
i++;
}
if (countNL == n) break;
}
printf("%d integers are divisible by %d.\n", ans, k);
return 0;
}