带有随机读/写的SSD原始I/O基准
SSD raw I/O benchmarks with random read/write
我的笔记本电脑有一个SSD磁盘,物理磁盘扇区大小为512字节,逻辑磁盘扇区大小为4,096字节。我正在研究一个ACID数据库系统,其中有绕过所有操作系统缓存,因此我直接从分配的内部内存(RAM)写入SSD磁盘。我还在运行测试之前扩展文件,并且在测试期间不调整其大小。
现在这是我的问题,根据SSD基准随机读取&写速率应该分别在30 MB/s到90 MB/s之间。但以下是我从众多性能测试中获得的遥测结果(相当糟糕):
- 读取随机512字节块(物理扇区大小)时1.2 MB/s
- 写入随机512字节块(物理扇区大小)时512 KB/s
- 8.5 MB/s读取随机4,096字节块(逻辑扇区大小)
- 当写入随机4,096字节块(逻辑扇区大小)时4.9 MB/s
除了使用异步I/O之外,我还设置了FILE_SHARE_READ
和FILE_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;
}
随机IO性能不能很好地以MB/sec来衡量。以IOPS为单位。"读取随机512字节块时1.2 MB/s " => 20000 IOPS。不坏。将块大小增加一倍,你将获得199%的MB/sec和99%的IOPS,因为读取512字节所花费的时间与读取1024字节几乎相同(几乎没有时间)。固态硬盘并非像人们有时错误地认为的那样没有查找成本。
所以这些数字实际上一点也不差。
ssd受益于高队列深度。尝试同时发行多款IOs游戏,并始终保持这一数量。最优并发性在1-32之间。
由于ssd具有硬件并发性,因此可以期望单线程性能的小倍数。例如,我的SSD有4个并行的"银行"。
使用FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING
就可以实现对硬件的直接写入。如果这些标志不工作,你的硬件不尊重这些标志,你不能做任何事情。所有的服务器硬件都尊重这些标志,我还没见过哪个消费磁盘不这样做。
共享标志在此上下文中没有意义。
代码很好,虽然我不明白为什么你使用异步IO,然后等待一个事件等待完成。这没有道理。要么使用同步IO(其执行与异步IO大致相同),要么使用带完成端口且不等待的异步IO。
使用hdparm -I/dev/sdx检查您的逻辑和物理块大小。大多数现代ssd具有4096字节的物理块大小,但也支持512字节的块,以向后兼容旧驱动器。操作系统软件。这是通过"512字节仿真"(即512e)完成的。如果您的驱动器是进行512字节模拟的驱动器之一,那么512字节访问实际上是读修改写操作。SSD将尝试将顺序访问转换为4k块写。
如果你可以切换到4k块写,你将(可能)看到更好的IOPS和带宽的数字,因为这使得在SSD上的工作少得多。随机512块写也有很大的长期性能影响,因为增加了写放大。
- 将浮动的heightmap数组导出为16位原始值
- 给定一个向量,如何找到该向量的所有子集和的原始索引
- 有没有办法从非C/C++文件中读取C++原始字符串文字的内容
- 是否可以将llvm::FunctionType转换为C/C++原始函数指针
- 使用rdtsc进行基准测试的缺点是什么
- 如何将原始字节附加到 std::vector?
- 从堆栈分配的原始指针构造智能指针
- 如何重写全局方法名称以在调用原始方法之前将我的代码推到前面
- 将unique_ptr分配给原始指针
- 是否可以从 OpenGL 缓冲区获取原始大小的像素?
- 使用 OpenSSL 从内存中读取原始 SSL/TLS 证书
- 如何将唯一指针的 std::vector 转换为原始指针的 std::span?
- 从原始字节解码协议缓冲区(以 C++为单位)
- C ++如何在原始抽象类中创建一个函数,该函数接受派生类的输入
- C++:尝试使用等效的 STL 算法消除原始循环
- 如何在连接器 C++ 中将原始字节转换为字符串
- 对 'std::thread::_M_start_thread CMake 的未定义引用进行基准测试
- 更高效地在微控制器上对C++进行基准测试
- 通过引用传递类不会更改原始类
- 带有随机读/写的SSD原始I/O基准