You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 20:54:22