Мой ноутбук имеет SSD-диск с размером сектора физического диска 512 байт и размером сектора логического диска 4096 байт. Я работаю над системой баз данных ACID, которая имеет чтобы обойти все кэши ОС, поэтому я пишу напрямую из выделенной внутренней памяти (RAM) на диск SSD. Я также расширяю файлы до Я запускаю тесты и не изменяю их размеры во время тестов.
Теперь вот моя проблема, согласно Тесты SSD случайное чтение & Запись должна быть в диапазоне от 30 МБ / с до 90 МБ / с соответственно. Но вот моя (довольно ужасная) телеметрия из моих многочисленных тестов производительности:
В дополнение к использованию асинхронного ввода-вывода я также установил FILE_SHARE_READ
а также FILE_SHARE_WRITE
флаги для отключения всей буферизации ОС — потому что наша база данных ACID, я должен сделать это, я также пытался FlushFileBuffers()
но это дало мне еще худшие результаты. Я также жду завершения каждой операции асинхронного ввода-вывода, как того требует наш код.
Вот мой код, есть проблемы с ним, или я застрял с этой плохой производительностью ввода-вывода?
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;
}
Производительность случайного ввода-вывода плохо измеряется в МБ / с. Измеряется в IOPS. «1,2 МБ / с при чтении случайных 512-байтовых блоков» => 20000 IOPS. Неплохо. Удвойте размер блока, и вы получите 199% МБ / с и 99% IOPS, потому что чтение 512 байт занимает почти то же время, что и чтение 1024 байт (почти без времени). Твердотельные накопители не свободны от поиска затрат, как иногда ошибочно предполагают.
Так что цифры на самом деле совсем не плохие.
SSD-накопители получают выгоду от высокой глубины очереди. Попробуйте выполнить несколько операций ввода-вывода одновременно и сохранить это число всегда в обращении. Оптимальный параллелизм будет где-то в диапазоне 1-32.
Поскольку SSD имеют аппаратный параллелизм, вы можете ожидать небольшую кратность однопоточной производительности. У моего SSD есть 4 параллельных «банка», например.
С помощью FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING
это все, что нужно для прямой записи на аппаратное обеспечение. Если эти флаги не работают, ваше оборудование не соблюдает эти флаги, и вы ничего не можете с этим поделать. Все серверное оборудование уважает эти флаги, и я не видел потребительский диск, который не делает.
Флаги совместного использования не имеют смысла в этом контексте.
Код в порядке, хотя я не понимаю, почему вы используете асинхронный ввод-вывод, а потом ждете события, чтобы дождаться завершения. Это бессмысленно. Либо используйте синхронный ввод-вывод (который будет работать примерно так же, как асинхронный ввод-вывод), либо используйте асинхронный ввод-вывод с портами завершения и без ожидания.
Используйте hdparm -I / dev / sdx для проверки вашего логического и физического размера блока. Большинство современных твердотельных накопителей имеют размер физического блока 4096 байт, но также поддерживают блоки 512 байт для обратной совместимости со старыми дисками & Программное обеспечение ОС. Это делается с помощью «эмуляции 512 байт» A.K.A 512e. Если ваш диск является одним из тех, которые эмулируют 512 байт, то ваши 512-байтовые обращения фактически являются операциями чтения и записи. SSD попытается превратить последовательный доступ в запись блока 4k.
Если вы сможете переключиться на запись блоков 4k, вы (вероятно) увидите гораздо лучшие показатели для операций ввода-вывода в секунду, а также пропускную способность, поскольку это значительно снижает нагрузку на SSD. Случайные записи 512 блоков также оказывают большое влияние на долговременную производительность из-за увеличенного усиления записи.