使用 CreateProcess 时出现奇怪的减速

Posted

技术标签:

【中文标题】使用 CreateProcess 时出现奇怪的减速【英文标题】:Weird slowdown when using CreateProcess 【发布时间】:2019-10-09 18:31:22 【问题描述】:

让我从一些示例代码开始。我为此做了一个最小的测试用例。要重现,需要两块:

第一个可执行文件,一个使用CreateProcess 的小应用程序。我们称之为调试器

#include <Windows.h>
#include <string>
#include <iostream>
#include <vector>

int main()

    STARTUPINFO         si = 0;
    PROCESS_INFORMATION pi = 0;
    si.cb = sizeof(si);

    // Starts the 'App':
    auto exe = L"C:\\Tests\\x64\\Release\\TestProject.exe";
    std::vector<wchar_t> tmp;
    tmp.resize(1024);
    memcpy(tmp.data(), exe, (1 + wcslen(exe)) * sizeof(wchar_t));

    auto result = CreateProcess(NULL, tmp.data(), NULL, NULL, FALSE, DEBUG_PROCESS, NULL, NULL, &si, &pi);
    DEBUG_EVENT debugEvent =  0 ;
    bool continueDebugging = true;
    while (continueDebugging) 
    
        if (WaitForDebugEvent(&debugEvent, INFINITE))
        
            std::cout << "Event " << debugEvent.dwDebugEventCode << std::endl;
            if (debugEvent.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT)
            
                continueDebugging = false;
            

            // I real life, this is more complicated... For a minimum test, this will do
            auto continueStatus = DBG_CONTINUE;
            ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, continueStatus);

        
    
    std::cout << "Done." << std::endl;

    std::string s;
    std::getline(std::cin, s);

    return 0;

第二个可执行文件,一个小应用程序,做一些愚蠢的事情,耗费时间。我们称之为App

#include <Windows.h>
#include <iostream>
#include <string>
#include <vector>

__declspec(noinline) void CopyVector(uint64_t value, std::vector<uint8_t> data)

    // irrelevant.
    data.resize(10);
    *reinterpret_cast<uint64_t*>(data.data()) = value;


int main(int argc, const char** argv)

    for (int i = 0; i < 10; ++i) 
    
        LARGE_INTEGER StartingTime, EndingTime, ElapsedMicroseconds;
        LARGE_INTEGER Frequency;

        QueryPerformanceFrequency(&Frequency);
        QueryPerformanceCounter(&StartingTime);

        // Activity to be timed
        std::vector<uint8_t> tmp;
        tmp.reserve(10'000'000 * 8);

        // The activity (*)
        uint64_t v = argc;
        for (size_t j = 0; j < 10'000'000; ++j)
        
            v = v * 78239742 + 1278321;

            CopyVector(v, tmp);
        

        QueryPerformanceCounter(&EndingTime);
        ElapsedMicroseconds.QuadPart = EndingTime.QuadPart - StartingTime.QuadPart;

        // We now have the elapsed number of ticks, along with the
        // number of ticks-per-second. We use these values
        // to convert to the number of elapsed microseconds.
        // To guard against loss-of-precision, we convert
        // to microseconds *before* dividing by ticks-per-second.

        ElapsedMicroseconds.QuadPart *= 1000000;
        ElapsedMicroseconds.QuadPart /= Frequency.QuadPart;

        std::cout << "Elapsed: " << ElapsedMicroseconds.QuadPart << " microsecs" << std::endl;
    

    std::string s;
    std::getline(std::cin, s);

注意调试器 应用程序实际上并没有做任何事情。它只是坐在那里,等到 app 完成。我用的是最新版本的VS2019。

我现在测试了四个场景。对于每种情况,我都计算了单次迭代(变量i)所需的时间。我所期望的是运行 App (1) 和运行 Debugger (4) 的速度大致相同(因为 Debugger 并没有真正做任何事情)。然而,现实却大不相同:

    运行应用程序(Windows 资源管理器/Ctrl-F5)。在我的电脑上,每次迭代大约需要 1 秒。 在 Visual Studio 调试器 (F5) 中运行 App。同样,每次迭代大约需要 1 秒。我的期望。 在 Visual Studio 调试器 (F5) 中运行 Debugger。同样,每次迭代大约需要 1 秒。再次,我所期望的。 运行 Debugger(只需从 Windows 资源管理器或 ctrl-F5)。这一次,我们必须等待大约。每次迭代 4 秒 (!)。不是我所期望的!

我已将问题范围缩小到 vector&lt;uint8_t&gt; data 参数,它是按值传递的(调用复制 c'tor)。

我很想知道这里发生了什么...为什么运行 debugger 慢了 4 倍,而它什么也没做?

-- 更新--

我使用专有库为我的小调试器程序添加了一些堆栈跟踪和分析功能......以比较情况(3)和(4)。我基本上已经计算了堆栈跟踪中指针出现的频率。

这些方法可以在案例(4)的结果中找到显着,但在案例(3)中不显着。开头的数字是一个简单的计数器:

352       - inside memset (address: 0x7ffa727349d5)
284       - inside RtlpNtMakeTemporaryKey (address: 0x7ffa727848b2)
283       - inside RtlAllocateHeap (address: 0x7ffa726bbaba)
261       - inside memset (address: 0x7ffa727356af)
180       - inside RtlFreeHeap (address: 0x7ffa726bfc10)
167       - inside RtlpNtMakeTemporaryKey (address: 0x7ffa72785408)
161       - inside RtlGetCurrentServiceSessionId (address: 0x7ffa726c080f)

特别是 RtlpNtMakeTemporaryKey 似乎出现了很多。不幸的是,我不知道这意味着什么,而且 Google 似乎也没有提供帮助......

【问题讨论】:

“没有附加任何东西”是什么意思? “每行 n 秒”是什么意思? “TestProject.exe”是“应用程序”? @WernerHenze '没有附加任何东西' 意味着只是从 Windows 资源管理器启动 EXE。 “每行 N 秒”意味着生成 i 的单次迭代需要 N 秒。是的,“TestProject.exe”就是应用程序。我会更新问题,感谢您的反馈。 “调试器”在任何情况下都能看到任何调试事件吗?哪个和多少?这有区别吗? @WernerHenze 好问题,这也是我最初的猜测。答案是:不在测试执行期间,只在之前和之后(加载 dll、启动/退出进程等)。换句话说:不产生相关事件。零。 我注意到的一件事是向量复制构造函数使世界变得与众不同。没有它,时间几乎是你所期望的。有了它,奇怪的事情正在发生...... 【参考方案1】:

调试堆不同。阅读The Windows Heap Is Slow When Launched from the Debugger

和Accelerating Debug Runs, Part 1: _NO_DEBUG_HEAP

当进程初始化系统(ntdll)检查存在调试器时,如果检查环境变量_NO_DEBUG_HEAP 存在并且设置为非零。如果否 - 设置 NtGlobalFlag(在 PEB 中)以调试堆使用(FLG_HEAP_ENABLE_TAIL_CHECKFLG_HEAP_ENABLE_FREE_CHECKFLG_HEAP_VALIDATE_PARAMETERS)所有这些检查并用特殊模式填充所有分配的块(baadf00dabababab 在块的末尾)使所有堆分配/释放变慢(比较没有这种情况)

另一方面,您的程序大部分时间用于从堆中分配/释放内存。

配置文件也显示了这个 - RtlAllocateHeap, memset - 确定分配块时填充了魔法模式,RtlpNtMakeTemporaryKey - 这个“函数”由单个指令组成 - jmp ZwDeleteKey - 所以你真的不在这个函数里面但“靠近”它,在另一个与堆相关的函数中。


如上所述 Simon Mourier - 为什么情况 (2) 和 (3) 运行速度与 (1) 一样快(当没有调试器时)但只有情况 (4) 更慢?

来自C++ Debugging Improvements in Visual Studio "14"

因此,为了提高启动 C++ 应用程序时的性能 Visual Studio 调试器,在 Visual Studio 2015 中我们禁用了操作 系统的调试堆。

这是通过在调试过程环境中设置 _NO_DEBUG_HEAP=1 来完成的。所以比较Accelerating Debug Runs, Part 1: _NO_DEBUG_HEAP(文章是旧的) - 现在这是默认情况。

我们可以通过应用程序中的下一个代码检查这一点:

WCHAR _no_debug_heap[32];
if (GetEnvironmentVariable(L"_NO_DEBUG_HEAP", _no_debug_heap, _countof(_no_debug_heap)))

    DbgPrint("_NO_DEBUG_HEAP=%S\n", _no_debug_heap);

else

    DbgPrint("error=%u\n", GetLastError());

所以当我们在调试器下启动应用程序时 - 没有调试堆,因为 VS 调试器添加了_NO_DEBUG_HEAP=1。当您在调试器和应用程序下从调试器启动调试器时 - 从 CreateProcessW 函数

lpEnvironment

指向新进程的环境块的指针。如果这 参数为NULL,新进程使用调用的环境 过程。

因为你在这里传递了 0 - 所以应用程序使用与调试器相同的环境 - 继承 _NO_DEBUG_HEAP=1

但在情况 (4) 中 - 您没有自行设置 _NO_DEBUG_HEAP=1。结果调试堆使用并且运行更慢。

【讨论】:

每个人都知道由于各种原因在调试器下运行速度较慢,但​​是 - 或者我错过了一些东西 - 这并不能解释为什么 4(不在 VS 调试器下运行)更慢 i> 大于 3(在 VS 调试器下运行)。 @SimonMourier - 同意你的问题。这对我来说只是不清楚。可能是这个 - thetweaker.files.wordpress.com/2014/09/… 在项目(应用程序和调试器)中设置,这解释了为什么当从 VS 调试器运行代码时没有调试器比较慢,但这是非常不寻常的,默认情况下不是。这里需要更多信息和研究,但我确定时间来源不同 - 在 4 种情况下调试堆。我在所有 3 种情况下都要求打印NtGlobalFlags @SimonMourier 我自己不能在这里进行测试/检查。但我确信堆设置的不同是不同的来源。所以需要在开始时检查NtGlobalFlag。然后,如果我的假设正确,请在应用程序中检查 _NO_DEBUG_HEAP - 此变量是否存在,如果存在 - 它的值 我还尝试了“启用 Windows 调试堆分配器(仅限本机)”工具/选项/调试设置。选中后,3 和 4 运行相同。所以总的来说,它看起来意味着在 VS 调试器设置 _NO_DEBUG_HEAP=1 下运行,除非选中此设置。我认为这就是他们在这里所说的:devblogs.microsoft.com/cppblog/… "... when launching C++ apps with the VS debugger ... we disable the operating system’s debug heap ...,而只需调用 CreateProcess w DEBUG_PROCESS 即可启用它。 @SimonMourier - 我只是在 vs2019 调试器下启动代码并查看 _NO_DEBUG_HEAP=1 变量已设置。这都是解释。即使我们没有在项目设置中手动设置它 - 现在默认设置。因为这 2 和 1 同时运行。如果不直接设置,则继承环境。结果 3 也作为 1 运行(应用程序从调试器继承 _NO_DEBUG_HEAP=1)。但在第 4 种情况下 - 没有 _NO_DEBUG_HEAP=1

以上是关于使用 CreateProcess 时出现奇怪的减速的主要内容,如果未能解决你的问题,请参考以下文章

CreateProcess 时出现 ERROR_FILE_NOT_FOUND

金蝶K3在建立帐套时出现无法创建数据库,错误xpsql.cpp,错误5来自createprocess

使用子类 collectionViewFlowLayout 时出现奇怪的错误

尝试使用 matplotlib 绘图时出现奇怪的错误

使用 NSFetchedResultsController 时出现奇怪的删除行为

使用“innerHTML”时出现奇怪的错误..?