Как писать в несколько файлов на разных дисках одновременно в одном потоке с DMA?

Я использую aio для записи нескольких файлов на разные диски в одном потоке. Когда я использую буферизованную запись, обработка ввода-вывода выполняется параллельно. Но нагрузка на процессор очень высока. Когда я открываю файлы с флагом DIRECT, обработка ввода-вывода не выполняется параллельно.

Как писать в несколько файлов на разных дисках одновременно в одном потоке с DMA?

#include <malloc.h>
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <sstream>
#include <inttypes.h>

#include <unistd.h>
#include <fcntl.h>
#include <sys/syscall.h>
#include <linux/aio_abi.h>

using namespace std;

long double timeDiff(timespec start, timespec end) {
const long double s = start.tv_sec + start.tv_nsec * 1.0e-9;
const long double e = end.tv_sec   + end.tv_nsec   * 1.0e-9;
return e - s;
}

// nr: maximum number of requests that can simultaneously reside in the context.
inline int io_setup(unsigned nr, aio_context_t *ctxp) {
return syscall(__NR_io_setup, nr, ctxp);
}

inline int io_destroy(aio_context_t ctx) {
return syscall(__NR_io_destroy, ctx);
}

// Every I/O request that is submitted to 
inline int io_submit(aio_context_t ctx, long nr, struct iocb **iocbpp) {
return syscall(__NR_io_submit, ctx, nr, iocbpp);
}

// For every completed I/O request kernel creates an io_event structure.
// minimal number of events one wants to get.
// maximum number of events one wants to get.
inline int io_getevents(aio_context_t ctx, long min_nr, long max_nr,
    struct io_event *events, struct timespec *timeout) {
return syscall(__NR_io_getevents, ctx, min_nr, max_nr, events, timeout);
}

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

// prepare data
const unsigned int kAlignment = 4096;
const long data_size = 1600 * 1024 * 12 / 8; 
//const long data_size = 2448 * 1344 * 12 / 8; 
void * data = memalign(kAlignment, data_size);
memset(data, 0, data_size);
//for (int i = 0; i < data_size; ++i)
//   data[i] = 'A';

// prepare fd
//const int file_num = 3;
const int file_num = 2;
int fd_arr[file_num];
for (int i = 0; i < file_num; ++i) {
    ostringstream filename;
    if (i == 0) {
        //filename << "/data/test";
        filename << "/test";
    } else {
        filename << "/data" << i << "/test";
    }
    //filename << "/data/test" << i;
    int fd = open(filename.str().c_str(), O_WRONLY | O_NONBLOCK | O_CREAT | O_DIRECT | O_APPEND, 0644);
    //int fd = open(filename.str().c_str(), O_WRONLY | O_NONBLOCK | O_CREAT | O_DIRECT, 0644);
    //int fd = open(filename.str().c_str(), O_WRONLY | O_NONBLOCK | O_CREAT, 0644);
    if (fd < 0) {
        perror("open");
        return -1;
    }
    fd_arr[i] = fd;
}

aio_context_t ctx;
struct io_event events[file_num];
int ret;
ctx = 0;

ret = io_setup(1000, &ctx);
if (ret < 0) {
    perror("io_setup");
    return -1;
}

struct iocb cbs[file_num];
for (int i = 0; i < file_num; ++i) {
    memset(&cbs[i], 0, sizeof(cbs[i]));
}
struct iocb * cbs_pointer[file_num];
for (int i = 0; i < file_num; ++i) {
    /* setup I/O control block */
    cbs_pointer[i] = &cbs[i];
    cbs[i].aio_fildes = fd_arr[i];
    cbs[i].aio_lio_opcode = IOCB_CMD_PWRITE; // IOCV_CMD
    cbs[i].aio_nbytes = data_size;
}

timespec tStart, tCurr;
clock_gettime(CLOCK_REALTIME, &tStart);

const int frame_num = 10000;
for (int k = 0; k < frame_num; ++k) {

    for (int i = 0; i < file_num; ++i) {
        /* setup I/O control block */
        cbs[i].aio_buf = (uint64_t)data;
        //cbs[i].aio_offset = k * data_size;
    }

    ret = io_submit(ctx, file_num, cbs_pointer);
    if (ret < 0) {
        perror("io_submit");
        return -1;
    }

    /* get reply */
    ret = io_getevents(ctx, file_num, file_num, events, NULL);
    //printf("events: %d, k: %d\n", ret, k);
}

clock_gettime(CLOCK_REALTIME, &tCurr);
cout << "frame: " << frame_num << " time: " << timeDiff(tStart, tCurr) << endl;

ret = io_destroy(ctx);
if (ret < 0) {
    perror("io_destroy");
    return -1;
}

// close fd
for (int i = 0; i < file_num; ++i) {
    fsync(fd_arr[i]);
    close(fd_arr[i]);
}
return 0;
}

person ceys    schedule 07.07.2017    source источник


Ответы (1)


Linux может сделать запись действительно асинхронной тогда и только тогда, когда записываемые физические экстенты уже выделены на диске. В противном случае он должен взять мьютекс и сначала выполнить выделение, таким образом, все становится синхронным.

Обратите внимание, что усечение файла до новой длины обычно не приводит к выделению базовых экстентов. Сначала вам нужно предварительно записать содержимое. После этого перезапись одних и тех же экстентов теперь будет выполняться асинхронно и, таким образом, станет параллельным.

Как вы могли догадаться, асинхронный файловый ввод-вывод в Linux не очень хорош, хотя со временем он становится лучше. Windows или FreeBSD имеют гораздо более совершенные реализации. Даже OS X не страшна. Вместо этого используйте любой из них.

person Niall Douglas    schedule 08.07.2017
comment
Благодарю вас! В чем разница между открытием файла с флагом O_DIRECT? Без него общая пропускная способность больше, чем пропускная способность записи на один диск, но нагрузка на ЦП высока. При этом общая пропускная способность такая же, как пропускная способность одного диска. - person ceys; 11.07.2017
comment
Без O_DIRECT все операции ввода-вывода сначала идут в кеш страниц ядра, т. е. memcpy(), а затем, на более позднем этапе, они передаются DMA на диск. При включенном O_DIRECT, если блок, который вы записываете, представляет собой блок размером 4 КБ, выровненный по границе 4 КБ, и вы не обращаетесь к этой памяти во время ввода-вывода, велика вероятность, что она будет подвергнута DMA напрямую без промежуточный memcpy(). Конечно, если вы выделяете новые экстенты, как я уже упоминал, это глобальный мьютекс, поэтому вы сериализуете и потеряете параллелизм. - person Niall Douglas; 13.07.2017