IT TIP

스레드 컨텍스트 전환 오버 헤드를 추정하는 방법은 무엇입니까?

itqueen 2020. 12. 25. 10:42
반응형

스레드 컨텍스트 전환 오버 헤드를 추정하는 방법은 무엇입니까?


실시간 마감일로 스레드 응용 프로그램의 성능을 향상 시키려고합니다. Windows Mobile에서 실행 중이며 C / C ++로 작성되었습니다. 높은 빈도의 스레드 전환이 가시적 인 오버 헤드를 유발할 수 있다는 의혹이 있지만이를 증명하거나 반증 할 수는 없습니다. 모두가 알다시피 증거의 부족은 반대의 증거가 아닙니다. :).

따라서 내 질문은 두 가지입니다.

  • 존재하는 경우 스레드 컨텍스트 전환 비용에 대한 실제 측정 값은 어디에서 찾을 수 있습니까?

  • 테스트 애플리케이션을 작성하는 데 시간을 들이지 않고 기존 애플리케이션에서 스레드 전환 오버 헤드를 추정하는 방법은 무엇입니까?

  • 주어진 스레드에 대한 컨텍스트 스위치 (켜기 / 끄기) 수를 찾는 방법을 아는 사람이 있습니까?


테스트 애플리케이션을 작성하고 싶지 않다고 말했지만 ARM9 Linux 플랫폼에서 이전 테스트에서 오버 헤드가 무엇인지 알아 내기 위해이 작업을 수행했습니다. 다음과 같은 두 개의 스레드가 있습니다. 초당 수행 할 수있는 컨텍스트 전환 수 물론 이것은 실제로 정확하지는 않지만, 요점은 두 스레드가 서로 CPU를 양보했으며 너무 빨라 오버 헤드에 대해 생각하는 것이 더 이상 의미가 없다는 것입니다. 따라서 존재하지 않을 수있는 문제에 대해 너무 많이 생각하는 대신 간단한 테스트를 작성하기 만하면됩니다.

그 외에는 성능 카운터와 함께 제안 된 1800과 같이 시도 할 수 있습니다.

아, 그리고 Windows CE 4.X에서 실행되는 응용 프로그램이 기억납니다. 여기서는 때때로 집중적 인 스위칭이있는 4 개의 스레드가 있고 성능 문제가 발생하지 않았습니다. 또한 스레드없이 코어 스레딩을 구현하려고 시도했지만 성능 향상이 없었습니다 (GUI는 훨씬 느리게 응답했지만 다른 모든 것은 동일했습니다). 컨텍스트 전환 수를 줄이거 나 스레드를 완전히 제거하여 (테스트 용으로 만) 동일하게 시도 할 수 있습니다.


기존 플랫폼의 웹 어딘가에서이 오버 헤드를 찾을 수 있을지 의심됩니다. 너무 많은 다른 플랫폼이 있습니다. 오버 헤드는 두 가지 요인에 따라 달라집니다.

  • 다른 CPU 유형에서 필요한 작업이 더 쉽고 어려울 수 있으므로 CPU
  • 다른 커널은 각 스위치에서 다른 작업을 수행해야하기 때문에 시스템 커널

다른 요인에는 전환이 발생하는 방식이 포함됩니다. 전환은 다음과 같은 경우에 발생할 수 있습니다.

  1. 스레드는 모든 시간 퀀텀을 사용했습니다. 스레드가 시작될 때, 다음 사람을 결정할 커널로 제어권을 반환하기 전에 주어진 시간 동안 실행될 수 있습니다.

  2. 스레드가 선점되었습니다. 이것은 다른 스레드가 CPU 시간을 필요로하고 우선 순위가 더 높은 경우에 발생합니다. 예를 들어 마우스 / 키보드 입력을 처리하는 스레드는 그러한 스레드 일 수 있습니다. 현재 어떤 스레드 가 CPU를 소유하고 있어도 사용자가 무언가를 입력하거나 클릭 할 때 현재 스레드 시간 퀀텀이 완전히 소모 될 때까지 기다리지 않고 시스템이 즉시 반응하는 것을보고 싶어합니다. 따라서 일부 시스템은 현재 스레드를 즉시 중지하고 우선 순위가 더 높은 다른 스레드에 제어를 반환합니다.

  3. 스레드는 일부 작업을 차단하거나 실행을 중지하기 위해 sleep () (또는 유사)을 호출하기 때문에 더 이상 CPU 시간이 필요하지 않습니다.

이 세 가지 시나리오는 이론적으로 스레드 전환 시간이 다를 수 있습니다. 예를 들어, sleep ()에 대한 호출은 CPU가 커널에 다시 주어지고 커널이 약 후에 스레드가 깨어나도록하는 wake-up 호출을 설정해야하기 때문에 마지막 것이 가장 느릴 것으로 예상합니다. 수면을 요청한 시간의 양, 그런 다음 스레드를 스케줄링 프로세스에서 제거해야하며 스레드가 깨어 난 후에는 스레드를 스케줄링 프로세스에 다시 추가해야합니다. 이 모든 가파른 곳은 어느 정도 시간이 걸립니다. 따라서 실제 절전 호출은 다른 스레드로 전환하는 데 걸리는 시간보다 길 수 있습니다.

확실히 알고 싶다면 벤치마킹해야한다고 생각합니다. 문제는 일반적으로 스레드를 절전 모드로 전환하거나 뮤텍스를 사용하여 동기화해야한다는 것입니다. 잠자기 또는 잠금 / 잠금 해제 뮤텍스 자체에는 오버 헤드가 있습니다. 즉, 벤치 마크에 이러한 오버 헤드도 포함됩니다. 강력한 프로파일 러가 없으면 나중에 실제 스위치에 사용 된 CPU 시간과 절전 / 뮤텍스 호출에 얼마나 많이 사용되었는지 나중에 말하기가 어렵습니다. 반면에 실제 시나리오에서는 스레드가 잠자기 상태이거나 잠금을 통해 동기화됩니다. 순수하게 컨텍스트 전환 시간을 측정하는 벤치 마크는 실제 시나리오를 모델링하지 않으므로 종합적인 벤치 마크입니다. 벤치 마크는 실제 시나리오를 기반으로하면 훨씬 더 "현실적"입니다. 실제 3D 응용 프로그램에서이 결과를 얻을 수없는 경우 내 GPU가 이론적으로 초당 20 억 개의 폴리곤을 처리 할 수 ​​있음을 알려주는 GPU 벤치 마크는 어떤 용도로 사용됩니까? 실제 3D 응용 프로그램에서 GPU가 1 초에 처리 할 수있는 다각형 수를 아는 것이 훨씬 더 흥미롭지 않을까요?

불행히도 나는 Windows 프로그래밍에 대해 아무것도 모릅니다. Java 또는 C #으로 Windows 용 애플리케이션을 작성할 수 있지만 Windows의 C / C ++는 나를 울게 만듭니다. POSIX에 대한 일부 소스 코드 만 제공 할 수 있습니다.

#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <pthread.h>
#include <sys/time.h>
#include <unistd.h>

uint32_t COUNTER;
pthread_mutex_t LOCK;
pthread_mutex_t START;
pthread_cond_t CONDITION;

void * threads (
    void * unused
) {
    // Wait till we may fire away
    pthread_mutex_lock(&START);
    pthread_mutex_unlock(&START);

    pthread_mutex_lock(&LOCK);
    // If I'm not the first thread, the other thread is already waiting on
    // the condition, thus Ihave to wake it up first, otherwise we'll deadlock
    if (COUNTER > 0) {
        pthread_cond_signal(&CONDITION);
    }
    for (;;) {
        COUNTER++;
        pthread_cond_wait(&CONDITION, &LOCK);
        // Always wake up the other thread before processing. The other
        // thread will not be able to do anything as long as I don't go
        // back to sleep first.
        pthread_cond_signal(&CONDITION);
    }
    pthread_mutex_unlock(&LOCK); //To unlock
}

int64_t timeInMS ()
{
    struct timeval t;

    gettimeofday(&t, NULL);
    return (
        (int64_t)t.tv_sec * 1000 +
        (int64_t)t.tv_usec / 1000
    );
}


int main (
    int argc,
    char ** argv
) {
    int64_t start;
    pthread_t t1;
    pthread_t t2;
    int64_t myTime;

    pthread_mutex_init(&LOCK, NULL);
    pthread_mutex_init(&START, NULL);   
    pthread_cond_init(&CONDITION, NULL);

    pthread_mutex_lock(&START);
    COUNTER = 0;
    pthread_create(&t1, NULL, threads, NULL);
    pthread_create(&t2, NULL, threads, NULL);
    pthread_detach(t1);
    pthread_detach(t2);
    // Get start time and fire away
    myTime = timeInMS();
    pthread_mutex_unlock(&START);
    // Wait for about a second
    sleep(1);
    // Stop both threads
    pthread_mutex_lock(&LOCK);
    // Find out how much time has really passed. sleep won't guarantee me that
    // I sleep exactly one second, I might sleep longer since even after being
    // woken up, it can take some time before I gain back CPU time. Further
    // some more time might have passed before I obtained the lock!
    myTime = timeInMS() - myTime;
    // Correct the number of thread switches accordingly
    COUNTER = (uint32_t)(((uint64_t)COUNTER * 1000) / myTime);
    printf("Number of thread switches in about one second was %u\n", COUNTER);
    return 0;
}

산출

Number of thread switches in about one second was 108406

잠금 및 조건부 대기가 있음에도 불구하고 100'000 이상은 나쁘지 않습니다. 나는이 모든 것 없이는 적어도 두 배의 스레드 스위치가 초당 가능하다고 생각합니다.


당신은 그것을 추정 할 수 없습니다. 당신은 그것을 측정해야합니다. 그리고 그것은 장치의 프로세서에 따라 달라질 것입니다.

컨텍스트 전환을 측정하는 매우 간단한 두 가지 방법이 있습니다. 하나는 코드를 포함하고 다른 하나는 포함하지 않습니다.

첫째, 코드 방식 (의사 코드) :

DWORD tick;

main()
{
  HANDLE hThread = CreateThread(..., ThreadProc, CREATE_SUSPENDED, ...);
  tick = QueryPerformanceCounter();
  CeSetThreadPriority(hThread, 10); // real high
  ResumeThread(hThread);
  Sleep(10);
}

ThreadProc()
{
  tick = QueryPerformanceCounter() - tick;
  RETAILMSG(TRUE, (_T("ET: %i\r\n"), tick));
}

Obviously doing it in a loop and averaging will be better. Keep in mind that this doesn't just measure the context switch. You're also measuring the call to ResumeThread and there's no guarantee the scheduler is going to immediately switch to your other thread (though the priority of 10 should help increase the odds that it will).

You can get a more accurate measurement with CeLog by hooking into scheduler events, but it's far from simple to do and not very well documented. If you really want to go that route, Sue Loh has several blogs on it that a search engine can find.

The non-code route would be to use Remote Kernel Tracker. Install eVC 4.0 or the eval version of Platform Builder to get it. It will give a graphical display of everything the kernel is doing and you can directly measure a thread context switch with the provided cursor capabilities. Again, I'm certain Sue has a blog entry on using Kernel Tracker as well.

All that said, you're going to find that CE intra-process thread context switches are really, really fast. It's the process switches that are expensive, as it requires swapping the active process in RAM and then doing the migration.


My 50 lines of C++ show for Linux (QuadCore Q6600) the context switch time ~ 0.9us (0.75us for 2 threads, 0.95 for 50 threads). In this benchmark threads call yield immediately when they get a quantum of time.


Context Switch is expensive, as a rule of thumb it costs 30µs of CPU overhead http://blog.tsunanet.net/2010/11/how-long-does-it-take-to-make-context.html


I've only ever tried to estimate this once and that was on a 486! The upshot was that the processor context switch was taking about 70 instructions to complete (note this was happening for many OS api calls as well as thread switching). We calculated that it was taking approx 30us per thread switch (including OS overhead) on a DX3. The few thousand context switches we were doing per second was absorbing between 5-10% of the processor time.

How that would translate to a multi-core, multi-ghz modern processor I don't know but I would guess that unless you were completely going over the top with thread switching its a negligible overhead.

Note that thread creation/deletion is a more expensive CPU/OS hogger than activating/deactivating threads. A good policy for heavily threaded apps is to use thread pools and activate/deactivate as required.


The problem with context switches is that they have a fixed time. GPU's implemented 1 cycle context switch between threads. The following for example can not be threaded on CPU's:

double * a; 
...
for (i = 0; i < 1000; i ++)
{
    a[i] = a[i] + a[i]
}

because its time of execution is much less than context switch cost. On Core i7 this code takes around 1 micro second (depends on the compiler). So context switch time does matter because it defines how small jobs can be threaded. I guess this also provides a method for effective measurement of context switch. Check how long does the array (in the upper example) has to be so that two threads from thread pool will start showing some real advantage in compare to a single threaded one. This may easily become 100 000 elements and therefore the effective context switch time would be somewhere in the range of 20us within the same app.

All the encapsulations used by the thread pool have to be counted to the thread switch time because that is what it all comes down to (at the end).

Atmapuri


I don't know but do you have the usual performance counters in windows mobile? You could look at things like context switches/sec. I don't know if there is one that specifically measures context switch time though.


Context Switch is very expensive. Not because of the CPU operation itself, but because of cache invalidation. If you have an intensive task running, it will fill the CPU cache, both for instructions and data, also the memory prefetch, TLB and RAM will optimize the work toward some areas of ram.

When you change context all these cache mechanisms are reset and the new thread start from "blank" state.

The accepted answer is wrong unless your thread are just incrementing a counter. Of course there is no cache flush involved in this case. There is no point in benchmarking context switching without filling cache like real applications.

ReferenceURL : https://stackoverflow.com/questions/304752/how-to-estimate-the-thread-context-switching-overhead

반응형