2017-07-07 16 views
1

J'utilise aio pour écrire plusieurs fichiers sur un disque différent dans un thread. Lorsque j'utilise l'écriture tamponnée, le traitement des E/S est simultané. Mais les charges de cpu sont très élevées. Lorsque j'ouvre des fichiers avec un indicateur DIRECT, le traitement des E/S n'est pas simultané.Comment écrire simultanément plusieurs fichiers sur différents disques dans un thread avec DMA?

Comment écrire simultanément plusieurs fichiers sur différents disques dans un fil avec 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; 
} 

Répondre

1

Linux peut rendre les écritures réellement asynchrones si et seulement si les extensions physiques écrites sont déjà allouées sur le disque. Sinon, il doit prendre un mutex et faire l'allocation en premier, donc tout devient synchrone.

Notez que tronquer le fichier à une nouvelle longueur n'alloue généralement pas réellement les extensions sous-jacentes. Vous devez d'abord pré-écrire le contenu. Par la suite, réécrivant les mêmes étendues seront maintenant effectuées async et deviendront ainsi concurrentes.

Comme vous pouvez le constater, les entrées/sorties de fichiers asynchrones sous Linux ne sont pas géniales, bien qu'elles continuent à s'améliorer avec le temps. Windows ou FreeBSD ont des implémentations bien supérieures. Même OS X n'est pas terrible. Utilisez n'importe lequel d'entre eux à la place.

+0

Merci! Quelle est la différence entre l'ouverture d'un fichier avec un indicateur O_DIRECT? Sans cela, la bande passante totale est supérieure à une bande passante d'écriture de disque, mais les charges du processeur sont élevées. Avec elle, la bande passante totale est la même que celle d'un disque. – ceys

+0

Sans 'O_DIRECT', toutes les E/S vont d'abord dans le cache de la page du noyau, c'est-à-dire' memcpy() ', et à un stade plus avancé, elles recevront DMAed sur le disque. Si 'O_DIRECT' est activé, si le bloc que vous écrivez est un multiple de 4Ko aligné sur une limite de 4Kb et que vous ** n'accédez pas à cette mémoire pendant les E/S **, il y a de fortes chances que DMA soit directement sans intervenant 'memcpy()'. Bien sûr, si vous allouez de nouvelles extensions, comme je l'ai mentionné, c'est un mutex global, donc vous sérialiserez et perdrez la simultanéité. –