为什么 THREAD_MODE_BACKGROUND_BEGIN 导致我的代码运行速度比 THREAD_PRIORITY_LOWEST 慢 20 倍?

Ben*_*enj 4 c++ performance winapi

所以我不会让你厌烦为什么,但我的应用程序可以选择使用 CRC 对非常大的文件(高达 50gb)执行一些完整性检查。因为我不想杀死用户的机器,如果他们打开这个选项,我在句柄上设置了 IoPriorityHintVeryLow 提示,并且还使用这个 API将线程优先级设置为 THREAD_MODE_BACKGROUND_BEGIN 。

我的代码中耗时的部分如下所示:

//
// Read one block of the changed data at a time, checking each CRC
//
DWORD blockNum = 0;
vector<BYTE> changeBuffer(DIRTY_BLOCK_SIZE);
outputDirtyBlockMap.reserve(crcList.size() / 8);
while (::ReadFile(hChangedFile, changeBuffer.data(), DIRTY_BLOCK_SIZE, &bytesRead, NULL) && bytesRead > 0)
{
    // Check for cancellation every 500 blocks, doing it every time reduces CPU performance by 50% since WaitForSingleObject is quite expensive
    if ((blockNum % 500 == 0) && IsCancelEventSignalled(hCancel))
    {
        RETURN_TRACED(ERROR_CANCELLED);
    }

    // Increase the size of the dirty block map?
    ULONG mapByte = blockNum / 8;
    if (mapByte == outputDirtyBlockMap.size())
    {
        outputDirtyBlockMap.resize(mapByte + 1);
    }

    DWORD mapBitNum = blockNum & 0x7L;
    UCHAR mapBit = 1 << (7 - mapBitNum);
    if (driverDirtyBlockMap.size() > mapByte && (driverDirtyBlockMap[mapByte] & mapBit))
    {
        //
        // The bit is already set in the drivers block map, we don't have to bother generating comparing CRCs for this block
        //
        outputDirtyBlockMap[mapByte] |= mapBit;
    }
    else
    {
        // Validate that the CRC hasn't changed, if it has, mark it as such in the dirty block map
        DWORD newCrc = CRC::Crc32(changeBuffer.data(), changeBuffer.size());
        if ((blockNum >= crcList.size() || newCrc != crcList[blockNum]))
        {
            OPTIONAL_DEBUG(DEBUG_DIRTY_BLOCK_MAP & DEBUG_VERBOSE, "Detected change at block [%u], CRC [new 0x%x != old 0x%x]", blockNum, newCrc, blockNum < crcList.size() ? crcList[blockNum] : 0x0);

            // The CRC is changed or the file has grown, mark it as such in the dirty block map
            outputDirtyBlockMap[mapByte] |= mapBit;
        }
    }

    ++blockNum;
}
Run Code Online (Sandbox Code Playgroud)

当我分析这段代码时,我非常惊讶地发现当这个循环在 THREAD_MODE_BACKGROUND_BEGIN 中运行时,它需要 74 秒来运行一个 500Mb 的文件。使用 THREAD_PRIORITY_LOWEST 运行时,运行 500Mb 文件需要 2.7 秒。(我现在已经测试了大约 8 次,这是平均值)

在这两种情况下,我正在测试的机器除了运行这个循环之外都是空闲的。所以问题:

为什么 THREAD_MODE_BACKGROUND_BEGIN 需要这么长时间?我原以为如果机器没有做任何其他事情,它仍然应该像其他任何优先级一样快速运行,因为它不需要优先级?

关于这个优先级,我有没有从文档中无法弄清楚的事情?

小智 5

设置后台模式有以下作用:

  • 将 I/O 优先级设置为非常低
  • 将内存优先级设置为 1
  • 将绝对线程优先级设置为 4

将相对线程优先级设置为 LOWEST 具有以下效果:

  • 将相对线程优先级设置为 -2(即:绝对 6,假设正常进程优先级)

因此,一般来说,特别是如果您受 I/O 限制(但即使在受 CPU 限制的情况下),您肯定会期望优先级为 4 的线程以非常低的 I/O 优先级和后台内存优先级 (1) 运行比具有前台内存优先级 (5) + 优先级为 6 的普通 I/O 优先级的线程执行得差得多......


And*_*ers 2

THREAD_MODE_* 与 THREAD_PRIORITY_* 不同也许并不令人惊讶?

我不知道是否在任何地方记录了确切的差异,但如果 CPU 支持核心停放且频率较低,后台模式尝试在单个核心上运行所有内容,我不会感到惊讶。

SetThreadPriority 文档还暗示了对线程执行的任何 I/O 的一些更改:

THREAD_PRIORITY_* 值影响线程的 CPU 调度优先级。对于执行文件I/O、网络I/O或数据处理等后台工作的线程,调整CPU调度优先级是不够的;即使是空闲的 CPU 优先级线程在使用磁盘和内存时也很容易干扰系统响应能力。执行后台工作的线程应使用 THREAD_MODE_BACKGROUND_BEGIN 和 THREAD_MODE_BACKGROUND_END 值来调整其资源调度优先级;与用户交互的线程不应使用 THREAD_MODE_BACKGROUND_BEGIN。

您是否尝试过衡量性能损失是否在ReadFileCRC 计算中?