让我从一些示例代码开始。我为此做了一个最小的测试用例。要复制,需要两块:
第一个可执行文件,一个使用CreateProcess
的小型应用程序。我们称它为[[Debugger。
#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。现在已经测试了四种方案。对于每种情况,我都为单次迭代(变量debugger
i
)花费了时间。我期望的是,运行App
(1)和运行Debugger(4)的速度大约相同(因为Debugger并没有做任何事情)。但是,现实却大不相同:vector<uint8_t> data
参数,该参数通过值传递(称为副本c'tor)。 我已经使用专有库向我的小调试器程序中添加了一些堆栈跟踪和性能分析功能...以将情况(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似乎没有帮助...
FLG_HEAP_ENABLE_TAIL_CHECK
,FLG_HEAP_ENABLE_FREE_CHECK
,FLG_HEAP_VALIDATE_PARAMETERS
)所有这些检查并用特殊模式(baadf00d
和abababab
填充所有分配的块]在块末尾)使所有堆分配/空闲变慢(在没有这种情况下进行比较)从程序的另一面来看,大多数时间用于从堆分配/释放内存。
配置文件也显示此-RtlAllocateHeap
,memset
-确定分配的块中是否填充了魔术图案,RtlpNtMakeTemporaryKey
-此由单个指令组成的“功能”-jmp ZwDeleteKey
-因此,您确实不在此功能内,但在靠近堆的另一个函数中,“靠近”它。
如所述 Simon Mourier
来自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-因此应用使用与调试器相同的环境-继承
但在情况(4)中-您不能自行设置CreateProcessW
_NO_DEBUG_HEAP=1
。结果使用了调试堆并且运行速度更慢。