Как повысить скорость записи C ++ до скорости, протестированной CrystalDiskMark? - PullRequest
0 голосов
/ 23 января 2019

Теперь я получаю около 3,6 ГБ данных в секунду в памяти, и мне нужно непрерывно записывать их на мой SSD.Я использовал CrystalDiskMark для проверки скорости записи моего SSD, она составляет почти 6 ГБ в секунду, поэтому я подумал, что эта работа не должна быть такой сложной.

! [Мой результат теста SSD] [1]:

[1] https://plus.google.com/u/0/photos/photo/106876803948041178149/6649598887699308850?authkey=CNbb5KjF8-jxJQ «результат теста»:

Мой компьютер - Windows 10, используется сообщество Visual Studio 2017.

Я нашел этот вопрос и попробовал самый высокий голос.К сожалению, скорость записи составила всего около 1 с / ГБ для его option_2, намного медленнее, чем тестировал CrystalDiskMark.А затем я попытался сопоставить память, на этот раз запись становится быстрее, около 630 мс / ГБ, но все еще намного медленнее.Затем я попробовал многопоточное сопоставление памяти, кажется, что когда число потоков равно 4, скорость составляет около 350 мс / ГБ, а когда я добавляю число потоков, скорость записи больше не повышается.

Код для отображения памяти:

#include <fstream>
#include <chrono>
#include <vector>
#include <cstdint>
#include <numeric>
#include <random>
#include <algorithm>
#include <iostream>
#include <cassert>
#include <thread>
#include <windows.h>
#include <sstream>


// Generate random data
std::vector<int> GenerateData(std::size_t bytes) {
    assert(bytes % sizeof(int) == 0);
    std::vector<int> data(bytes / sizeof(int));
    std::iota(data.begin(), data.end(), 0);
    std::shuffle(data.begin(), data.end(), std::mt19937{ std::random_device{}() });
    return data;
}

// Memory mapping
int map_write(int* data, int size, int id){
    char* name = (char*)malloc(100);
    sprintf_s(name, 100, "D:\\data_%d.bin",id);
    HANDLE hFile = CreateFile(name, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);//
    if (hFile == INVALID_HANDLE_VALUE){
        return -1;
    }

    Sleep(0);

    DWORD dwFileSize = size;

    char* rname = (char*)malloc(100);
    sprintf_s(rname, 100, "data_%d.bin", id);

    HANDLE hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, dwFileSize, rname);//create file  
    if (hFileMap == NULL) {
        CloseHandle(hFile);
        return -2;
    }

    PVOID pvFile = MapViewOfFile(hFileMap, FILE_MAP_WRITE, 0, 0, 0);//Acquire the address of file on disk
    if (pvFile == NULL) {
        CloseHandle(hFileMap);
        CloseHandle(hFile);
        return -3;
}

    PSTR pchAnsi = (PSTR)pvFile;
    memcpy(pchAnsi, data, dwFileSize);//memery copy 

    UnmapViewOfFile(pvFile);

    CloseHandle(hFileMap);
    CloseHandle(hFile);

    return 0;
}

// Multi-thread memory mapping
void Mem2SSD_write(int* data, int size){
    int part = size / sizeof(int) / 4;

    int index[4];

    index[0] = 0;
    index[1] = part;
    index[2] = part * 2;
    index[3] = part * 3;

    std::thread ta(map_write, data + index[0], size / 4, 10);
    std::thread tb(map_write, data + index[1], size / 4, 11);
    std::thread tc(map_write, data + index[2], size / 4, 12);
    std::thread td(map_write, data + index[3], size / 4, 13);

    ta.join();
    tb.join();
    tc.join();
    td.join();
 }

//Test:
int main() {
    const std::size_t kB = 1024;
    const std::size_t MB = 1024 * kB;
    const std::size_t GB = 1024 * MB;

    for (int i = 0; i < 10; ++i) {
        std::vector<int> data = GenerateData(1 * GB);
        auto startTime = std::chrono::high_resolution_clock::now();
        Mem2SSD_write(&data[0], 1 * GB);
        auto endTime = std::chrono::high_resolution_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count();
        std::cout << "1G writing cost: " << duration << " ms" << std::endl;
    }

    system("pause");
    return 0;
}

Итак, я хотел бы спросить, есть ли более быстрый способ записи на C ++ для записи больших файлов?Или почему я не могу писать так быстро, как было протестировано CrystalDiskMark?Как пишет CrystalDiskMark?

Любая помощь будет принята с благодарностью.Спасибо!

Ответы [ 3 ]

0 голосов
/ 23 января 2019

Вот мои мысли:

  1. остановить все запущенные процессы, использующие диск, в частности
    • отключить защиту Защитника Windows в реальном времени (или другой антивирус/ malware)
    • отключить файл подкачки
  2. использовать Windows Resource Monitor для поиска процессов чтения или записи на ваш диск
  3. убедитесь, что вы записываете непрерывные сектора на диске
  4. не принимать во внимание время открытия и закрытия файла
  5. не использовать многопоточность (ваш диск использует DMA, поэтому процессор не имеет значения)
  6. записывать данные, которыенаходится в оперативной памяти (очевидно)
  7. при сборке (сборке релиза) обязательно отключите все функции отладки
  8. при использовании диска M.2 PCIe (похоже, ваш случай) убедитесь, что другие PCIeустройства не крадут линии PCIe на ваш диск (у CPU также ограниченное число и mobo)
  9. не запускать тест из вашей IDE
  10. отключить индексирование файлов Windows

Наконец, выВы можете найти хорошие советы о том, как писать быстрые записи в C / C ++ в ветке этого вопроса: Запись двоичного файла в C ++ очень быстрая

0 голосов
/ 23 января 2019

Прежде всего это не вопрос c ++ , а вопрос, связанный с ОС. для получения максимальной производительности необходимо использовать специальный вызов API низкого уровня, которого нет в общем случае c ++ libs. Из вашего кода ясно видно, что вы используете Windows API, поэтому поиск решения для Windows, как минимум.

из CreateFileW функция:

Когда FILE_FLAG_NO_BUFFERING объединяется с FILE_FLAG_OVERLAPPED, флаги дают максимальную асинхронную производительность, потому что ввод / вывод делает не полагаться на синхронные операции диспетчера памяти.

, поэтому нам нужно использовать комбинацию из этих 2 флагов при вызове CreateFileW или FILE_NO_INTERMEDIATE_BUFFERING при вызове NtCreateFile

также увеличивает размер файла и допустимая длина данных занимает некоторое время, поэтому лучше, если известен конечный файл в начале - просто установите конечный размер файла с помощью NtSetInformationFile с помощью FileEndOfFileInformation или через SetFileInformationByHandle с FileEndOfFileInfo. а затем установите действительную длину данных с помощью SetFileValidData или с помощью NtSetInformationFile с помощью FileValidDataLengthInformation . установить допустимую длину данных, требуются права доступа SE_MANAGE_VOLUME_NAME, включенные при первом открытии файла (но не при вызове SetFileValidData)

также ищите сжатие файлов - если файл сжат (он будет сжат по умолчанию, если создан в сжатой папке), это очень медленная запись. поэтому необходимо отменить сжатие файла с помощью FSCTL_SET_COMPRESSION

тогда, когда мы используем асинхронный ввод / вывод (самый быстрый способ), нам не нужно создавать несколько выделенных потоков. вместо этого нам нужно определить количество запросов ввода-вывода, выполняемых одновременно. если вы используете CrystalDiskMark , на самом деле он запускает CdmResource \ diskspd \ diskspd64.exe для тестирования, и ему присваивается параметр -o<count> (для запуска diskspd64.exe /? > h.txt смотри список параметров).

использование не Буферизация ввода / вывода усложняет задачу, поскольку существуют 3 дополнительных требования:

  1. Любой ByteOffset, переданный в WriteFile, должен быть кратным сектору размер.
  2. Длина, переданная в WriteFile, должна быть интегралом сектора размер
  3. Буферы должны быть выровнены в соответствии с требованием выравнивания базового устройства. Чтобы получить эту информацию, позвоните NtQueryInformationFile с FileAlignmentInformation или GetFileInformationByHandleEx с FileAlignmentInfo

в большинстве ситуаций, выровненная по страницам память также будет выровнена по секторам, потому что случай, когда размер сектора больше, чем размер страницы редкий.

, так что почти всегда буферы, выделенные с помощью функции VirtualAlloc и нескольких размеров страницы (4096 байт), в порядке. в конкретном тесте для меньшего размера кода я использую это предположение

struct WriteTest 
{
    enum { opCompression, opWrite };

    struct REQUEST : IO_STATUS_BLOCK 
    {
        WriteTest* pTest;
        ULONG opcode;
        ULONG offset;
    };

    LONGLONG _TotalSize, _BytesLeft;
    HANDLE _hFile;
    ULONG64 _StartTime;
    void* _pData;
    REQUEST* _pRequests;
    ULONG _BlockSize;
    ULONG _ConcurrentRequestCount;
    ULONG _dwThreadId;
    LONG _dwRefCount;

    WriteTest(ULONG BlockSize, ULONG ConcurrentRequestCount) 
    {
        if (BlockSize & (BlockSize - 1))
        {
            __debugbreak();
        }
        _BlockSize = BlockSize, _ConcurrentRequestCount = ConcurrentRequestCount;
        _dwRefCount = 1, _hFile = 0, _pRequests = 0, _pData = 0;
        _dwThreadId = GetCurrentThreadId();
    }

    ~WriteTest()
    {
        if (_pData)
        {
            VirtualFree(_pData, 0, MEM_RELEASE);
        }

        if (_pRequests)
        {
            delete [] _pRequests;
        }

        if (_hFile)
        {
            NtClose(_hFile);
        }

        PostThreadMessageW(_dwThreadId, WM_QUIT, 0, 0);
    }

    void Release()
    {
        if (!InterlockedDecrement(&_dwRefCount))
        {
            delete this;
        }
    }

    void AddRef()
    {
        InterlockedIncrementNoFence(&_dwRefCount);
    }

    void StartWrite()
    {
        IO_STATUS_BLOCK iosb;

        FILE_VALID_DATA_LENGTH_INFORMATION fvdl;
        fvdl.ValidDataLength.QuadPart = _TotalSize;
        NTSTATUS status;

        if (0 > (status = NtSetInformationFile(_hFile, &iosb, &_TotalSize, sizeof(_TotalSize), FileEndOfFileInformation)) ||
            0 > (status = NtSetInformationFile(_hFile, &iosb, &fvdl, sizeof(fvdl), FileValidDataLengthInformation)))
        {
            DbgPrint("FileValidDataLength=%x\n", status);
        }

        ULONG offset = 0;
        ULONG dwNumberOfBytesTransfered = _BlockSize;

        _BytesLeft = _TotalSize + dwNumberOfBytesTransfered;

        ULONG ConcurrentRequestCount = _ConcurrentRequestCount;

        REQUEST* irp = _pRequests;

        _StartTime = GetTickCount64();

        do 
        {
            irp->opcode = opWrite;
            irp->pTest = this;
            irp->offset = offset;
            offset += dwNumberOfBytesTransfered;
            DoWrite(irp++);
        } while (--ConcurrentRequestCount);
    }

    void FillBuffer(PULONGLONG pu, LONGLONG ByteOffset)
    {
        ULONG n = _BlockSize / sizeof(ULONGLONG);
        do 
        {
            *pu++ = ByteOffset, ByteOffset += sizeof(ULONGLONG);
        } while (--n);
    }

    void DoWrite(REQUEST* irp)
    {
        LONG BlockSize = _BlockSize;

        LONGLONG BytesLeft = InterlockedExchangeAddNoFence64(&_BytesLeft, -BlockSize) - BlockSize;

        if (0 < BytesLeft)
        {
            LARGE_INTEGER ByteOffset;
            ByteOffset.QuadPart = _TotalSize - BytesLeft;

            PVOID Buffer = RtlOffsetToPointer(_pData, irp->offset);

            FillBuffer((PULONGLONG)Buffer, ByteOffset.QuadPart);

            AddRef();

            NTSTATUS status = NtWriteFile(_hFile, 0, 0, irp, irp, Buffer, BlockSize, &ByteOffset, 0);

            if (0 > status)
            {
                OnComplete(status, 0, irp);
            }
        }
        else if (!BytesLeft)
        {
            // write end
            ULONG64 time = GetTickCount64() - _StartTime;

            WCHAR sz[64];
            StrFormatByteSizeW((_TotalSize * 1000) / time, sz, RTL_NUMBER_OF(sz));
            DbgPrint("end:%S\n", sz);
        }
    }

    static VOID NTAPI _OnComplete(
        _In_    NTSTATUS status,
        _In_    ULONG_PTR dwNumberOfBytesTransfered,
        _Inout_ PVOID Ctx
        )
    {
        reinterpret_cast<REQUEST*>(Ctx)->pTest->OnComplete(status, dwNumberOfBytesTransfered, reinterpret_cast<REQUEST*>(Ctx));
    }

    VOID OnComplete(NTSTATUS status, ULONG_PTR dwNumberOfBytesTransfered, REQUEST* irp)
    {
        if (0 > status)
        {
            DbgPrint("OnComplete[%x]: %x\n", irp->opcode, status);
        }
        else 
        switch (irp->opcode)
        {
        default:
            __debugbreak();

        case opCompression:
            StartWrite();
            break;

        case opWrite:
            if (dwNumberOfBytesTransfered == _BlockSize)
            {
                DoWrite(irp);
            }
            else
            {
                DbgPrint(":%I64x != %x\n", dwNumberOfBytesTransfered, _BlockSize);
            }
        }

        Release();
    }

    NTSTATUS Create(POBJECT_ATTRIBUTES poa, ULONGLONG size)
    {
        if (!(_pRequests = new REQUEST[_ConcurrentRequestCount]) ||
            !(_pData = VirtualAlloc(0, _BlockSize * _ConcurrentRequestCount, MEM_COMMIT, PAGE_READWRITE)))
        {
            return STATUS_INSUFFICIENT_RESOURCES;
        }

        ULONGLONG sws = _BlockSize - 1;
        LARGE_INTEGER as;

        _TotalSize = as.QuadPart = (size + sws) & ~sws;

        HANDLE hFile;
        IO_STATUS_BLOCK iosb;

        NTSTATUS status = NtCreateFile(&hFile,
            DELETE|FILE_GENERIC_READ|FILE_GENERIC_WRITE&~FILE_APPEND_DATA,
            poa, &iosb, &as, 0, 0, FILE_OVERWRITE_IF, 
            FILE_NON_DIRECTORY_FILE|FILE_NO_INTERMEDIATE_BUFFERING, 0, 0);

        if (0 > status)
        {
            return status;
        }

        _hFile = hFile;

        if (0 > (status = RtlSetIoCompletionCallback(hFile, _OnComplete, 0)))
        {
            return status;
        }

        static USHORT cmp = COMPRESSION_FORMAT_NONE;

        REQUEST* irp = _pRequests;

        irp->pTest = this;
        irp->opcode = opCompression;

        AddRef();
        status = NtFsControlFile(hFile, 0, 0, irp, irp, FSCTL_SET_COMPRESSION, &cmp, sizeof(cmp), 0, 0);

        if (0 > status)
        {
            OnComplete(status, 0, irp);
        }

        return status;
    }
};

void WriteSpeed(POBJECT_ATTRIBUTES poa, ULONGLONG size, ULONG BlockSize, ULONG ConcurrentRequestCount)
{
    BOOLEAN b;
    NTSTATUS status = RtlAdjustPrivilege(SE_MANAGE_VOLUME_PRIVILEGE, TRUE, FALSE, &b);

    if (0 <= status)
    {
        status = STATUS_INSUFFICIENT_RESOURCES;

        if (WriteTest * pTest = new WriteTest(BlockSize, ConcurrentRequestCount))
        {
            status = pTest->Create(poa, size);

            pTest->Release();

            if (0 <= status)
            {
                MessageBoxW(0, 0, L"Test...", MB_OK|MB_ICONINFORMATION);
            }
        }
    }
}
0 голосов
/ 23 января 2019

Одна область, которая может дать вам улучшение, - это чтобы ваши потоки работали постоянно, а каждое чтение из очереди.

В данный момент каждый раз, когда вы идете писать, вы создаете 4 потока (что медленно), а затем они разрушаются в конце функции. Вы увидите ускорение, по крайней мере, на время процессора вашей функции, если вы создадите потоки в начале и все они будут читать из отдельных очередей в бесконечном цикле.

Они просто проверят после МАЛЕНЬКОЙ задержки, есть ли что-нибудь в их очереди, если они есть, они напишут все это. Ваша единственная проблема заключается в том, что порядок данных сохраняется.

...