Асинхронный ввод в c с использованием Windows API: какой метод использовать и почему мой код выполняется синхронно? - PullRequest
8 голосов
/ 15 декабря 2010

У меня есть приложение C, которое генерирует много выходных данных и для которых скорость является критической.Программа представляет собой цикл по большому (8-12 ГБ) двоичному входному файлу, который необходимо читать последовательно.На каждой итерации считанные байты обрабатываются, а выходные данные генерируются и записываются в несколько файлов, но никогда в несколько файлов одновременно.Поэтому, если вы находитесь в точке, где генерируется вывод, и есть 4 выходных файла, которые вы записываете либо в файл 0, либо в 1, либо в 2, либо 3. В конце итерации я теперь записываю вывод, используя fwrite(), ожидая, таким образом,операция записи до конца.Общее количество операций вывода велико, до 4 миллионов на файл, а размер вывода файлов варьируется от 100 МБ до 3,5 ГБ.Программа работает на базовом многоядерном процессоре.

Я хочу записать вывод в отдельном потоке и знаю, что это можно сделать с помощью

  1. Асинхронный ввод / вывод
  2. Создание потоков
  3. Порты завершения ввода / вывода

У меня есть 2 типа вопросов, а именно концептуальные и специфичные для кода.

Концептуальный вопрос

Какой будет наилучший подход.Обратите внимание, что приложение должно быть переносимым на Linux, однако я не понимаю, как это было бы очень важно для моего выбора для 1-3, так как я бы написал обертку вокруг чего-либо конкретного ядра / API.Для меня самый важный критерий - скорость.Я читал, что вариант 1 вряд ли увеличит производительность программы и что ядро ​​в любом случае создает новые потоки для операции ввода / вывода, поэтому почему бы не использовать вариант (2) немедленно с тем преимуществом, которое кажетсяпроще в программировании (также, поскольку мне не удалось выполнить опцию (1), см. ниже проблемы с кодом).

Обратите внимание, что я прочитал https://stackoverflow.com/questions/3689759/how-can-i-run-a-specific-function-of-thread-asynchronously-in-c-c,, но я не вижу мотивации того, что использовать, основываясь нахарактер приложения.Поэтому я надеюсь, что кто-нибудь сможет дать мне совет, какой будет лучше в моей ситуации.Также из книги «Системное программирование Windows» Джонсона М. Харта я знаю, что рекомендация заключается в использовании потоков, в основном из-за простоты.Однако будет ли он также самым быстрым?

Кодовый вопрос

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

Чтобы уменьшить время выполнения, я пытаюсь записать вывод с помощью нового потока, используя WINAPI через CreateFile() с FILE_FLAGGED_OVERLAP с наложенной структурой.Я создал пример программы, в которой я пытаюсь заставить это работать.Однако я столкнулся с двумя проблемами:

  1. Файл открывается только в режиме наложения при удалении уже существующего файла (я пробовал использовать CreateFile в разных режимах (CREATE_ALWAYS,CREATE_NEW, OPEN_EXISTING), но это не помогает).

  2. Только первый WriteFile выполняется асинхронно.Остальные команды WriteFile являются синхронными.По этой проблеме я уже консультировался http://support.microsoft.com/kb/156932. Кажется, проблема, с которой я столкнулся, связана с тем фактом, что «любая операция записи в файл, которая увеличивает его длину, будет синхронной».Я уже пытался решить эту проблему, увеличив размер файла / допустимый размер данных (закомментированная область в коде).Тем не менее, я до сих пор не заставить его работать.Я осознаю тот факт, что может быть так, чтобы получить максимальную отдачу от асинхронного ввода-вывода, я должен CreateFile с FILE_FLAG_NO_BUFFERING, однако я также не могу заставить это работать.

Обратите внимание, что программа создает файл размером около 120 МБ в пути исполнения.Также обратите внимание, что печатные выражения "не в порядке" нежелательны, я хотел бы, чтобы на моем экране появлялось сообщение "могу работать в фоновом режиме" ... Что здесь не так?

#include <windows.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define ASYNC  // remove this definition to run synchronously (i.e. using fwrite)

#ifdef ASYNC
    struct _OVERLAPPED *pOverlapped;
    HANDLE *pEventH;
    HANDLE *pFile;
#else
    FILE *pFile;
#endif

#define DIM_X   100
#define DIM_Y   150000

#define _PRINTERROR(msgs)\
 {printf("file: %s, line: %d, %s",__FILE__,__LINE__,msgs);\
 fflush(stdout);\
 return 0;}    \

#define _PRINTF(msgs)\
 {printf(msgs);\
  fflush(stdout);}      \

#define _START_TIMER       \
 time_t time1,time2;       \
 clock_t clock1;        \
 time(&time1);        \
 printf("start time: %s",ctime(&time1));  \
 fflush(stdout);

#define _END_TIMER\
 time(&time2);\
 clock1 = clock();\
 printf("end time: %s",ctime(&time2));\
 printf("elapsed processor time: %.2f\n",(((float)clock1)/CLOCKS_PER_SEC));\
 fflush(stdout);

double  aio_dat[DIM_Y] = {0};

double do_compute(double A,double B, int arr_len);

int main()
{
 _START_TIMER;

 const char *pName = "test1.bin";

    DWORD dwBytesToWrite;
    BOOL bErrorFlag = FALSE;

 int j=0;
 int i=0;
 int fOverlapped=0;

    #ifdef ASYNC
     // create / open the file
        pFile=CreateFile(pName,
            GENERIC_WRITE,   // open for writing
            0,               // share write access
            NULL,            // default security
            CREATE_ALWAYS,   // create new/overwrite existing
         FILE_FLAG_OVERLAPPED,  // | FILE_FLAG_NO_BUFFERING,   // overlapped file
         NULL);           // no attr. template

  // check whether file opening was ok
     if(pFile==INVALID_HANDLE_VALUE){
   printf("%x\n",GetLastError());
   _PRINTERROR("file not opened properly\n");
  }

  // make the overlapped structure
     pOverlapped = calloc(1,sizeof(struct _OVERLAPPED)); 
  pOverlapped->Offset = 0;
  pOverlapped->OffsetHigh = 0;

   // put event handle in overlapped structure
  if(!(pOverlapped->hEvent = CreateEvent(NULL,TRUE,FALSE,NULL))){
   printf("%x\n",GetLastError());
   _PRINTERROR("error in createevent\n");
  }
 #else
  pFile = fopen(pName,"wb");
 #endif 

 // create some output 
 for(j=0;j<DIM_Y;j++){
     aio_dat[j] = do_compute(i, j, DIM_X);
 }

 // determine how many bytes should be written
  dwBytesToWrite = (DWORD)sizeof(aio_dat);

 for(i=0;i<DIM_X;i++){ // do this DIM_X times

        #ifdef ASYNC
         //if(i>0){
      //SetFilePointer(pFile,dwBytesToWrite,NULL,FILE_CURRENT);
      //if(!(SetEndOfFile(pFile))){
      // printf("%i\n",pFile);
      // _PRINTERROR("error in set end of file\n");
      //}
      //SetFilePointer(pFile,-dwBytesToWrite,NULL,FILE_CURRENT);
      //}

      // write the bytes
      if(!(bErrorFlag = WriteFile(pFile,aio_dat,dwBytesToWrite,NULL,pOverlapped))){
       // check whether io pending or some other error
       if(GetLastError()!=ERROR_IO_PENDING){
    printf("lasterror: %x\n",GetLastError());
    _PRINTERROR("error while writing file\n");
       }
       else{
        fOverlapped=1;
       }
      }
          else{
       // if you get here output got immediately written; bad!
       fOverlapped=0;
      }

      if(fOverlapped){   
       // do background, this msgs is what I want to see
       for(j=0;j<DIM_Y;j++){
        aio_dat[j] = do_compute(i, j, DIM_X);
       }
       for(j=0;j<DIM_Y;j++){
        aio_dat[j] = do_compute(i, j, DIM_X);
       }

       _PRINTF("can do work in background\n");
      }
      else{
       // not overlapped, this message is bad
       _PRINTF("not ok\n");
      } 

                // wait to continue
      if((WaitForSingleObject(pOverlapped->hEvent,INFINITE))!=WAIT_OBJECT_0){
       _PRINTERROR("waiting did not succeed\n");
      }

      // reset event structure
      if(!(ResetEvent(pOverlapped->hEvent))){
       printf("%x\n",GetLastError());
       _PRINTERROR("error in resetevent\n");
      }

      pOverlapped->Offset+=dwBytesToWrite;

  #else
      fwrite(aio_dat,sizeof(double),DIM_Y,pFile);
   for(j=0;j<DIM_Y;j++){
    aio_dat[j] = do_compute(i, j, DIM_X);
   }
   for(j=0;j<DIM_Y;j++){
    aio_dat[j] = do_compute(i, j, DIM_X);
   }
  #endif
 }

 #ifdef ASYNC
     CloseHandle(pFile);
  free(pOverlapped);
 #else
     fclose(pFile);
 #endif

 _END_TIMER;

 return 1;
} 

double do_compute(double A,double B, int arr_len)
{
  int i;
  double   res = 0;
  double  *xA = malloc(arr_len * sizeof(double));
  double  *xB = malloc(arr_len * sizeof(double));

  if ( !xA || !xB )
   abort();

  for (i = 0; i < arr_len; i++) {
   xA[i] = sin(A);
   xB[i] = cos(B);
   res = res + xA[i]*xA[i];
  }

  free(xA);
  free(xB);

  return res;
}

Полезные ссылки

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

Ответы [ 3 ]

2 голосов
/ 16 декабря 2010

Вы должны быть в состоянии заставить это работать, используя структуру OVERLAPPED.

Вы на правильном пути: система не позволяет писать асинхронно, потому что каждый WriteFile увеличивает размер файла.Однако вы неправильно делаете расширение размера файла.Простой вызов SetFileSize на самом деле не зарезервирует место в MFT.Используйте функцию SetFileValidData.Это выделит кластеры для вашего файла (обратите внимание, что они будут содержать весь мусор, который был на диске), и вы сможете параллельно выполнять WriteFile и ваши вычисления.

Я бы держался подальше от FILE_FLAG_NO_BUFFERING.Я полагаю, вы после большей производительности с параллелизмом?Не мешайте кешу выполнять свою работу.

1 голос
/ 16 декабря 2010

Другой вариант, который вы не учли, - это файл с отображенной памятью. Они доступны в Windows и Linux. Существует удобная абстракция Boost, которую вы можете использовать.

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

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

0 голосов
/ 15 декабря 2010

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

Если у вас есть какой-нибудь модный диск, например, SSD или виртуальный RAM, параллельная запись может быть быстрее.Вы должны создать файл в полном размере, а затем выполнить параллельную магию.

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

Так что, imho, вы должны использовать последовательную запись или параллельную запись на несколько дисков.

hth

...