Win32磁盘写入性能优化:如何确定磁盘最优写入块大小?
如何确定重叠I/O场景下磁盘的最优写入块大小
问题描述
为实现硬盘的最大写入性能,我采用了Overlapped I/O(重叠I/O)机制,该方案目前可正常运行。具体操作流程为:从传感器获取4MB数据后写入磁盘,在获取下一批4MB数据前,会先检查上一次写入操作是否完成。现咨询:如何确定适配当前磁盘的最优写入块大小(当前使用4MB)?
相关实现代码
// AsyncFile.cpp : Defines the exported functions for the DLL application. // #include "stdafx.h" #include "AsyncFile.h" /****************************************************************************/ CAsyncFile::CAsyncFile() { } /****************************************************************************/ CAsyncFile::~CAsyncFile() { } /****************************************************************************/ int CAsyncFile::OpenFile(char *pcFileName, bool bAsync, // Whether async read/write is required bool bWrite) // True is file is used for writing to { DWORD dwAsyncMask = bAsync ? (FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING) : 0; DWORD dwCreation = bWrite ? CREATE_ALWAYS : OPEN_EXISTING; DWORD dwAccess = bWrite ? GENERIC_WRITE : GENERIC_READ; DWORD dwShareMode = bWrite ? FILE_SHARE_READ : FILE_SHARE_WRITE; if (strlen(pcFileName) < sizeof(m_cFileName)) strcpy_s(m_cFileName, 256, pcFileName); else m_cFileName[0] = 0; // NULL (error - file name is too long) // Calling openFile() sets a valid value to the file handle m_hFileHandle = INVALID_HANDLE_VALUE; // Auto reset (manual reset=false), init state = false, no name m_hIoCompleted = CreateEvent(NULL, FALSE, FALSE, NULL); // Init OVERLAPPED structure, for async read m_tOverlapped.Offset = 0; m_tOverlapped.OffsetHigh = 0; m_tOverlapped.hEvent = m_hIoCompleted; m_Event = m_tOverlapped.hEvent; if (m_hFileHandle != INVALID_HANDLE_VALUE) { // File is already opened; check open mode if ((bAsync == m_bAsync) && (bWrite == m_bWrite)) return (ASYNCFILE_OK); // File is already opened, but in other mode; Should close file // before using it again return ASYNCFILE_FILE_IS_NOT_IN_WRITE_MODE; } m_hFileHandle = CreateFile((LPCTSTR)m_cFileName, dwAccess, // Open for read or write dwShareMode, // NULL, // No SECURITY_ATTRBUTES dwCreation, // Open exisiting file (if read) \ create new (if write) dwAsyncMask, // For asynchronous operations, for maximum asynchronous performence 0); if (m_hFileHandle == INVALID_HANDLE_VALUE) { DWORD dwError = GetLastError(); return ASYNCFILE_FAILED_TO_OPEN_FILE; } //In case file opened for reading, get its size if (bWrite == false) { GetFileSizeEx(m_hFileHandle, &m_FileSize); } // Save open mode m_bAsync = bAsync; m_bWrite = bWrite; return ASYNCFILE_OK; } /****************************************************************************/ int CAsyncFile::CloseFile() { //BOOL Status; if (!CloseHandle(m_hFileHandle)) return ASYNCFILE_FAILED_TO_CLOSE_FILE; if (!CloseHandle(m_hIoCompleted)) return ASYNCFILE_FAILED_TO_CLOSE_FILE; return ASYNCFILE_OK; } /****************************************************************************/ int CAsyncFile::StartAsyncRead(void* pBuffer, DWORD dwReadSize, bool* pbEof) { *pbEof = false; // By default, EOF is false int iError; if (m_hFileHandle == INVALID_HANDLE_VALUE) return (false); if (!ReadFile(m_hFileHandle, pBuffer, dwReadSize, NULL, // actual bytes read is not valid now &m_tOverlapped)) { if ((iError = GetLastError()) == ERROR_HANDLE_EOF) { *pbEof = true; return ASYNCFILE_OK; } else if (!(m_bAsync && (iError == ERROR_IO_PENDING))) { return ASYNCFILE_START_READ_FAILED; } } return ASYNCFILE_OK; } /****************************************************************************/ int CAsyncFile::WaitAsyncOperationEnd(DWORD* pdwActualBytesTransferred) { if (m_hFileHandle == INVALID_HANDLE_VALUE) return ASYNCFILE_WAIT_FOR_COMPLETION_FAILED; // Wait for read operation to complete if (!GetOverlappedResult(m_hFileHandle, &m_tOverlapped, pdwActualBytesTransferred, true)) return ASYNCFILE_WAIT_FOR_COMPLETION_FAILED; return ASYNCFILE_OK; } /****************************************************************************/ int CAsyncFile::StartAsyncWrite(void* pSrcBuf, DWORD dwSize) // In bytes { int iError; if (!WriteFile(m_hFileHandle, pSrcBuf, dwSize, NULL, // actual bytes written is not valid now &m_tOverlapped)) { iError = GetLastError(); if (iError != ERROR_IO_PENDING) return ASYNCFILE_START_WRITE_FAILED; } return ASYNCFILE_OK; } /****************************************************************************/ void CAsyncFile::SetFilePosition(UINT64 Position) { m_tOverlapped.Offset = Position & 0xFFFFFFFF; m_tOverlapped.OffsetHigh = Position >> 32; } /****************************************************************************/ UINT64 CAsyncFile::GetFilePosition() { UINT64 Position; Position = (m_tOverlapped.Offset) | ((UINT64)m_tOverlapped.OffsetHigh << 32); return (Position); } /****************************************************************************/ UINT64 CAsyncFile::GetFileSize() { return (m_FileSize.QuadPart); }
确定最优写入块大小的方法
1. 对齐磁盘核心参数
- 先获取磁盘的物理扇区大小和簇大小:Windows下可通过
GetDiskFreeSpaceExAPI或直接查看磁盘属性获取。写入块大小必须是物理扇区的整数倍,否则磁盘会触发额外的读-改-写操作,降低性能。 - 你的代码启用了
FILE_FLAG_NO_BUFFERING,这种场景下,写入缓冲区的地址和大小必须严格对齐到物理扇区大小,否则WriteFile会直接调用失败。
2. 针对性性能测试
- 编写基准测试程序,用不同块大小(比如64KB、128KB、256KB、512KB、1MB、2MB、4MB、8MB等)模拟你的业务流程:获取数据→等待上一次写入完成→发起新写入,记录每秒吞吐量和IOPS。
- 多次测试取平均值,避开系统高负载时段,保证数据真实有效。
3. 根据磁盘类型调整
- 机械硬盘(HDD):最优块大小通常在64KB-256KB之间。太大的块会增加寻道时间占比,太小则会产生过多I/O请求,都拉低性能。
- 固态硬盘(SSD):无寻道时间限制,1MB-4MB的块大小通常能获得更高吞吐量,但要保证是SSD内部页大小(一般4KB/8KB)的整数倍。
- NVMe SSD:可尝试4MB-16MB的块大小,这类磁盘并行处理能力强,大尺寸块能更充分利用其带宽。
4. 平衡系统与硬件限制
- 检查磁盘最大I/O请求大小:通过
DeviceIoControl调用IOCTL_STORAGE_QUERY_PROPERTY获取磁盘最大传输单元(MTU),写入块大小不要超过这个值。 - 内存资源约束:更大的块需要更多缓冲区内存,如果系统内存有限,要在性能和内存占用之间找平衡。
针对当前代码的优化建议
- 确保写入缓冲区地址对齐:因为用了
FILE_FLAG_NO_BUFFERING,建议用VirtualAlloc或_aligned_malloc分配内存,保证缓冲区地址和物理扇区大小对齐。 - 尝试多队列异步写入:当前是串行等待写入完成再处理下一批,可同时发起2-4个重叠I/O请求,让磁盘持续处于忙碌状态,进一步提升吞吐量,此时块大小也要结合队列深度调整。
内容的提问来源于stack exchange,提问作者Zvi Vered
相关产品推荐
相关产品推荐

