在C语言中,将一定数量的字符最快地输出到标准输出的方法是什么?

5
我需要在stdout上打印一定数量的空格,但这个数量不是固定的。我正在使用putchar(),但我不确定它是否快速。在C语言中打印一定数量的字符的最快方法是什么?同时,我不能使用系统函数。
谢谢你的帮助!

这里有很多有趣的答案。当然,你需要测量每个答案的性能才能知道正确的答案。但是随着现代操作系统的发展,它们之间可能没有太大差异,因为瓶颈在于磁盘IO/控制台输出。 - Skizz
7个回答

4

我建议使用fwrite函数。它简单、正确且易于理解。

void put_spaces(int n)
{
    static const char SPACES[32] = "                                ";
    for (; n >= 32; n -= 32)
        fwrite(SPACES, 32, 1, stdout);
    if (n)
        fwrite(SPACES, n, 1, stdout);
}

然而需要注意的是,简单的版本也非常快速:

void put_spaces(int n)
{
    while (n--)
        putchar(' ');
}

为什么这很快?在大多数系统中,putchar是一个宏,大部分时间直接写入缓冲区。如果你不确定它是否快速,正确的答案是对你的应用程序进行性能分析,而不是“先优化”。
避免使用malloc(它只是不必要的),puts(每次调用它都会添加'\n'),以及printf(对于这样一个简单的任务来说太复杂了)。

1

我会尝试使用系统命令而不是自己编写。

类似这样:

void print_spaces(unsigned int number_of_spaces) {
  char* spaces = malloc(sizeof(char)*number_of_spaces + 1);
  memset (spaces,' ',number_of_spaces);
  spaces[number_of_spaces] = '\0';
  printf("%s",spaces);
  free(spaces);
}

这个可以解决问题。


0

printf() 可以让你调整要打印的空格数,但是这必须在格式字符串中声明。可以参考这里

char format[256];
sprintf(format, "%%%ds", number_of_spaces); // creates the format string
printf(format, " ");

这个问题(以及paxdiablo的回答)不是依赖于printf的实现吗?printf的填充难道不是一个字符一个字符地循环吗? - Skizz
@Skizz,库函数的实现方式通常比你期望的天真C代码编译出来的要聪明得多。这是因为它们经常被使用。 - paxdiablo
@Skizz:不,printf的格式选项已经被定义为标准,不应该改变。 - Constantinius
正如paxdiablo在我的答案评论中提到的那样,printf仍然需要解析格式字符串。 - Skizz

0
我不懂C语言,但是这里有一个基本的想法。 创建一个大小为8192的数组,并用空格完全填充该特定数组,现在您可以使用puts或write系统调用或使用一些高效的东西,然后打印此数组。 这里我有一个Go代码片段,但如果您喜欢C语言,您可以看到一个example,它实际上是GNU的yes程序,非常快地打印东西,那里有跟进的解释。
package main

import (
    "bufio"
    "os"
)

func main() {
    t := []byte{'y', '\n'}
    var used int
    const tot = 8192
    buf := make([]byte, 0, tot)

    for used < tot {
        buf = append(buf, t...)
        used += 2
    }

    //Filled complete array named as buf with "y\n"
    w := bufio.NewWriter(os.Stdout)
    for {
        w.Write(buf) //using write system call to print.
    }
    w.Flush()
}

//syscall.Write({without buf}) : 1.40MiB/s
//syscall.Write(buf) : 1.5GiB/s

0

我猜你所说的“系统函数”是指非标准扩展。如果是这样,那就要看你是想写得最快还是执行得最快了?

如果是前者,并且假设有一个上限,那么你可以使用类似以下的代码:

void outSpaces (unsigned int num) {
    static char *lotsaSpaces = "                       ";
    printf ("%*.*s", num, num, lotsaSpaces);
}

如果是后者,类似这样的东西应该是一个不错的起点:
void outSpaces (unsigned int num) {
    static char *hundredSpaces = "<<insert 100 spaces here>>";
    while (num >= 100) {
        puts (hundredSpaces);
        num -= 100;
    }
    printf ("%*.*s", num, num, hundredSpaces);
}

你需要意识到函数调用即使使用输出缓冲也可能会很耗费资源。在这种情况下,最好一次性调用puts来输出一百个字符,而不是调用putchar一百次。


0

也许:

void PrintSpaces (int num_spaces)
{
  char *spaces = "                    "; /* twenty spaces */
  while (num_spaces > 20)
  {
    puts (spaces);
    num_spaces -= 20;
  }

  if (num_spaces)
  {
    puts (&spaces [19 - num_spaces]);
  }
}

那最后一段很聪明。但我可能会选择使用 puts,因为它没有检查 % 格式说明符的开销。 - paxdiablo
puts 函数会在末尾添加一个换行符。 - Dietrich Epp
@Dietrich:哎呀,你说得很对。fputs不会写入新行,所以你可以使用它——我的C标准库有点生疏了! - Skizz
s/&spaces[ 19 - num_spaces ]/spaces + 19 - num_spaces/ => s/&spaces[ 19 - num_spaces ]/spaces + 19 - num_spaces/ - William Pursell

0
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>

int main() {
    const size_t size = 5;
    char* const data = (char*)malloc(size * sizeof(char) + 1);
    if (!data) {
        return EXIT_FAILURE;
    }
    memset(data, ' ', size);
    data[size] = '\0'; // not needed (in this case)
    fwrite(data, sizeof(char), size, stdout);
    free(data);
    return EXIT_SUCCESS;
}

(如果空格数量不是过分的话)


根据C规范,sizeof(char)始终为1。而为了将数据复制到另一个内存缓冲区中,分配内存缓冲区似乎是非常浪费的。 - Dietrich Epp
我只是用 sizeof(char) 显示意图。对于额外的缓冲区问题,setbuf(stream, NULL) 可能会有所帮助。不确定它在标准输出上是否按预期工作。对于一个实际文件的流,它应该可以工作,并且以二进制模式编写会更有帮助。 - Shadow2531
不,如果你调用 setbuf(stream, NULL),我相当确定这将通过强制IO为每个stdio调用来破坏性能。 - Dietrich Epp
明白了。我对fwrite做出了一些错误的假设。 - Shadow2531

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接