Пример: преобразование файлов с использованием перекрывающегося ввода/вывода и множественной буферизации

Пример: преобразование файлов с использованием перекрывающегося ввода/вывода и множественной буферизации

Программа 2.4 (atou) осуществляла преобразование ASCII-файла к кодировке UNICODE путем последовательной обработки файла, а в главе 5 было показано, как выполнить такую же последовательную обработку с помощью отображения файлов. В программе 14.1 (atouOV) та же самая задача решается с использованием перекрывающегося ввода/вывода и множественных буферов, в которых хранятся записи фиксированного размера.

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

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

Рис. 14.1. Модель асинхронного обновления файла

Программа 14.1. atouOV: преобразование файла с использованием перекрывающегося ввода/вывода 

/* Глава 14. atouOV

   Преобразование файла из кодировки ASCII в кодировку Unicode с использованием перекрывающегося ввода/вывода. Программа работает только в Windows NT. */

#include "EvryThng.h"

#define MAX_OVRLP 4 /* Количество перекрывающихся операций ввода/вывода.*/ 

#define REC_SIZE 0x8000 /* 32 Кбайт: Минимальный размер записи, обеспечивающий приемлемую производительность. */

#define UREC_SIZE 2 * REC_SIZE

int _tmain(int argc, LPTSTR argv[]) {

 HANDLE hInputFile, hOutputFile;

 /* Каждый из элементов определенных ниже массивов переменных */

 /* и структур соответствует отдельной незавершенной операции */

 /* перекрывающегося ввода/вывода. */

 DWORD nin[MAX_OVRLP], nout[MAX_OVRLP], ic, i;

 OVERLAPPED OverLapIn[MAX_OVRLP], OverLapOut[MAX_OVRLP];

 /* Необходимость использования сплошного, двумерного массива */

 /* диктуется Функцией WaitForMultipleObjects. */

 /* Значение 0 первого индекса соответствует чтению, значение 1 – записи.*/

 HANDLE hEvents[2][MAX_OVRLP];

 /* В каждом из определенных ниже двух буферных массивов первый индекс */

 /* нумерует операции ввода/вывода. */

 CHAR AsRec[MAX_OVRLP][REC_SIZE];

 WCHAR UnRec[MAX_OVRLP][REC_SIZE];

 LARGE_INTEGER CurPosIn, CurPosOut, FileSize;

 LONGLONG nRecord, iWaits;

 hInputFile = CreateFile(argv[1], GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);

 hOutputFile = CreateFile(argv[2], GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_FLAG_OVERLAPPED, NULL);

 /* Общее количество записей, подлежащих обработке, вычисляемое */

 /* на основе размера входного файла. Запись, находящаяся в конце, */

 /* может быть неполной. */

 FileSize.LowPart = GetFileSize(hInputFile, &FileSize.HighPart);

 nRecord = FileSize.QuadPart / REC_SIZE;

 if ((FileSize.QuadPart % REC_SIZE) != 0) nRecord++;

 CurPosIn.QuadPart = 0;

 for (ic = 0; ic < MAX_OVRLP; ic++) {

  /* Создать события чтения и записи для каждой структуры OVERLAPPED.*/

  hEvents[0][ic] = OverLapIn[ic].hEvent /* Событие чтения.*/

   = CreateEvent(NULL, TRUE, FALSE, NULL);

  hEvents[1][ic] = OverLapOut[ic].hEvent /* Событие записи. */

   = CreateEvent(NULL, TRUE, FALSE, NULL);

  /* Начальные позиции в файле для каждой структуры OVERLAPPED. */

  OverLapIn[ic].Offset = CurPosIn.LowPart;

  OverLapIn[ic].OffsetHigh = CurPosIn.HighPart;

  /* Инициировать перекрывающуюся операцию чтения для данной структуры OVERLAPPED. */

  if (CurPosIn.QuadPart < FileSize.QuadPart) ReadFile(hInputFile, AsRec[ic], REC_SIZE, &nin[ic], &OverLapIn[ic]); 

  CurPosIn.QuadPart += (LONGLONG)REC_SIZE;

 }

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

 iWaits =0; /* Количество выполненных к данному моменту операций ввода/вывода. */

 while (iWaits < 2 * nRecord) {

  ic = WaitForMultipleObjects(2 * MAX_OVRLP, hEvents[0], FALSE, INFINITE) – WAIT_OBJECT_0;

  iWaits++; /* Инкрементировать счетчик выполненных операций ввода вывода.*/

  ResetEvent(hEvents[ic / MAX_OVRLP][ic % MAX_OVRLP]);

  if (ic < MAX_OVRLP) {

   /* Чтение завершено. */

   GetOverlappedResult(hInputFile, &OverLapIn[ic], &nin[ic], FALSE);

   /* Обработать запись и инициировать операцию записи. */

   CurPosIn.LowPart = OverLapIn[ic].Offset;

   CurPosIn.HighPart = OverLapIn[ic].OffsetHigh;

   CurPosOut.QuadPart = (CurPosIn.QuadPart / REC_SIZE) * UREC_SIZE;

   OverLapOut[ic].Offset = CurPosOut.LowPart;

   OverLapOut[ic].OffsetHigh = CurPosOut.HighPart;

   /* Преобразовать запись из ASCII в Unicode. */

   for (i =0; i < REC_SIZE; i++) UnRec[ic][i] = AsRec[ic][i];

   WriteFile(hOutputFile, UnRec[ic], nin[ic] * 2, &nout[ic], &OverLapOut[ic]);

   /* Подготовиться к очередному чтению, которое будет инициировано после того, как завершится начатая выше операция записи. */

   CurPosIn.QuadPart += REC_SIZE * (LONGLONG)(MAX_OVRLP);

   OverLapIn[ic].Offset = CurPosIn.LowPart;

   OverLapIn[ic].OffsetHigh = CurPosIn.HighPart;

  } else if (ic < 2 * MAX_OVRLP) { /* Операция записи завершилась. */

   /* Начать чтение. */

   ic –= MAX_OVRLP; /* Установить индекс выходного буфера. */

   if (!GetOverlappedResult (hOutputFile, &OverLapOut[ic], &nout[ic], FALSE)) ReportError(_T("Ошибка чтения."), 0, TRUE);

   CurPosIn.LowPart = OverLapIn[ic].Offset;

   CurPosIn.HighPart = OverLapIn[ic].OffsetHigh;

   if (CurPosIn.QuadPart < FileSize.QuadPart) {

    /* Начать новую операцию чтения. */

    ReadFile(hInputFile, AsRec[ic], REC_SIZE, &nin[ic], &OverLapIn[ic]);

   }

  }

 } 

 /* Закрыть все события. */

 for (ic = 0; ic < MAX_OVRLP; ic++) {

  CloseHandle(hEvents[0][ic]);

  CloseHandle(hEvents[1][ic]);

 }

 CloseHandle(hInputFile);

 CloseHandle(hOutputFile);

 return 0;

Программа 14.1 способна работать только под управлением Windows NT. Средства асинхронного ввода/вывода Windows 9x не позволяют использовать дисковые файлы. В приложении В приведены результаты и комментарии, свидетельствующие о сравнительно низкой производительности программы atouOV. Как показали эксперименты, для достижения приемлемой производительности размер буфера должен составлять, по крайней мере, 32 Кбайт, но даже и в этом случае обычный синхронный ввод/вывод работает быстрее. К тому же, производительность этой программы не повышается и в условиях SMP, поскольку в данном примере, в котором обрабатываются всего лишь два файла, ЦП не является критическим ресурсом.