【发布时间】: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、启动/退出进程等)。换句话说:不产生相关事件。零。
-
我注意到的一件事是向量复制构造函数使世界变得与众不同。没有它,时间几乎是你所期望的。有了它,奇怪的事情正在发生......
标签: c++ winapi visual-c++ createprocess