【问题标题】:Why is my "cat" function with system calls slower compared to Linux's "cat"?为什么我的带有系统调用的“cat”函数比 Linux 的“cat”慢?
【发布时间】:2010-10-20 15:38:33
【问题描述】:

我已经在 C 中使用系统调用(打开、读取和写入)来模拟 Linux 系统中的“cat”函数,它比真实的要慢...

我使用与真正的“猫”相同的缓冲区大小,并使用“strace”,我认为它进行了相同数量的系统调用。但是我的“猫”的输出比真正的“猫”要慢一点。

这是我的代码:

#define BUFSIZ 32768

int sysWriteBuffer(int fdout, char *buffer, ssize_t readBytes) {
    ssize_t writtenBytes = 0;

    while(writtenBytes < readBytes) {
        writtenBytes += write(fdout,
            buffer + writtenBytes, readBytes - writtenBytes);
        if(writtenBytes == -1) {
            return -1;
        }
    }

    return 0;
}

int catPrint(int fdin, int fdout) {
    char buffer[BUFSIZ];
    ssize_t readBytes;

    do {
        readBytes = read(fdin, buffer, BUFSIZ);

        if(readBytes == -1) {
            return -1;
        }

        if(sysWriteBuffer(fdout, buffer, readBytes) == -1) {
            return -1;
        }
    } while(readBytes > 0);

    return 0;
}

我正在读取一个文件(我将其作为参数传递给 main,我认为这里不需要代码),而不是使用该文件描述符调用 catPrint() 函数,输出描述符为 1,因此它打印到标准输出。

我不明白为什么它会变慢,因为我使用相同的文件进行测试,并且对于两者(真正的“猫”和我的),整个文本只有一个 read() 和一个 write()。整个文本不应该只显示在屏幕上吗?

P.S:我已将此标记为作业,尽管我在这里的问题(为什么它变慢)不是作业的一部分。我只需要使用系统调用创建一个“cat”类型的函数,就完成了。我只是对我的代码有点慢很感兴趣。

我的愚蠢解决了问题:
我只是决定在同一个文件上调用 linux 的原始 cat 几次,一个接一个,我只是意识到我调用它的某些时候它也很慢,就像我自己的一样慢。我想一切都比……还好。

很抱歉像这些人一样浪费你的时间。

【问题讨论】:

  • 恕我直言,homework 标签具有误导性。您的问题涉及一个有趣的背景事实。 homework 意味着乏味的初学者工作或(在规模的另一端)一个测验问题。
  • 顺便说一句,如果错误发生在第二次 write() 上,则错误(即写入返回 -1)处理不正确。
  • 如果您认为更好,您可以删除作业标签... jpalecek 是什么意思?只有一次写入(如在系统调用中)我只有一个辅助功能。如果该辅助函数中的 write() 失败,我需要将 -1 一直返回到调用 catPrint() 的位置...
  • 这里只是问一下——cat 使用系统调用而不是 fread、fwrite 缓冲的吗?
  • @Blingo:cat 很可能使用系统调用而不是 fread() 和 fwrite()。它不需要标准 I/O(fread() 等)优化的小型 I/O 单元;它处理文件的大块 - 可能比标准 I/O 缓冲区大小更大。

标签: c linux system-calls cat


【解决方案1】:

多少钱?典型的猫是这样的

char bufr[BUFSIZ];
ssize_t len;

while((len=read(fdin, bufr, BUFSIZ)) >0)
     write(fdout, bufr, len);

节省了一些指令。

【讨论】:

  • 这可能是规范版本,但不正确(例如,如果在您 write() 时出现信号)
  • 你错过了“类似”的哪一部分?
  • 就像我说的,原来的猫和我的猫,都调用一个缓冲区大小为 32768 的 read() 和一个具有相同缓冲区大小的 write() 和最后一个 read() (当它不读取任何其他内容并终止时)。
  • 对,但是您的版本有多个测试 == 更多说明。但是,听起来真正的问题是 CPU 周期争用。
【解决方案2】:

也许你编译时没有优化(或没有那么高的优化设置)?

此外,您的代码将调用一次sysWriteBuffer,而readBytes 等于零——也许这(部分)解释了它?

您也可以内联 sysWriteBuffer(通过编译器开关或手动)。

"inlining" 表示将函数体复制到其调用站点,以消除调用函数的开销。有时编译器会自动执行此操作(我认为 -O3 在 gcc 中启用了此优化)。您还可以在gcc 中使用inline 关键字来告诉编译器内联函数。如果这样做,您的声明将如下所示:

static inline int sysWriteBuffer(int fdout, char *buffer, ssize_t readBytes) {
....

【讨论】:

  • 如果你在 cat 上使用 strace,你会发现它也在那里发生,所以我就离开了它......我正在使用 -O2 标志。
  • 你可以试试“-O3 -funroll_loops”看看效果如何。更好的是确定编译 cat 的确切标志。
  • 请注意,襟翼是 -funroll-loops(第二个连字符不是下划线),无论如何我认为在这种情况下它不会做很多事情。
  • 感谢您对标志的更正——顺便说一句,它是“标志”,而不是“襟翼”(您不希望我们可以编辑这些 cmets 吗?):)
  • 什么是“内联”(这个词甚至存在吗?)sysWriteBuffer?
【解决方案3】:

您是否比较了两者的straces?您可以尝试使用-tt 参数来获取系统调用的时间。

【讨论】:

  • 我对strace的了解不多,试了-tt参数,出现一堆数字,但看不懂他们的意思。
  • 尝试找到读写的部分(输出的格式应该是“time syscall(parameters) = return value”,所以找到read()或者write())并贴出来
【解决方案4】:

研究 mmap(2)。

您将丢弃 ftell/fread 的细节,但如果读取吞吐量真的很重要,它将跳过一层间接性。

【讨论】:

  • 我不能在这个练习中使用其他任何东西。
【解决方案5】:

不比较源代码,很难说。如果您将您的 cat 与 GNU cat 进行比较,请记住,您是在将几小时/几天前的代码与发展了 20 多年的代码进行比较。

您可能想要进行更全面的性能分析,以不同的输入大小、从不同的设备(RAM 磁盘最好)并连续多次运行这两个程序。您必须尝试确定程序中的 WHERE 速度较慢。

由于 cat 本身确实微不足道(并且您在评论中说您已经在优化编译),我敢打赌您观察到的性能影响不是在实际算法中,而是在程序加载时间上。如果系统二进制文件是prelinked(这在当今大多数发行版中很常见),您会发现它的加载速度比您自己编译的任何程序都快(直到您包含您的程序预链接)。

【讨论】:

    【解决方案6】:

    啊,根据您的编辑,您被预读缓冲区咬住了。您不能通过运行一次来​​测试两个并排读取文件的程序。第一个总是较慢,因为文件在磁盘上,一旦文件在内存中,第二个将运行得更快,您必须为每个创建新数据或运行一个然后运行两个,这样它们都可以获得预读缓冲区的好处。

    【讨论】:

      猜你喜欢
      • 2022-11-12
      • 1970-01-01
      • 2017-08-08
      • 2014-10-14
      • 2012-11-25
      • 1970-01-01
      • 2015-09-15
      • 2013-12-13
      • 1970-01-01
      相关资源
      最近更新 更多