Позвольте мне начать с примера кода. Я сделал минимальный тестовый пример для этого. Для воспроизведения необходимы две части:
Первый исполняемый файл, небольшое приложение, которое использует 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;
}
Второй исполняемый файл, небольшое приложение, которое делает что-то глупое, которое тратит время. Давайте назовем это Приложение :
#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 isn 'действительно ничего не делаю). Однако реальность совсем иная:
- Запуск Приложение (проводник Windows / Ctrl-F5). Это займет примерно 1 секунду на каждую итерацию на моем компьютере.
- Запустите Приложение в отладчике Visual Studio (F5). Опять же, около 1 секунды на итерацию. Что бы я ожидал.
- Запустите Отладчик в отладчике Visual Studio (F5). Опять же, около 1 секунды на итерацию. Опять же, что я и ожидал.
- Запустить Отладчик (просто из Windows Explorer или ctrl-F5). На этот раз нам придется подождать ок. 4 секунды (!) На итерацию. Не то, что я ожидал!
Я сузил проблему до аргумента vector<uint8_t> data
, который передается по значению (вызывая копию c'tor).
Мне бы очень хотелось узнать, что здесь происходит ... Почему работает отладчик в 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)
Особенно RtlpNtMakeTevenKey, кажется, часто появляется. К сожалению, я понятия не имею, что это значит, и Google, похоже, не помогает ...