为什么 'dd' 从管道中读取的速度比我使用 ifstream 写的程序快?

8
我有两个程序,它们通过 Linux 管道(命名管道或非命名管道)相互传递数据。我需要在这两个程序之间实现大约 2600MB/s 的传输速率,但目前只能达到约 2200MB/s 的较慢速率。然而,我发现如果我用 'dd' 替换我的第二个进程,传输速率就能跳到 3000MB/s以上。是我的程序从管道中读取数据的方式不如 'dd' 高效吗?我该如何提高这种吞吐量?'ifstream' 读取二进制数据的方法是否天生比其他方法慢?
为了总结这两种情况:
场景1: Program1 -> [命名管道] -> Program2 产生约 2200MB/s 的传输速率。
场景2: Program1 -> [命名管道] -> 'dd if=pipename of=/dev/null bs=8M' 产生约 3000MB/s 的传输速率。
以下是我当前程序读取管道的方式:
ifstream inputFile;
inputFile.open(inputFileName.c_str(), ios::in | ios::binary);
while (keepLooping)
{
    inputFile.read(&buffer[0], 8*1024*1024);
    bytesRead = inputFile.gcount();
    //Do something with data
}

更新:

我现在尝试使用'read(fd, &buffer[0], 8*1024*1024)'代替istream,似乎显示了轻微的改善(但不如dd)。

我还尝试使用stream->rdbuf()->sgetn(&buffer[0], 8*1024*1024)代替stream->read(),但没有帮助。


2
如果有帮助的话,这是 dd 的源代码:http://lingrok.org/xref/coreutils/src/dd.c - jason
1
我相信fstream在处理区域设置时会有一些开销,但是dd似乎使用了没有任何相关区域检查的read。即使在ios::binary模式下,你仍然需要支付一些这种惩罚。如果你使用FILE*,你的性能会如何变化?虽然它不像C++那样优雅,但如果性能是你关心的问题... - Dan Lecocq
使用FILE*还是直接使用文件描述符进行读取更好? - KyleL
“对数据进行操作”是什么意思?由于dd命令只是将数据输出,因此在您的情况下输出到/dev/null。 - Dave S
我尝试使用像dd这样的直接文件描述符与'open()'和'read()',但没有看到任何改进。 - KyleL
显示剩余4条评论
2个回答

3

这个差异似乎是使用数组而不是std::vector引起的,我仍然很难相信。下面是我的两组代码进行比较。第一组可以以大约2500 MB/s的速率从程序1中读入数据。第二组则可以以3100 MB/s的速率读入。

程序1(2500 MB/s)

int main(int argc, char **argv)
{
    int fd = open("/tmp/fifo2", O_RDONLY);

    std::vector<char> buf(8*1024*1024);

    while(1)
    {
       read(fd, &buf[0], 8*1024*1024);
    }
}

Program 2 (3100 MB/s)

int main(int argc, char **argv)
{

    int fd = open("/tmp/fifo2", O_RDONLY);

    char buf[8*1024*1024];

    while(1)
    {
       read(fd, &buf[0], 8*1024*1024);
    }
}

这两个程序都使用gcc版本4.4.6编译,并且使用-O3进行编译。如果有人能解释原因,我会非常感兴趣(因为我理解std::vector基本上是数组的一个包装器)。

编辑:我刚刚测试了下面的第三个程序,它可以使用ifstream并以3000 MB/s的速度运行。所以看起来使用ifstream而不是'read()'会导致非常轻微的性能下降,远小于使用std::vector带来的影响。

程序3(3000 MB/s)

int main(int argc, char **argv)
{
    ifstream file("/tmp/fifo2", ios::in | ios::binary);

    char buf[8*1024*1024];

    while(1)
    {
       file.read(&buf[0], 32*1024);
    }
}

编辑2:

我修改了程序2的代码,使用malloc函数分配内存,而不是使用栈上的内存,性能下降到与vector相匹配。感谢ipc提供的提示。


我会期望原始接口版本运行更快。为什么这让你感到惊讶? - Randy Howard
1
向量和普通静态字符数组的区别在于,字符数组位于堆栈上,而向量在堆上分配数据。我有点惊讶你没有遇到堆栈溢出的情况。 - ipc
1
@ipc 堆内存并不会变慢,而且由于向量将其数据连续存储,所以差异只是额外的指针间接引用吗? - s3rius
@ipc 我进行了一项测试,你关于堆的观点是正确的。将程序2更改为使用malloc分配的内存会将性能降至与程序1相同的水平。 - KyleL

1
这段代码使用 g++ -Ofast 编译:
int main(int argc, char *argv[])
{
  if (argc != 2) return -1;
  std::ifstream in(argv[1]);
  std::vector<char> buf(8*1024*1024);
  in.rdbuf()->pubsetbuf(&buf[0], buf.size());
  std::ios_base::sync_with_stdio(false);
  std::cout << in.rdbuf();
}

并不表现得很差。
$ time <<this program>> <<big input file>> >/dev/null
0.20s user 3.50s system 9% cpu 40.548 total
$ time dd if=<<big input file>> bs=8M > /dev/null
0.01s user 3.84s system 9% cpu 40.786 total

你需要考虑到,std::coutstdout 共享缓冲区,如果不关闭,会非常耗时。所以如果想要速度并且不打算使用 C 的输入输出方法(它们本来就更慢),请调用std::ios_base::sync_with_stdio(false);
此外,在 C++ 中进行原始和快速的输入/输出,请使用通过 rdbuf() 获得的 streambuf 方法。

如果我使用命名管道而不是无名管道,那么std::ios_base::sync_with_stdio(false)会有帮助吗? 此外,pubsetbuf()有什么影响?它只是增加了streambuf的缓冲区大小吗? - KyleL
1
pubsetbuf()函数设置内部缓冲区大小。如果您在代码中调用read()函数,它仍然使用较小的默认缓冲区大小。如果您正在使用std::cinstd::cout,则始终可以使用std::ios_base::sync_with_stdio(false)来提高性能。 - ipc
管道是命名的还是未命名的并不会有太大的区别。你可以自己检查一下,因为我已经向你展示了我的代码。 - ipc

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