2016-02-14 80 views
3

我想獲得在Linux中使用 vmsplice()/ splice()工作的零拷貝語義,但我沒有看到任何性能改進。這個 在linux 3.10上,在3.0.0和2.6.32上試過。下面的代碼嘗試 做文件寫入,我也嘗試過網絡套接字寫入(),也不能, 看到任何改進。零拷貝在Linux中使用vmsplice/splice

有人可以告訴我做錯了什麼嗎?

有沒有人在生產中使用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; 
} 
+0

我的系統上,上面的程序顯示10%之間 - 快50%的執行與多個運行拼接。 – TheCodeArtist

+0

我調查了這一點。在虛擬環境中,它沒有顯示出任何改進,但是在裸機上我能夠看到改進。 – WhiteZ

回答

0

我做了一個驗證的概念幾年前,我使用優化的,專門定製,vmsplice()的代碼作爲了4倍的速度提升。這是針對基於通用套接字/寫入()的解決方案進行測量的。 This blog post from natsys-lab迴應我的發現。但我相信你需要準確的用例才能接近這個數字。

那麼你在做什麼錯?主要我認爲你正在測量錯誤的東西。直接寫入文件時,您有1個系統調用,即write()。而你實際上並沒有複製數據(除了內核)。當你想要寫入磁盤的數據有一個緩衝區時,它不會比這更快。

在你的vmsplice/splice設置中,你仍然在拷貝你的數據到內核中,但總共有2個系統調用vmsplice()+ splice()來獲取它到磁盤。與write()相同的速度可能僅僅是Linux系統調用速度的一個證明:-)

更「公平」的設置是編寫一個程序,從stdin讀取()和寫入()相同的數據標準輸出。編寫一個相同的程序,將stdin簡單地拼接()到一個文件中(或者在運行時將stdout指向一個文件)。雖然這個設置可能太簡單,不能真正展示任何東西。另外:vmsplice()的一個(未公開的)特性是你也可以用來從管道讀取數據。我在舊的POC中使用了這個。它基本上只是一個基於使用vmsplice()傳遞內存頁的想法的IPC層。

注:NowUsecs()可能溢出INT