4

我的笔记本电脑有一个 SSD 磁盘,其物理磁盘扇区大小为 512 字节,逻辑磁盘扇区大小为 4,096 字节。我正在开发一个必须绕过所有操作系统缓存的 ACID 数据库系统因此我直接从分配的内部存储器 (RAM) 写入 SSD 磁盘。我还在运行测试之前扩展了文件,并且在测试期间不调整它的大小。

现在这是我的问题,根据SSD 基准,随机读写应该分别在 30 MB/s 到 90 MB/s 范围内。但这是我无数次性能测试中的(相当可怕的)遥测数据:

  • 读取随机 512 字节块(物理扇区大小)时为 1.2 MB/s
  • 写入随机 512 字节块(物理扇区大小)时为 512 KB/s
  • 读取随机 4,096 字节块(逻辑扇区大小)时为 8.5 MB/s
  • 写入随机 4,096 字节块(逻辑扇区大小)时为 4.9 MB/s

除了使用异步 I/OI 之外,还设置了FILE_SHARE_READFILE_SHARE_WRITE标志来禁用所有操作系统缓冲——因为我们的数据库是 ACID 我必须这样做,我也尝试过FlushFileBuffers(),但这给了我更糟糕的性能。我还等待每个异步 I/O 操作按照我们的某些代码的要求完成。

这是我的代码,它有问题还是我陷入了这种糟糕的 I/O 性能?

HANDLE OpenFile(const wchar_t *fileName)
{
    // Set access method
    DWORD desiredAccess = GENERIC_READ | GENERIC_WRITE ;

    // Set file flags
    DWORD fileFlags = FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING /*| FILE_FLAG_RANDOM_ACCESS*/;

    //File or device is being opened or created for asynchronous I/O
    fileFlags |= FILE_FLAG_OVERLAPPED ;

    // Exlusive use (no share mode)
    DWORD shareMode = 0;

    HANDLE hOutputFile = CreateFile(
        // File name
        fileName,
        // Requested access to the file 
        desiredAccess,
        // Share mode. 0 equals exclusive lock by the process
        shareMode,
        // Pointer to a security attribute structure
        NULL,
        // Action to take on file
        CREATE_NEW,
        // File attributes and flags
        fileFlags,
        // Template file
        NULL
    );
    if (hOutputFile == INVALID_HANDLE_VALUE)
    {
        int lastError = GetLastError();
        std::cerr << "Unable to create the file '" << fileName << "'. [CreateFile] error #" << lastError << "." << std::endl;
    }

    return hOutputFile;
}

DWORD ReadFromFile(HANDLE hFile, void *outData, _UINT64 bytesToRead, _UINT64 location, OVERLAPPED *overlappedPtr, 
    asyncIoCompletionRoutine_t completionRoutine)
{
    DWORD bytesRead = 0;

    if (overlappedPtr)
    {
        // Windows demand that you split the file byte locttion into high & low 32-bit addresses
        overlappedPtr->Offset = (DWORD)_UINT64LO(location);
        overlappedPtr->OffsetHigh = (DWORD)_UINT64HI(location);

        // Should we use a callback function or a manual event
        if (!completionRoutine && !overlappedPtr->hEvent)
        {
            // No manual event supplied, so create one. The caller must reset and close it themselves
            overlappedPtr->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
            if (!overlappedPtr->hEvent)
            {
                DWORD errNumber = GetLastError();
                std::wcerr << L"Could not create a new event. [CreateEvent] error #" << errNumber << L".";
            }
        }
    }

    BOOL result = completionRoutine ? 
        ReadFileEx(hFile, outData, (DWORD)(bytesToRead), overlappedPtr, completionRoutine) : 
        ReadFile(hFile, outData, (DWORD)(bytesToRead), &bytesRead, overlappedPtr);

    if (result == FALSE)
    {
        DWORD errorCode = GetLastError();
        if (errorCode != ERROR_IO_PENDING)
        {
            std::wcerr << L"Can't read sectors from file. [ReadFile] error #" << errorCode << L".";
        }
    }

    return bytesRead;
}
4

2 回答 2

2

随机 IO 性能无法以 MB/秒为单位很好地衡量。它以 IOPS 为单位。“读取随机 512 字节块时为 1.2 MB/s”=> 20000 IOPS。不错。将块大小加倍,您将获得 199% 的 MB/秒和 99% 的 IOPS,因为读取 512 字节所需的时间几乎与读取 1024 字节所需的时间相同(几乎完全没有时间)。SSD 并非像有时错误地假设的那样没有寻求成本。

所以这些数字实际上一点也不差。

SSD 受益于高队列深度。尝试一次发出多个 IO 并始终保持该数量未完成。最佳并发将在 1-32 范围内。

由于 SSD 具有硬件并发性,因此您可以期待单线程性能的一小部分。例如,我的 SSD 有 4 个并行“库”。

只需使用FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING即可实现对硬件的直接写入。如果这些标志不起作用,则您的硬件不尊重这些标志,您将无能为力。所有服务器硬件都尊重这些标志,我还没有看到不这样做的消费磁盘。

在这种情况下,共享标志没有意义。

代码很好,虽然我不明白你为什么使用异步 IO 并稍后等待事件等待完成。这是没有意义的。要么使用同步 IO(其性能与异步 IO 大致相同),要么使用带有完成端口且无需等待的异步 IO。

于 2014-05-05T11:16:02.410 回答
0

使用 hdparm -I /dev/sdx 检查您的逻辑和物理块大小。大多数现代 SSD 的物理块大小为 4096 字节,但也支持 512 字节块,以便与旧驱动器和操作系统软件向后兼容。这是通过“512 字节仿真”AKA 512e 完成的。如果您的驱动器是执行 512 字节仿真的驱动器之一,那么您的 512 字节访问实际上是读取修改写入操作。SSD 将尝试将顺序访问转换为 4k 块写入。

如果您可以切换到 4k 块写入,您将(可能)看到更好的 IOPS 数字和带宽,因为这可以减少 SSD 上的工作量。由于增加的写入放大,随机 512 块写入也会对长期性能产生重大影响。

于 2015-04-04T01:33:09.400 回答