使用 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<uint8_t> 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_CHECK
、FLG_HEAP_ENABLE_FREE_CHECK
、FLG_HEAP_VALIDATE_PARAMETERS
)所有这些检查并用特殊模式填充所有分配的块(baadf00d
和abababab
在块的末尾)使所有堆分配/释放变慢(比较没有这种情况)
另一方面,您的程序大部分时间用于从堆中分配/释放内存。
配置文件也显示了这个 - 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 时出现奇怪的错误