Подтвердить что ты не робот

Как долго создается и завершается создание потоков под Windows?

Я разделил сложную задачу обработки массива на несколько потоков, чтобы воспользоваться преимуществами многоядерной обработки, и вижу большие преимущества. В настоящее время, в начале задания, я создаю потоки, а затем жду, пока они завершатся, когда они завершат свою работу. Обычно я создаю примерно в четыре раза больше потоков, так как есть ядра, так как каждый поток может занять различное время, а дополнительные потоки гарантируют, что все ядра остаются занятыми большую часть времени. Мне было интересно, будет ли много преимуществ производительности при создании потоков, когда программа загорается, сохраняя их бездействующими до тех пор, пока они не потребуются, и используйте их при начале обработки. Проще говоря, сколько времени требуется, чтобы начать и закончить новый поток выше и выше обработки в потоке? Я начинаю использовать потоки, используя

CWinThread *pMyThread = AfxBeginThread(CMyThreadFunc,&MyData,THREAD_PRIORITY_NORMAL);

Обычно я использую 32 потока через 8 ядер в 64-битной архитектуре. Этот процесс в настоящее время принимает < 1 секунда, и загорается каждый раз, когда дисплей обновляется. Если начальный и конечный поток равен < 1 мс, возврат не оправдывает усилия. У меня возникли трудности с профилированием этого.

A связанный с этим вопрос помогает, но немного расплывчато для того, что мне нужно. Любые отзывы оцениваются.

4b9b3361

Ответ 1

Я написал это довольно давно, когда у меня был тот же основной вопрос (вместе с другим, который будет очевидным). Я обновил его, чтобы показать немного больше о том, сколько времени требуется для создания потоков, но сколько времени требуется для запуска потоков:

#include <windows.h>
#include <iostream>
#include <time.h>
#include <vector>

const int num_threads = 32;

const int switches_per_thread = 100000;

DWORD __stdcall ThreadProc(void *start) {
    QueryPerformanceCounter((LARGE_INTEGER *) start);
    for (int i=0;i<switches_per_thread; i++)
        Sleep(0);
    return 0;
}

int main(void) {
    HANDLE threads[num_threads];
    DWORD junk;

    std::vector<LARGE_INTEGER> start_times(num_threads);

    LARGE_INTEGER l;
    QueryPerformanceCounter(&l);

    clock_t create_start = clock();
    for (int i=0;i<num_threads; i++)
        threads[i] = CreateThread(NULL, 
                            0, 
                            ThreadProc, 
                            (void *)&start_times[i], 
                            0, 
                            &junk);
    clock_t create_end = clock();

    clock_t wait_start = clock();
    WaitForMultipleObjects(num_threads, threads, TRUE, INFINITE);
    clock_t wait_end = clock();

    double create_millis = 1000.0 * (create_end - create_start) / CLOCKS_PER_SEC / num_threads;
    std::cout << "Milliseconds to create thread: " << create_millis << "\n";
    double wait_clocks = (wait_end - wait_start);
    double switches = switches_per_thread*num_threads;
    double us_per_switch = wait_clocks/CLOCKS_PER_SEC*1000000/switches;
    std::cout << "Microseconds per thread switch: " << us_per_switch;

    LARGE_INTEGER f;
    QueryPerformanceFrequency(&f);

    for (auto s : start_times) 
        std::cout << 1000.0 * (s.QuadPart - l.QuadPart) / f.QuadPart <<" ms\n";

    return 0;
}

Примеры результатов:

Milliseconds to create thread: 0.015625
Microseconds per thread switch: 0.0479687

Первые несколько строк времени запуска выглядят следующим образом:

0.0632517 ms
0.117348 ms
0.143703 ms
0.18282 ms
0.209174 ms
0.232478 ms
0.263826 ms
0.315149 ms
0.324026 ms
0.331516 ms
0.3956 ms
0.408639 ms
0.4214 ms

Обратите внимание, что хотя они монотонно возрастают, это не гарантируется (хотя определенная тенденция в этом общем направлении).

Когда я впервые написал это, единицы, которые я использовал, имели больше смысла - на 33 МГц 486 эти результаты не были крошечными фракциями, подобными этому.:-) Полагаю, что когда-нибудь, когда я почувствую амбициозность, я должен переписать это, чтобы использовать std::async для создания потоков и std::chrono для выполнения синхронизации, но...

Ответ 2

Некоторые советы:

  • Если у вас есть много рабочих элементов для обработки (или их не так много, но вы должны повторять весь процесс времени), убедитесь, что вы используете какой-то пул потоков. Таким образом, вам не придется постоянно воссоздавать потоки, и ваш исходный вопрос больше не имеет значения: потоки будут создаваться только один раз. Я напрямую использую API QueueUserWorkItem (поскольку мое приложение не использует MFC), даже если это не слишком болезненно. Но в MFC у вас могут быть возможности более высокого уровня, чтобы использовать пул потоков. (http://support.microsoft.com/kb/197728)
  • Попробуйте выбрать оптимальный объем работы для одного рабочего элемента. Конечно, это зависит от особенностей вашего программного обеспечения: предполагается ли оно в реальном времени, или это число хруст в фоновом режиме? Если это не в режиме реального времени, то слишком небольшое количество работы на рабочий элемент может повредить производительность: за счет увеличения доли накладных расходов на распределение работы по потокам.
  • Так как аппаратные конфигурации могут быть очень разными, если у ваших конечных пользователей могут быть разные машины, вы можете включить некоторые процедуры калибровки асинхронно во время запуска программного обеспечения, чтобы вы могли оценить, сколько времени потребуется для определенной операции. Результатом калибровки может быть ввод для более эффективного определения размера работы для реальных вычислений.

Ответ 3

Мне было интересно узнать о современном планировщике Windows, поэтому я написал еще одно тестовое приложение. Я сделал свою лучшую попытку измерить время остановки нити, по желанию раскручивая наблюдающую нить.

// Tested on Windows 10 v1903 with E5-1660 v3 @ 3.00GHz, 8 Core(s), 16 Logical Processor(s)
// Times are (min, average, max) in milliseconds.

threads: 100, iterations: 1, testStop: true
Start(0.1083, 5.3665, 13.7103) - Stop(0.0341, 1.5122, 11.0660)

threads: 32, iterations: 3, testStop: true
Start(0.1349, 1.6423, 3.5561) - Stop(0.0396, 0.2877, 3.5195)
Start(0.1093, 1.4992, 3.3982) - Stop(0.0351, 0.2734, 2.0384)
Start(0.1159, 1.5345, 3.5754) - Stop(0.0378, 0.4938, 3.2216)

threads: 4, iterations: 3, testStop: true
Start(0.2066, 0.3553, 0.4598) - Stop(0.0410, 0.1534, 0.4630)
Start(0.2769, 0.3740, 0.4994) - Stop(0.0414, 0.1028, 0.2581)
Start(0.2342, 0.3602, 0.5650) - Stop(0.0497, 0.2199, 0.3620)

threads: 4, iterations: 3, testStop: false
Start(0.1698, 0.2492, 0.3713)
Start(0.1473, 0.2477, 0.4103)
Start(0.1756, 0.2909, 0.4295)

threads: 1, iterations: 10, testStop: false
Start(0.1910, 0.1910, 0.1910)
Start(0.1685, 0.1685, 0.1685)
Start(0.1564, 0.1564, 0.1564)
Start(0.1504, 0.1504, 0.1504)
Start(0.1389, 0.1389, 0.1389)
Start(0.1234, 0.1234, 0.1234)
Start(0.1550, 0.1550, 0.1550)
Start(0.2800, 0.2800, 0.2800)
Start(0.1587, 0.1587, 0.1587)
Start(0.1877, 0.1877, 0.1877)

Источник:

#include <windows.h>
#include <iostream>
#include <vector>
#include <chrono>
#include <iomanip>

using namespace std::chrono;

struct Test
{
    HANDLE Thread = { 0 };
    time_point<steady_clock> Creation;
    time_point<steady_clock> Started;
    time_point<steady_clock> Stopped;
};

DWORD __stdcall ThreadProc(void* lpParamater) {
    auto test = (Test*)lpParamater;
    test->Started = steady_clock::now();
    return 0;
}

DWORD __stdcall TestThreadsEnded(void* lpParamater) {
    auto& tests = *(std::vector<Test>*)lpParamater;

    std::size_t finished = 0;
    while (finished < tests.size())
    {
        for (auto& test : tests)
        {
            if (test.Thread != NULL && WaitForSingleObject(test.Thread, 0) == WAIT_OBJECT_0)
            {
                test.Stopped = steady_clock::now();
                test.Thread = NULL;
                finished++;
            }
        }
    }

    return 0;
}

duration<double, std::milli> diff(time_point<steady_clock> start, time_point<steady_clock> stop)
{
    return stop - start;
}

struct Stats
{
    double min;
    double average;
    double max;
};

Stats stats(const std::vector<double>& durations)
{
    Stats stats = { 1000, 0, 0 };

    for (auto& duration : durations)
    {
        stats.min = duration < stats.min ? duration : stats.min;
        stats.max = duration > stats.max ? duration : stats.max;
        stats.average += duration;
    }

    stats.average /= durations.size();

    return stats;
}

void TestScheduler(const int threadCount, const int iterations, const bool testStop)
{
    std::cout << "\nthreads: " << threadCount << ", iterations: " << iterations << ", testStop: " << (testStop ? "true" : "false") << "\n";

    for (auto i = 0; i < iterations; i++)
    {
        std::vector<Test> tests(threadCount);
        HANDLE testThreadsEnded = NULL;

        if (testStop)
        {
            testThreadsEnded = CreateThread(NULL, 0, TestThreadsEnded, (void*)& tests, 0, NULL);
        }

        for (auto& test : tests)
        {
            test.Creation = steady_clock::now();
            test.Thread = CreateThread(NULL, 0, ThreadProc, (void*)& test, 0, NULL);
        }

        if (testStop)
        {
            WaitForSingleObject(testThreadsEnded, INFINITE);
        }
        else
        {
            std::vector<HANDLE> threads;
            for (auto& test : tests) threads.push_back(test.Thread);
            WaitForMultipleObjects((DWORD)threads.size(), threads.data(), TRUE, INFINITE);
        }

        std::vector<double> startDurations;
        std::vector<double> stopDurations;
        for (auto& test : tests)
        {
            startDurations.push_back(diff(test.Creation, test.Started).count());
            stopDurations.push_back(diff(test.Started, test.Stopped).count());
        }

        auto startStats = stats(startDurations);
        auto stopStats = stats(stopDurations);

        std::cout << std::fixed << std::setprecision(4);
        std::cout << "Start(" << startStats.min << ", " << startStats.average << ", " << startStats.max << ")";
        if (testStop)
        {
            std::cout << " - ";
            std::cout << "Stop(" << stopStats.min << ", " << stopStats.average << ", " << stopStats.max << ")";
        }
        std::cout << "\n";
    }
}

int main(void)
{
    TestScheduler(100, 1, true);
    TestScheduler(32, 3, true);
    TestScheduler(4, 3, true);
    TestScheduler(4, 3, false);
    TestScheduler(1, 10, false);
    return 0;
}