9

我试图stdin通过setvbuf在 `_IOFBF~ 模式下使用来有效地读取。我是缓冲的新手。我正在寻找工作示例。

输入以两个整数 ( 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;
}

问题是如果 number 在边界处,缓冲区 buf将从 读取232354\n当它应该读取2354(它不能)或根本没有读取时。

我该如何解决这个问题?


编辑
立即解决(通过分析)

编辑
完整的问题规范

4

11 回答 11

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;
}

gcc version 3.4.5 (mingw-vista special r3)我使用 Perl 脚本生成 0 到 1,000,000 之间的 1,000,000 个随机整数,并在我的 Windows XP 笔记本电脑上编译该程序后检查它们是否可被 5 整除。整个过程不到0.8秒。

当我使用 关闭缓冲时setvbuf(stdin, (char*)NULL, _IONBF, 0);,时间增加到大约 15 秒。

于 2010-03-04T00:54:00.953 回答
2

我发现令人困惑的一件事是,为什么您既要通过调用在流对象中启用完整缓冲,setvbuf又要通过将完整缓冲区读取到buf.

我理解需要做缓冲,但这有点矫枉过正。

我将建议您坚持setvbuf并删除您自己的缓冲。原因是实现自己的缓冲可能很棘手。问题是当令牌(在您的情况下为数字)跨越缓冲区边界时会发生什么。例如,假设您的缓冲区是 8 个字节(尾随 NULL 总共 9 个字节)并且您的输入流看起来像

12345 12345

第一次填充缓冲区时,您会得到:

"12345 12"

而第二次填充缓冲区时,您会得到:

"345"

适当的缓冲需要您处理这种情况,以便将缓冲区视为两个数字 {12345, 12345} 而不是三个数字 {12345, 12, 234}。

由于 stdio 已经为您处理了,请使用它。继续调用setvbuf,去掉fread并使用scanf从输入流中读取单个数字。

于 2010-03-04T00:41:43.557 回答
2

版本 1:getchar_unlocked按照 R Samuel Klatchko 的建议使用(见评论)

#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, &dividend);

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

PS - 使用 GCC 编译的每次运行都使用 -O1 标志

于 2010-03-04T10:38:54.303 回答
1

不使用重定向时的问题是您没有导致 EOF。

由于这似乎是 Posix(基于您使用 gcc 的事实),只需键入ctrl-D(即在按下控制按钮的同时按下/释放 d),这将导致到达 EOF。

如果您使用的是 Windows,我相信您会使用ctrl-Z

于 2010-03-03T23:47:23.810 回答
1

如果您追求彻底的速度并且在 POSIX-ish 平台上工作,请考虑使用内存映射。我使用标准 I/O 对思南的回答进行了计时,并使用内存映射创建了下面的程序。请注意,如果数据源是终端或管道而不是文件,则内存映射将不起作用。

100 万个值介于 0 到 10 亿之间(固定除数为 17),这两个程序的平均时间为:

  • 标准I/O:0.155s
  • 内存映射:0.086s

粗略地说,内存映射 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);
}
于 2010-03-06T17:21:30.553 回答
0

n看到n整数后,您可以使用 的值停止读取输入。

将外while循环的条件更改为:

while(n > 0 && fread(buf, sizeof('1'), BUFSIZE, stdin))

并将内部的主体更改为:

{
  n--;
  if(tmp%k == 0)  ++ans;
}

您继续遇到的问题是,因为您从不buf在内while循环中进行调整,所以sscanf一遍又一遍地读取相同的数字。

如果您切换到使用strtol()intead of sscanf(),那么您可以使用endptr输出参数在读取数字时在缓冲区中移动。

于 2010-03-03T23:17:47.377 回答
0

好吧,从顶部开始,scanf("%d%d",&n,&k) 将只将一个值推入 n 并默默地保持 k 未设置 - 如果您检查 scanf() 的返回值,您会看到这一点,它告诉你它填充了多少变量。我认为您希望 scanf("%d %d",&n,&k) 带有空格。

其次,n 是要运行的迭代次数,但您测试“n>0”但从不减少它。因此,n>0 始终为真,循环不会退出。

正如其他人提到的,通过管道提供标准输入会导致循环退出,因为标准输入的末尾有一个 EOF,这导致 fread() 返回 NULL,从而退出循环。您可能想在其中的某处添加“n=n-1”或“n--”。

接下来,在您的 sscanf 中, %n 并不是一个标准的东西;我不确定它是什么意思,但它可能什么都不做:scanf() 通常会在第一个无法识别的格式标识符处停止解析,这在这里什么都不做(因为您已经获得了数据),但这是不好的做法。

最后,如果性能很重要,最好不要使用 fread() 等,因为它们并不是真正的高性能。查看 isdigit(3) 和 iscntrl(3) 并考虑如何解析使用 read(2) 读取的原始数据缓冲区中的数字。

于 2010-03-04T00:44:10.900 回答
-1

最外层的while()循环只会在 read fromstdin返回时退出EOF。这只会发生在到达输入文件的实际文件结尾,或者写入输入管道的进程退出时。因此,该printf()语句永远不会执行。我认为这与对setvbuf().

于 2010-03-03T13:24:31.040 回答
-1

Mabe 还看看这个 getline 实现:

http://www.cpax.org.uk/prg/portable/c/libs/sosman/index.php

(用于从流中获取长度未知的一行数据的 ISO C 例程。)

于 2010-03-03T13:36:09.983 回答
-2

所有这些过分优化对运行时的影响可以忽略不计的原因是,在 *nix 和 windows 类型的操作系统中,操作系统处理所有进出文件系统的 I/O,并实施了 30 年的研究、诡计和狡猾来做到这一点非常有效。

您试图控制的缓冲仅仅是您的程序使用的内存块。因此,速度的任何增加都将是最小的(执行 1 个大的“mov”指令与 6 或 7 个较小的“mov”指令的效果)。

如果您真的想加快速度,请尝试“mmap”,它允许您直接访问文件系统缓冲区中的数据。

于 2010-03-04T02:17:13.103 回答
-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;

}
于 2010-03-04T19:28:40.007 回答