You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Windows下C++优化PCIe到NVMe磁盘读写性能的技术咨询

软件层面性能优化方案

针对你的PCIe数据读取转NVMe写入的应用,以下是具体的软件优化措施:

1. 实现生产者-消费者并行模型,让读取与写入异步执行

当前代码是读一块、写一块的串行逻辑,PCIe读取和磁盘写入互相等待,严重浪费带宽。改用双缓冲区(或多缓冲区)的生产者消费者模型,让PCIe读取数据到空闲缓冲区的同时,磁盘写入另一块缓冲区的数据,实现并行操作:

示例思路:

// 定义两个缓冲区和同步机制
std::array<std::unique_ptr<uint32_t[]>, 2> buffers;
std::mutex mtx;
std::condition_variable cv;
bool buffer_used[2] = {false, false};
size_t buffer_size = dma_block_size_bytes / sizeof(uint32_t);

// 生产者线程:负责从PCIe读取数据到空闲缓冲区
void producer(xdma_device& dev) {
    int idx = 0;
    while (/* 读取未完成的判断条件 */) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [&](){ return !buffer_used[idx]; });
        
        // 读取数据到当前缓冲区
        size_t bytes_read = dev.read_from_engine(buffers[idx].get(), buffer_size * sizeof(uint32_t));
        
        buffer_used[idx] = true;
        cv.notify_one();
        idx = (idx + 1) % 2;
    }
}

// 消费者线程:负责将已填满的缓冲区写入磁盘
void consumer(std::ofstream& outfile) {
    int idx = 0;
    while (/* 写入未完成的判断条件 */) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [&](){ return buffer_used[idx]; });
        
        // 写入缓冲区数据到磁盘
        outfile.write(reinterpret_cast<char*>(buffers[idx].get()), buffer_size * sizeof(uint32_t));
        
        buffer_used[idx] = false;
        cv.notify_one();
        idx = (idx + 1) % 2;
    }
}

2. 优化文件写入策略

2.1 增大文件IO缓冲区

默认std::ofstream的缓冲区很小,手动设置更大的缓冲区(比如64KB~2MB),减少系统调用次数:

const size_t file_buf_size = 1024 * 1024; // 1MB
std::unique_ptr<char[]> file_buf(new char[file_buf_size]);
outfile.rdbuf()->pubsetbuf(file_buf.get(), file_buf_size);

2.2 使用Win32直接IO+异步IO

对于NVMe磁盘,使用**直接IO(FILE_FLAG_NO_BUFFERING)可以绕开系统文件缓存,避免额外的内存拷贝;配合异步IO(FILE_FLAG_OVERLAPPED)**进一步提升并行性:

HANDLE hFile = CreateFile(L"D:\\output.bin", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
                          FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED | FILE_FLAG_WRITE_THROUGH, NULL);
// 后续用WriteFile+OVERLAPPED结构体实现异步写入

注意:使用FILE_FLAG_NO_BUFFERING时,写入的缓冲区大小和偏移必须对齐到磁盘扇区大小(通常是4KB)。

3. 内存对齐与大页分配

PCIe DMA传输对内存地址有对齐要求,使用未对齐的内存会导致额外的拷贝操作。改用对齐内存分配:

// 用VirtualAlloc分配对齐到4KB的内存
size_t aligned_size = (dma_array_elements * sizeof(uint32_t) + 4095) & ~4095;
uint32_t* read_data = static_cast<uint32_t*>(VirtualAlloc(NULL, aligned_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE));

// 或者用aligned_alloc(C++17+)
uint32_t* read_data = static_cast<uint32_t*>(aligned_alloc(4096, dma_array_elements * sizeof(uint32_t)));

同时启用大页内存(需要系统权限),可以减少TLB miss,提升内存访问效率。

4. 调整ReadFile为异步模式

当前read_from_engine使用同步ReadFile,改为异步重叠IO,让PCIe读取操作在后台执行,无需等待完成:

size_t xdma_device::read_from_engine(void* buffer, size_t size, OVERLAPPED& ov) {
    unsigned long num_bytes_read;
    if (!ReadFile(c2h0.h, buffer, (DWORD)size, &num_bytes_read, &ov)) {
        if (GetLastError() != ERROR_IO_PENDING) {
            throw std::runtime_error("Read failed: " + std::to_string(GetLastError()));
        }
        // 后续用GetOverlappedResult等待完成,或绑定回调
    }
    return num_bytes_read;
}

5. 优化块大小匹配硬件特性

测试不同的dma_block_size_bytes,匹配PCIe的传输粒度(比如64KB、256KB)和NVMe的最优写入块大小(通常是128KB~1MB)。过小的块会导致频繁的IO调用,过大的块会增加内存占用和延迟。

6. 减少不必要的线程开销

当前代码中on_streaming_read_button_clicked用QtConcurrent::run启动线程,start_read_thread又创建了一个read_thread,属于冗余线程创建。直接在QtConcurrent::run中执行读取逻辑,或者使用Qt的QThreadPool复用线程,减少线程创建销毁的开销。

7. 关闭调试输出

性能测试时关闭qDebug()和std::cout输出,这些IO操作会严重拖慢程序运行速度,仅在调试阶段启用。


内容的提问来源于stack exchange,提问作者Lubron

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 18:55:15