【问题标题】:Zero copy in using vmsplice/splice in Linux在 Linux 中使用 vmsplice/splice 的零拷贝
【发布时间】:2016-02-14 18:49:58
【问题描述】:

我正在尝试使用零复制语义在 linux 中工作 vmsplice()/splice() 但我没有看到任何性能改进。这个 在 linux 3.10 上,在 3.0.0 和 2.6.32 上尝试过。以下代码尝试 做文件写入,我也试过网络套接字 writes(),不能 看到任何改进。

谁能告诉我我做错了什么?

有没有人在生产中使用 vmsplice()/splice() 得到改进?

#include <assert.h>
#include <fcntl.h>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#include <vector>

const char *filename = "Test-File";
const int block_size = 4 * 1024;
const int file_size = 4 * 1024 * 1024;

using namespace std;

int pipes[2];
vector<char *> file_data;

static int NowUsecs() {
  struct timeval tv;
  const int err = gettimeofday(&tv, NULL);
  assert(err >= 0);
  return tv.tv_sec * 1000000LL + tv.tv_usec;
}

void CreateData() {
  for (int xx = 0; xx < file_size / block_size; ++xx) {
    // The data buffer to fill.
    char *data = NULL;
    assert(posix_memalign(reinterpret_cast<void **>(&data), 4096, block_size) == 0);
    file_data.emplace_back(data);
  }
}

int SpliceWrite(int fd, char *buf, int buf_len) {
  int len = buf_len;
  struct iovec iov;
  iov.iov_base = buf;
  iov.iov_len = len;

  while (len) {
    int ret = vmsplice(pipes[1], &iov, 1, SPLICE_F_GIFT);
    assert(ret >= 0);
    if (!ret)
      break;
    len -= ret;
    if (len) {
      auto ptr = static_cast<char *>(iov.iov_base);
      ptr += ret;
      iov.iov_base = ptr;
      iov.iov_len -= ret;
    }
  }

  len = buf_len;
  while (len) {
    int ret = splice(pipes[0], NULL, fd, NULL, len, SPLICE_F_MOVE);
    assert(ret >= 0);
    if (!ret)
      break;

    len -= ret;
  }

  return 1;
}

int WriteToFile(const char *filename, bool use_splice) {
  // Open and write to the file.
   mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
  int fd = open(filename, O_CREAT | O_RDWR, mode);
  assert(fd >= 0);

  const int start = NowUsecs();
  for (int xx = 0; xx < file_size / block_size; ++xx) {
    if (use_splice) {
      SpliceWrite(fd, file_data[xx], block_size);
    } else {
      assert(write(fd, file_data[xx], block_size) == block_size);
    }
  }
  const int time = NowUsecs() - start;

  // Close file.
  assert(close(fd) == 0);

  return time;
}

void ValidateData() {
  // Open and read from file.
  const int fd = open(filename, O_RDWR);
  assert(fd >= 0);

  char *read_buf = (char *)malloc(block_size);
  for (int xx = 0; xx < file_size / block_size; ++xx) {
    assert(read(fd, read_buf, block_size) == block_size);
    assert(memcmp(read_buf, file_data[xx], block_size) == 0);
  }

  // Close file.
  assert(close(fd) == 0);
  assert(unlink(filename) == 0);
}

int main(int argc, char **argv) {
  auto res = pipe(pipes);
  assert(res == 0);

  CreateData();
  const int without_splice = WriteToFile(filename, false /* use splice */);
  ValidateData();
  const int with_splice = WriteToFile(filename, true /* use splice */);
  ValidateData();

  cout << "TIME WITH SPLICE: " << with_splice << endl;
  cout << "TIME WITHOUT SPLICE: " << without_splice << endl;

  return 0;
}

【问题讨论】:

  • 在我的系统上,上面的程序显示在多次运行时使用拼接可以提高 10% - 50% 的执行速度。
  • 我调查了这一点。在虚拟化环境中它没有显示出任何改进,但在裸机上我能够看到改进。

标签: linux sockets networking linux-kernel splice


【解决方案1】:

几年前我做了一个概念验证,使用经过优化的、特别定制的 vmsplice() 代码获得了 4 倍的加速。这是针对基于通用套接字/write() 的解决方案进行测量的。 This blog post from natsys-lab 与我的发现相呼应。但我相信你需要有准确的用例才能接近这个数字。

那你做错了什么?首先,我认为您测量的是错误的东西。直接写入文件时,您有 1 个系统调用,即 write()。而且你实际上并没有复制数据(除了内核)。当您有一个包含要写入磁盘的数据的缓冲区时,它不会比这更快。

在您的 vmsplice/splice 设置中,您仍在将数据复制到内核中,但您总共有 2 个系统调用 vmsplice()+splice() 将其保存到磁盘。与 write() 相同的速度可能只是 Linux 系统调用速度的证明:-)

更“公平”的设置是编写一个程序,从标准输入读取()并将相同的数据写入()到标准输出。编写一个相同的程序,只需将标准输入 splice() 插入文件(或在运行时将标准输出指向文件)。尽管此设置可能太简单而无法真正显示任何内容。

除此之外:vmsplice() 的一个(未记录的?)功能是您还可以使用 to 从管道中读取数据。我在我的旧 POC 中使用了它。它基本上只是一个基于使用 vmsplice() 传递内存页面的想法的 IPC 层。

注意:NowUsecs() 可能会溢出 int

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-05-25
    • 2012-04-22
    • 1970-01-01
    • 2014-10-25
    • 2017-07-06
    • 1970-01-01
    • 2013-06-25
    • 1970-01-01
    相关资源
    最近更新 更多