MSDN.WhiteKnight - Stack Overflow answers
Ответ на "Почему функция CreateThread запускает функцию сразу после создания?"
Answer 1036837
История: исполнение WriteConsoleOutputW занимает с каждым последующим обновлением занимает все больше времени, при чем размер буфера не меняется. Изначально была идея вынести вывод в отдельный поток, но оно все еще занимает 74-76 мс, что невероятно долго.
Хотелось бы увидеть, на каком размере буфера и в какой ОС вывод занимает так долго. У меня на Windows 10 при буфере 100х50 среднее время 0.1 - 0.2 мс, вот код для тестирования:
#include <windows.h> #include <stdio.h> const int W = 100; //ширина области рисования const int H = 50; //высота области рисования CHAR_INFO chiBuffer[W*H] = { 0 }; COORD coordBufSize; COORD coordBufCoord; HANDLE hStdout; SMALL_RECT srctWriteRect; void Render(int pos) { //рисуем звездочку в заданной позиции for (int i = 0; i < sizeof(chiBuffer) / sizeof(CHAR_INFO); i++) { chiBuffer[i].Attributes = FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_GREEN | BACKGROUND_BLUE; if (i == pos) chiBuffer[i].Char.UnicodeChar = L'*'; else chiBuffer[i].Char.UnicodeChar = L' '; } srctWriteRect.Top = 0; srctWriteRect.Left = 0; srctWriteRect.Bottom = H; srctWriteRect.Right = W; BOOL fSuccess = WriteConsoleOutputW( hStdout, // screen buffer to write to chiBuffer, // buffer to copy from coordBufSize, // col-row size of chiBuffer coordBufCoord, // top left src cell in chiBuffer &srctWriteRect); // dest. screen buffer rectangle if (!fSuccess) { printf("WriteConsoleOutput failed - (%d)\n", GetLastError()); } } int main(void) { BOOL fSuccess; hStdout = GetStdHandle(STD_OUTPUT_HANDLE); coordBufSize.Y = H; coordBufSize.X = W; coordBufCoord.X = 0; coordBufCoord.Y = 0; // Set the destination rectangle. srctWriteRect.Top = 0; srctWriteRect.Left = 0; srctWriteRect.Bottom = H; srctWriteRect.Right = W; const int N_RUNS = 50; //число запусков в пределах одного теста const int N_TESTS = 50; //число тестов float tsum = 0; DWORD t1; DWORD t2; Render(0); int pos = 1; for (int i = 0; i < N_TESTS; i++) { t1 = GetTickCount(); for (int j = 0; j < N_RUNS; j++) { Render(pos); pos++; if (pos >= W * H)pos = 0; } t2 = GetTickCount(); tsum += (t2 - t1)/(float)N_RUNS; //определим длительность отрисовки в пределах одного теста } //выведем среднее время отрисовки wchar_t s[100] = L""; swprintf(s,100,L"Render time = %.3f ms\n", tsum / (float)N_TESTS); MessageBoxW(GetConsoleWindow(), s, L"Results", MB_OK); getchar(); return 0; }
Но я слышал, что на Windows 7 и ранее консоль рисует медленнее.
Последнее что пришло в голову - разделить буфер на несколько частей и выводить их в нескольких потоках.
Идея распараллелить что-то с целью его ускорения редко является хорошей. Начать с того, что код может выполняться на компьютере с одним процессорным ядром, тогда "ускорение" в реальности обернется замедлением. Но даже если ядер несколько, реальный эффект от параллельности проявится только когда потоки не конкурируют за общие ресурсы. В случае консоли, я подозреваю, что там внутренне работает свой механизм синхронизации, и эффекта не будет.
Однако как я выяснил CreateThread запускает функции сразу после создания
Это не совсем так. Есть флаг CREATE_SUSPENDED:
The thread is created in a suspended state, and does not run until the ResumeThread function is called.
Но для вашей задачи это никак не поможет.
Реальная оптимизация, которая здесь могла бы помочь - это вместо вывода всего буфера проверять, какие области изменились, и выводить только их. Если на каждом шаге не весь экран изменяется, это может дать серьезное ускорение.
Дополнение
Если попробовать на большом буфере и распараллелить вот так с синхронизацией через события:
#include <windows.h> #include <stdio.h> void Render1(); void Render2(); const int W = 640; //ширина области рисования const int H = 320; //высота области рисования CHAR_INFO chiBuffer[W*H] = { 0 }; HANDLE hStdout; //первая часть COORD coordBufSize1; COORD coordBufCoord1; SMALL_RECT srctWriteRect1; //вторая часть COORD coordBufSize2; COORD coordBufCoord2; SMALL_RECT srctWriteRect2; //события для синхронизации HANDLE evtRendering1; HANDLE evtRendering2; HANDLE evtRendered1; HANDLE evtRendered2; DWORD WINAPI ThreadProc1(LPVOID lpThreadParameter) { while (1) { WaitForSingleObject(evtRendering1, INFINITE); //ждем сигнала ResetEvent(evtRendering1); //выводим первую половину Render1(); //сообщаем о завершении SetEvent(evtRendered1); } } DWORD WINAPI ThreadProc2(LPVOID lpThreadParameter) { while (1) { WaitForSingleObject(evtRendering2, INFINITE); //ждем сигнала ResetEvent(evtRendering2); //выводим первую половину Render2(); //сообщаем о завершении SetEvent(evtRendered2); } } void FillBuffer(int pos) { //рисуем звездочку в заданной позиции for (int i = 0; i < sizeof(chiBuffer) / sizeof(CHAR_INFO); i++) { chiBuffer[i].Attributes = FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_GREEN | BACKGROUND_BLUE; if (i == pos) chiBuffer[i].Char.UnicodeChar = L'*'; else chiBuffer[i].Char.UnicodeChar = L' '; } } void Render1() { srctWriteRect1.Top = 0; srctWriteRect1.Left = 0; srctWriteRect1.Bottom = H/2; srctWriteRect1.Right = W; BOOL fSuccess = WriteConsoleOutputW( hStdout, // screen buffer to write to chiBuffer, // buffer to copy from coordBufSize1, // col-row size of chiBuffer coordBufCoord1, // top left src cell in chiBuffer &srctWriteRect1); // dest. screen buffer rectangle if (!fSuccess) { printf("Render1: WriteConsoleOutput failed - (%d)\n", GetLastError()); } } void Render2() { srctWriteRect2.Top = H / 2; srctWriteRect2.Left = 0; srctWriteRect2.Bottom = H; srctWriteRect2.Right = W; BOOL fSuccess = WriteConsoleOutputW( hStdout, // screen buffer to write to &(chiBuffer[W*H/2]), // buffer to copy from coordBufSize2, // col-row size of chiBuffer coordBufCoord2, // top left src cell in chiBuffer &srctWriteRect2); // dest. screen buffer rectangle if (!fSuccess) { printf("Render2: WriteConsoleOutput failed - (%d)\n", GetLastError()); } } void Render(BOOL parallel) { if (parallel != FALSE) { //запускаем оба потока SetEvent(evtRendering1); SetEvent(evtRendering2); HANDLE ha[2] = { evtRendered1,evtRendered2 }; //ждем завершения вывода DWORD dwWaitResult = WaitForMultipleObjects( 2, // number of handles in array ha, // array of handles TRUE, // wait until all are signaled INFINITE); switch (dwWaitResult) { // All objects were signaled case WAIT_OBJECT_0: break; // An error occurred default: printf("WaitForMultipleObjects failed (%d)\n", GetLastError()); break; } ResetEvent(evtRendered1); ResetEvent(evtRendered2); } else { Render1(); Render2(); } } int main(void) { BOOL fSuccess; hStdout = GetStdHandle(STD_OUTPUT_HANDLE); coordBufSize1.Y = H/2; coordBufSize1.X = W; coordBufCoord1.X = 0; coordBufCoord1.Y = 0; coordBufSize2.Y = H / 2; coordBufSize2.X = W; coordBufCoord2.X = 0; coordBufCoord2.Y = 0/*H / 2*/; // Set the destination rectangle. srctWriteRect1.Top = 0; srctWriteRect1.Left = 0; srctWriteRect1.Bottom = H/2; srctWriteRect1.Right = W; srctWriteRect2.Top = H / 2; srctWriteRect2.Left = 0; srctWriteRect2.Bottom = H; srctWriteRect2.Right = W; const int N_TESTS = 500; //число тестов DWORD tsum = 0; DWORD t1; DWORD t2; evtRendering1 = CreateEvent( NULL, // default security attributes TRUE, // manual-reset event FALSE, // initial state is nonsignaled TEXT("evtRendering1") // object name ); evtRendering2 = CreateEvent( NULL, // default security attributes TRUE, // manual-reset event FALSE, // initial state is nonsignaled TEXT("evtRendering2") // object name ); evtRendered1 = CreateEvent( NULL, // default security attributes TRUE, // manual-reset event FALSE, // initial state is nonsignaled TEXT("evtRendered1") // object name ); evtRendered2 = CreateEvent( NULL, // default security attributes TRUE, // manual-reset event FALSE, // initial state is nonsignaled TEXT("evtRendered2") // object name ); HANDLE hThread = CreateThread( NULL, // default security 0, // default stack size ThreadProc1, // name of the thread function NULL, // no thread parameters 0, // default startup flags NULL); if (hThread == NULL) { printf("CreateThread failed (%d)\n", GetLastError()); return 1; } hThread = CreateThread( NULL, // default security 0, // default stack size ThreadProc2, // name of the thread function NULL, // no thread parameters 0, // default startup flags NULL); if (hThread == NULL) { printf("CreateThread failed (%d)\n", GetLastError()); return 1; } /* *** Начало теста *** */ BOOL parallel = TRUE; FillBuffer(0); Render(parallel); int pos = 1; for (int i = 0; i < N_TESTS; i++) { FillBuffer(pos); t1 = GetTickCount(); Render(parallel); t2 = GetTickCount(); pos++; if (pos >= W * H)pos = 0; tsum += (t2 - t1); //определим длительность отрисовки в пределах одного теста } //выведем среднее время отрисовки wchar_t s[100] = L""; swprintf(s,100,L"Render time = %.3f ms\n", tsum / (float)N_TESTS); MessageBoxW(GetConsoleWindow(), s, L"Results", MB_OK); getchar(); return 0; }
То получается
- без параллельности: 1.2 мс
- с параллельностью: 1.6 мс
То есть вроде как пользы от распараллеливания нет, даже наоборот, получается медленнее за счет накладных расходов на ожидание.
Content is retrieved from StackExchange API.
Auto-generated by ruso-archive tools.