Необработанные тесты ввода-вывода SSD со случайным чтением/записью

На моем ноутбуке установлен SSD-диск с размером сектора физического диска 512 байт и размером сектора логического диска 4096 байт. Я работаю над системой базы данных ACID, которая должна обходить все кеши ОС, поэтому я пишу непосредственно из выделенной внутренней памяти (ОЗУ) на диск SSD. Я также расширяю файлы перед запуском тестов и не изменяю их размер во время тестов.

Теперь вот моя проблема, согласно тестам SSD random скорость чтения и записи должна быть в диапазоне от 30 МБ/с до 90 МБ/с соответственно. Но вот моя (довольно ужасная) телеметрия из моих многочисленных тестов производительности:

  • 1,2 МБ/с при чтении случайных блоков по 512 байт (физический размер сектора)
  • 512 КБ/с при записи случайных блоков по 512 байт (физический размер сектора)
  • 8,5 МБ/с при чтении случайных блоков по 4096 байт (размер логического сектора)
  • 4,9 МБ/с при записи случайных блоков по 4096 байт (размер логического сектора)

В дополнение к использованию асинхронного ввода-вывода я также установил флаги 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;
}

person Community    schedule 05.05.2014    source источник


Ответы (2)


Производительность произвольного ввода-вывода плохо измеряется в МБ/с. Измеряется в IOPS. «1,2 МБ/с при чтении случайных блоков по 512 байт» => 20000 IOPS. Неплохо. Удвойте размер блока, и вы получите 199 % МБ/с и 99 % IOPS, потому что для чтения 512 байт требуется почти столько же времени, сколько для чтения 1024 байт (почти совсем нет времени). SSD не свободны от затрат на поиск, как иногда ошибочно полагают.

Так что цифры на самом деле не плохие.

SSD-накопители выигрывают от большой глубины очереди. Попробуйте выполнить несколько операций ввода-вывода одновременно и всегда держите это число открытым. Оптимальный параллелизм будет где-то в диапазоне 1-32.

Поскольку твердотельные накопители имеют аппаратный параллелизм, вы можете ожидать, что производительность будет немного выше однопоточной производительности. Например, мой SSD имеет 4 параллельных «банка».

Использование FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING — это все, что необходимо для прямой записи на оборудование. Если эти флаги не работают, ваше оборудование не соблюдает эти флаги, и вы ничего не можете с этим поделать. Все серверное оборудование соблюдает эти флаги, и я не видел ни одного потребительского диска, который бы этого не делал.

Флаги совместного использования не имеют смысла в этом контексте.

Код в порядке, хотя я не понимаю, почему вы используете асинхронный ввод-вывод, а затем ждете события, чтобы дождаться завершения. Это бессмысленно. Либо используйте синхронный ввод-вывод (который будет работать примерно так же, как асинхронный ввод-вывод), либо используйте асинхронный ввод-вывод с портами завершения и без ожидания.

person usr    schedule 05.05.2014

Используйте hdparm -I /dev/sdx, чтобы проверить размер вашего логического и физического блока. Большинство современных твердотельных накопителей имеют физический размер блока 4096 байт, но также поддерживают блоки размером 512 байт для обратной совместимости со старыми накопителями и программным обеспечением ОС. Это делается с помощью «эмуляции 512 байт» AKA 512e. Если ваш диск является одним из тех, которые выполняют эмуляцию 512 байтов, ваши 512-байтовые доступы фактически являются операциями чтения и модификации записи. SSD попытается преобразовать последовательный доступ в блок записи по 4 КБ.

Если вы можете переключиться на запись блоков по 4 КБ, вы (вероятно) увидите гораздо лучшие показатели IOPS, а также пропускной способности, поскольку это значительно снижает нагрузку на SSD. Произвольная запись блоков 512 также оказывает большое влияние на долгосрочную производительность из-за увеличенного усиления записи.

person Alex    schedule 04.04.2015