【问题标题】:How to speed up printf in C如何在 C 中加速 printf
【发布时间】:2017-11-03 01:22:19
【问题描述】:

我的任务是在课堂上打印 1 到 1000000 之间的所有质数,最快的 10 个程序获得额外分数。主要问题是质数打印到控制台所需的时间。

基本上使用埃拉托色尼筛法,我生成了一个只有布尔值的数组。如果i+2 是质数,则布尔值Numbers[i] 为真。

for(i = 0; i <= n - 2; ++i)
   if (Numbers[i]) // True if the number is prime
        printf("%d\n", i+2);

Printf 似乎真的很慢,因为程序可以在大约 0.035 秒内生成素数列表,但随后需要 11 秒才能打印列表。无论如何我可以加快速度,谢谢。

【问题讨论】:

  • 我不认为,因为如果您不使用 printf 编译器,只需擦除您的计算,因为您不使用它。尝试写入文件。
  • stdout 默认是行缓冲的,所以每次打新行都会遇到延迟。尝试对文件执行fprintf,看看是否更快。
  • 也...看看这个答案:stackoverflow.com/a/11558934/1212725
  • @bruceg: stdout 是行缓冲的 如果 它将进入交互式设备。将程序的输出重定向到文件可能会更快。控制台的输出可能会受到控制台速度的限制。
  • (a) Show all of your code. (b) 尝试仅打印找到的最后一个素数。这将阻止编译器优化您的代码,就像程序中没有 printf 时那样。我预测你会发现程序仍然很慢,即使它只有一个printf,这证明是素数计算在花时间,而不是printf

标签: c performance printf


【解决方案1】:

下面是我认为你应该做的一个稍微未优化的实现(尽管我跳过了中间列表并直接打印)。在 AMD A8-6600K 上以小负载运行该程序(主要是用于一些个人娱乐的 Youtube 音乐视频)会导致

real    0m1.211s
user    0m0.047s
sys     0m0.122s

平均运行几次。所以问题在于您对筛子的实施,或者您隐藏了有关硬件的一些基本事实。

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <limits.h>
#include <string.h>

/* I call it a general bitset. Others might call it an abomination. YMMV. */
#   define ERAT_BITS (sizeof(uint32_t)*CHAR_BIT)
#   define GET_BIT(s,n)  ((*(s+(n/ERAT_BITS)) &   ( 1<<( n % ERAT_BITS ))) != 0)
#   define SET_BIT(s,n)   (*(s+(n/ERAT_BITS)) |=  ( 1<<( n % ERAT_BITS )))
#   define CLEAR_BIT(s,n) (*(s+(n/ERAT_BITS)) &= ~( 1<<( n % ERAT_BITS )))
#   define TOG_BIT(s,n)   (*(s+(n/ERAT_BITS)) ^=  ( 1<<( n % ERAT_BITS )))
/* size is the size in bits, the overall size might be bigger */
typedef struct mp_bitset_t {
    uint32_t size;
    uint32_t *content;
} mp_bitset_t;
#   define mp_bitset_alloc(bst, n) \
  do {\
      (bst)->content=malloc(( n /(sizeof(uint32_t)) + 1 ));\
      if ((bst)->content == NULL) {\
          fprintf(stderr, "memory allocation for bitset failed");\
          exit(EXIT_FAILURE);\
        }\
      (bst)->size = n;\
  } while (0)
#   define mp_bitset_size(bst)  ((bst)->size)
#   define mp_bitset_setall(bst) memset((bst)->content,~(uint32_t)(0),\
   (bst->size /(sizeof(uint32_t) ) +1 ))
#   define mp_bitset_clearall(bst) memset((bst)->content,0,\
   (bst->size /(sizeof(uint32_t) ) +1 ))
#   define mp_bitset_clear(bst,n) CLEAR_BIT((bst)->content, n)
#   define mp_bitset_set(bst,n)     SET_BIT((bst)->content, n)
#   define mp_bitset_get(bst,n)     GET_BIT((bst)->content, n)
#   define mp_bitset_free(bst) \
  do {\
     free((bst)->content);\
     free(bst);\
  } while (0)

uint32_t mp_bitset_nextset(mp_bitset_t * bst, uint32_t n);
uint32_t mp_bitset_prevset(mp_bitset_t * bst, uint32_t n);
void mp_eratosthenes(mp_bitset_t * bst);


/* It's called Hallek's method but it has many inventors*/
static uint32_t isqrt(uint32_t n)
{
   uint32_t s, rem, root;
   if (n < 1)
      return 0;
   /* This is actually the highest square but it goes
    * downward from this, quite fast */
   s = 1 << 30;
   rem = n;
   root = 0;
   while (s > 0) {
      if (rem >= (s | root)) {
         rem -= (s | root);
         root >>= 1;
         root |= s;
      } else {
         root >>= 1;
      }
      s >>= 2;
   }
   return root;
}

uint32_t mp_bitset_nextset(mp_bitset_t *bst, uint32_t n)
{
   while ((n < mp_bitset_size(bst)) && (!mp_bitset_get(bst, n))) {
      n++;
   }
   return n;
}

/*
 * Standard method, quite antique now, but good enough for the handful
 * of primes needed here.
 */
void mp_eratosthenes(mp_bitset_t *bst)
{
   uint32_t n, k, r, j;

   mp_bitset_setall(bst);
   mp_bitset_clear(bst, 0);
   mp_bitset_clear(bst, 1);

   n = mp_bitset_size(bst);
   r = isqrt(n);
   for (k = 4; k < n; k += 2)
      mp_bitset_clear(bst, k);
   k = 0;
   while ((k = mp_bitset_nextset(bst, k + 1)) < n) {
      if (k > r) {
         break;
      }
      for (j = k * k; j < n; j += k * 2) {
         mp_bitset_clear(bst, j);
      }
   }
}

#define UPPER_LIMIT 1000000 /* one million */

int main(void) {
  mp_bitset_t *bst;
  uint32_t n, k, j;

  bst = malloc(sizeof(mp_bitset_t));
  if(bst == NULL) {
    fprintf(stderr, "failed to allocate %zu bytes\n",sizeof(mp_bitset_t));
    exit(EXIT_FAILURE);
  }
  mp_bitset_alloc(bst, UPPER_LIMIT);

  mp_bitset_setall(bst);
  mp_bitset_clear(bst, 0);      // 0 is not prime b.d.
  mp_bitset_clear(bst, 1);      // 1 is not prime b.d.

  n = mp_bitset_size(bst);
  for (k = 4; k < n; k += 2) {
    mp_bitset_clear(bst, k);
  }
  k = 0;

  while ((k = mp_bitset_nextset(bst, k + 1)) < n) {
    printf("%" PRIu32 "\n", k);
    for (j = k * k; j < n; j += k * 2) {
      mp_bitset_clear(bst, j);
    }
  }
  mp_bitset_free(bst);

  return EXIT_SUCCESS;
}

编译

gcc-4.9 -O3 -g3 -W -Wall -Wextra -Wuninitialized -Wstrict-aliasing -pedantic  -std=c11 tests.c -o tests

(GCC 是 gcc-4.9.real (Ubuntu 4.9.4-2ubuntu1~14.04.1) 4.9.4

【讨论】:

  • do{...}while(0) 块是怎么回事?对我来说看起来像是代码噪音。
  • 你真的不应该为他们做 OP 的家庭作业。
  • @jwdonahue:do { ... } while(0) 成语是您编写可在任何可能出现语句的上下文中使用的宏的方式。请参阅comp.lang.c FAQ,问题 10.4。
  • @jwdonahue 我很确定那个代码不适合作为初学者家庭作业的解决方案,是的,它是为了这个目标而完成的。这是为了证明它不是printf,而是 OP 对筛子的实现。在我写这篇文章时,已经有 2 票接近。我想我会给 OP 几个小时的时间来提出他们的代码(地球是一个球体),如果仍然有必要,请添加我的近距离投票。
【解决方案2】:

由于默认情况下控制台输出是行缓冲的,这是增加时间的原因。 您可以使用 setvbuf 函数来允许仅以块的形式而不是每次迭代打印到控制台/标准输出。

例如

char buffer[256];
setvbuf(stdout, buffer, _IOFBF, sizeof(buffer));

您可以根据需要更改缓冲区的大小。

IOFBF 选项用于完全缓冲,即一旦缓冲区已满,将打印输出。

See setvbuf for more details

【讨论】:

  • 我预测 printf 不会是执行时间的原因,在这种情况下,这个答案将被否决。很可能会发现 OP 的程序效率低下。
  • 按照 OP 提到的数字,程序可以在大约 0.035 秒内生成素数列表,但随后需要 11 秒才能打印列表。另外恕我直言,如果输出较长,滚动会消耗时间(由系统)
  • 程序在没有printf的情况下运行很快但运行缓慢的原因已经解释:编译器优化掉程序。
  • @EricPostpischil 谢谢。我错过了。
  • @EricPostpischil 是的,但问题的标题是“如何在 C 中加速 printf”,这本身就是一个重要且清晰的问题,所以很多人来这里是为了回答 那个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-28
  • 2014-09-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-09
相关资源
最近更新 更多