CPUバウンド、かつI/Oバウンドな処理(ファイルからのハッシュ計算など)の場合、非同期I/Oを使用することで処理時間が短縮する場合がある。
一般的に、ワーカースレッドとキューを用いた実装をする場合が多いかと思うが、ここでは1スレッド、そして古典的なWindows APIである ReadFile
(の滅多に使用しないlpOverlapped
パラメータ) を用いて非同期I/Oを実装したサンプルコードを掲載する。
処理イメージ
注意
- このサンプルコードが、本当に早い保証はありません。計測してください。
- 闇雲に非同期I/Oを使用すべきではない。
同期I/Oでも、OS等のキャッシュにより非同期I/Oとほぼ変わらない処理時間の場合が多い。
低速なCPUと低速なI/Oが想定される場合のみ非同期I/Oを検討するべきである。 - ハッシュ計算は古いAPIであるCryptoAPIを使用している。MicrosoftはCNG APIの使用を推奨している。
サンプルコード
CPUバウンドの処理の例として、SHA-512を行っている。
非同期I/OはWindows 2000 などでも可能だが、SHA-512はWindows XP SP3以降でないと対応していない。
main.c
# ifndef _WIN32_WINNT
# define _WIN32_WINNT 0x0502
# endif
# include <windows.h>
# include <tchar.h>
# include <wincrypt.h>
# include <bcrypt.h>
# pragma comment(lib, "winmm.lib")
# pragma comment(lib, "crypt32.lib")
# define BUFSIZE (4 * 1024 * 1024)
# define SHA512LEN (512/8)
struct BUF
{
LPBYTE lpBuffer;
OVERLAPPED ol;
DWORD dwBufferSize;
DWORD dwReaded;
BOOL fPending;
};
static void initBuf(struct BUF * pBuf)
{
memset(pBuf, 0x00, sizeof(struct BUF));
pBuf->lpBuffer = malloc(BUFSIZE);
pBuf->dwBufferSize = BUFSIZE;
//pBuf->ol.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
}
DWORD CalcHashAsync(LPCTSTR lpFileName, HCRYPTHASH hHash)
{
struct BUF buffers[2] = {0};
HANDLE hFile;
BOOL fContinue = TRUE;
BOOL index = 0;
ULARGE_INTEGER offset = {0};
DWORD dwError = ERROR_SUCCESS;
hFile = CreateFile(lpFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING | FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OVERLAPPED, NULL);
if(INVALID_HANDLE_VALUE == hFile)
{
dwError = GetLastError();
DebugBreak();
return dwError;
}
initBuf(&buffers[0]);
initBuf(&buffers[1]);
while(fContinue)
{
// -------------------------------
if(buffers[index].fPending)
{
DWORD dwPendingTick = timeGetTime();
//_tprintf(TEXT("[%d]Wait Pending I/O...\n"), index);
if(!GetOverlappedResult(hFile, &buffers[index].ol, &buffers[index].dwReaded, TRUE))
{
DWORD overlappedResult = GetLastError();
fContinue = FALSE;
if(overlappedResult != ERROR_HANDLE_EOF)
{
dwError = overlappedResult;
DebugBreak();
break;
}
}
_tprintf(TEXT("[%d] %u\n"), index, timeGetTime() - dwPendingTick);
}
// -------------------------------
if(fContinue)
{
BOOL nextIndex = !index;
buffers[nextIndex].dwReaded = 0;
buffers[nextIndex].ol.Offset = offset.LowPart;
buffers[nextIndex].ol.OffsetHigh = offset.HighPart;
memset(buffers[nextIndex].lpBuffer, 0xCC, buffers[nextIndex].dwBufferSize);
buffers[nextIndex].fPending = FALSE;
_tprintf(TEXT("[%d]Start ReadFile(offset:0x%X%08X)...\n"), nextIndex, offset.HighPart, offset.LowPart );
if(!ReadFile(hFile, buffers[nextIndex].lpBuffer, buffers[nextIndex].dwBufferSize, &buffers[nextIndex].dwReaded, &buffers[nextIndex].ol))
{
DWORD dwReadFileResult = GetLastError();
switch(dwReadFileResult)
{
case ERROR_IO_PENDING:
buffers[nextIndex].fPending = TRUE;
break;
case ERROR_HANDLE_EOF:
fContinue = FALSE;
break;
default:
fContinue = FALSE;
dwError = dwReadFileResult;
break;
}
}
else
{
_tprintf(TEXT("[%d]ReadFile completed synchronously.\n"), nextIndex);
}
offset.QuadPart += buffers[nextIndex].dwBufferSize;
}
// -------------------------------
if(buffers[index].dwReaded)
{
_tprintf(TEXT("[%d]CryptHashData(0x%08X)...\n"), index, buffers[index].dwReaded);
if(!CryptHashData(hHash, buffers[index].lpBuffer, buffers[index].dwReaded, 0))
{
dwError = GetLastError();
DebugBreak();
break;
}
}
index = !index; // 0, 1, 0, 1 ...
}
CloseHandle(hFile);
free(buffers[0].lpBuffer);
free(buffers[1].lpBuffer);
return dwError;
}
int _tmain(int argc, _TCHAR* argv[])
{
HCRYPTPROV hProv = 0;
HCRYPTHASH hHash = 0;
BYTE hashVal[SHA512LEN] = {0};
DWORD dwHashSize = sizeof(hashVal);
TCHAR hashStr[SHA512LEN * 4 + 1] = TEXT("");
DWORD cchStr = _countof(hashStr);
if(!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT))
{
DebugBreak();
return 1;
}
if(!CryptCreateHash(hProv, CALG_SHA_512, 0, 0, &hHash))
{
DebugBreak();
CryptReleaseContext(hProv, 0);
return 1;
}
CalcHashAsync(argv[1], hHash);
if(CryptGetHashParam(hHash, HP_HASHVAL, hashVal, &dwHashSize, 0))
{
CryptBinaryToString(hashVal, sizeof(hashVal),CRYPT_STRING_HEX | CRYPT_STRING_NOCR,hashStr, &cchStr);
_tprintf(TEXT("%s\n"), hashStr);
}
else
{
_ftprintf(stderr, TEXT("CryptGetHashParam failed.(0x%08X)\n"), GetLastError());
}
CryptDestroyHash(hHash);
CryptReleaseContext(hProv, 0);
return 0;
}