English 中文(简体)
如何估计线程上下文切换的开销?
原标题:
  • 时间:2008-11-20 09:21:23
  •  标签:

我正在尝试提高具有实时截止日期的分线应用程序的性能。它正在运行在Windows Mobile上,并且是用C / C ++编写的。我怀疑线程切换的高频率可能会造成实际的开销,但既不能证明也不能证明它。众所周知,没有证据不是相反的证明:)。

因此,我的问题有两个方面:

  • 如果有的话,我在哪里可以找到任何关于线程上下文切换成本的实际测量?

  • 不花时间编写测试应用程序,有哪些方法可以估计现有应用程序中的线程切换开销?

  • 有人知道如何找出给定线程的上下文切换(开/关)次数吗?

最佳回答

While you said you don t want to write a test application, I did this for a previous test on an ARM9 Linux platform to find out what the overhead is. It was just two threads that would boost::thread::yield() (or, you know) and increment some variable, and after a minute or so (without other running processes, at least none that do something), the app printed how many context switches it could do per second. Of course this is not really exact, but the point is that both threads yielded the CPU to each other, and it was so fast that it just didn t make sense any more to think about the overhead. So, simply go ahead and just write a simple test instead of thinking too much about a problem that may be non-existent.

除此之外,您可以尝试使用性能计数器,就像1800建议的那样。

哦,我还记得在 Windows CE 4.X 上运行的一个应用程序,我们也有四个线程经常进行密集切换,但从未遇到过性能问题。我们还尝试过完全不使用线程来实现核心线程处理,但没有看到性能提升(GUI 只是响应得更慢,但其他一切都是相同的)。也许你可以尝试一下,要么减少上下文切换的次数,要么完全删除线程(仅供测试)。

问题回答

我怀疑你无法在任何现有平台的网上找到这种开销。因为存在太多不同的平台。 这种开销取决于两个因素:

  • The CPU, as the necessary operations may be easier or harder on different CPU types
  • The system kernel, as different kernels will have to perform different operations on each switch

其他因素包括转换的方式。转换可以发生在以下情况下:

  1. 线程已经使用了它的时间片。当一个线程被启动时,它可能运行一定的时间,然后必须将控制权返回给内核,内核将决定下一步是谁。

  2. 线程被抢占了。当另一个线程需要 CPU 时间并且具有更高的优先级时,就会发生这种情况。例如,处理鼠标/键盘输入的线程可能是这样的线程。无论当前哪个线程拥有 CPU,当用户输入或单击某些东西时,他不想等到当前线程的时间量完全用完,他希望立即看到系统反应。因此,一些系统会立即使当前线程停止,并将控制返回给具有更高优先级的其他线程。

  3. 该线程不再需要CPU时间,因为它正在等待某些操作或仅调用了sleep()(或类似的函数)来停止运行。

这三种情况在理论上可能有不同的线程切换时间。例如,我会期望最后一个是最慢的,因为对 sleep() 的调用意味着 CPU 被返回给内核,内核需要设置一个唤醒调用,确保线程在大约请求睡眠的时间后被唤醒,然后必须将线程从调度过程中取出,一旦线程被唤醒,就必须再将线程添加到调度过程中。所有这些步骤都需要花费一定的时间。因此,实际的 sleep 调用可能比切换到另一个线程所需的时间更长。

我认为如果您想确定的话,必须进行基准测试。问题在于,您通常必须将线程置于睡眠状态,或者使用互斥体同步它们。睡眠或锁定/解锁互斥体本身就具有开销。这意味着您的基准测试也会包括这些开销。如果没有强大的分析器,很难随后说出实际切换使用了多少 CPU 时间以及睡眠/互斥体调用使用了多少 CPU 时间。另一方面,在真实的情况下,您的线程也将睡眠或通过锁同步。一个纯粹衡量上下文切换时间的基准测试是一个人工基准测试,因为它不模拟任何真实情况。如果基准测试基于真实场景,那么它们更加“现实”。如果说我的 GPU 基准测试告诉我我的 GPU 理论上可以处理每秒 20 亿个多边形,但在真实的 3D 应用程序中这个结果永远无法实现,那用处又有多少呢?如果我们知道实际的 3D 应用程序在每秒内可以让 GPU 处理多少个多边形,那岂不是更有趣味?

不幸的是,我对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
", COUNTER);
    return 0;
}

产量

Number of thread switches in about one second was 108406

超过100000并不算太糟糕,即使我们有锁定和条件等待的情况下。 我猜,如果没有所有这些东西,每秒至少可以有两倍的线程切换。

你无法估算它。你需要测量它。它会因设备中的处理器而异。

有两种相当简单的方法来测量上下文切换。一种涉及代码,另一种则不涉及。

首先,使用代码方式(伪代码):

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
"), tick));
}

循环执行并取平均值显然会更好。请记住,这不仅仅是测量上下文切换。您还在测量对ResumeThread的调用,而无法保证调度程序会立即切换到其他线程(尽管优先级为10应有助于增加这种可能性)。

您可以通过连接调度程序事件来获得更精确的CeLog测量结果,但这并不简单并且文档也非常少。如果您真的想走这条路,Sue Loh有几篇博客可以在搜索引擎中找到。

非代码路线是使用远程内核跟踪器。安装eVC 4.0或Platform Builder的Eval版本可获得它。它将显示内核正在执行的所有内容,并且您可以利用提供的游标功能直接测量线程上下文切换。同样,我确信Sue也有关于使用内核跟踪器的博客条目。

所有这些说,你会发现CE进程内线程上下文切换非常快。 进程切换很昂贵,因为它需要在RAM中交换活动进程,然后进行迁移。

上下文切换非常昂贵。不是因为CPU操作本身,而是因为高速缓存失效。如果您正在运行一个密集的任务,它会填充CPU高速缓存,包括指令和数据,还有内存预取、TLB和RAM会将工作优化为某些内存区域。

当您更改上下文时,所有这些缓存机制都将被重置,并且新线程将从“空白”状态开始。

除非您的线程只是递增计数器,否则接受的答案是错误的。当然,在这种情况下没有缓存刷新。无意义的基准测试上下文切换,而不像真实应用程序一样填充缓存。

我的50行C ++显示Linux(QuadCore Q6600)的上下文切换时间大约为0.9微秒(2个线程为0.75微秒,50个线程为0.95微秒)。在此基准测试中,线程在获得时间量时立即调用yield。

上下文切换成本很高,通常需要30微秒的CPU开销。 http://blog.tsunanet.net/2010/11/how-long-does-it-take-to-make-context.html

我只试过一次估算,而那是在486上!结果是处理器上下文切换需要完成大约70个指令(请注意,这也发生在许多操作系统API调用以及线程切换中)。我们计算出,在DX3上每个线程切换大约需要30微秒(包括操作系统开销)。每秒进行的几千个上下文切换吸收了处理器时间的5-10%。

我不知道这将如何转换成多核、多GHz的现代处理器,但我猜除非您在线程切换方面完全过度,否则开销微不足道。

请注意,相比于激活/停用线程,线程的创建/删除是更加耗费CPU/操作系统资源的。对于高度多线程的应用程序,一个好的策略是使用线程池,在需要时激活/停用线程。

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.

线程池使用的所有封装都必须计算到线程切换时间中,因为这是最终的结果。

Atmapuri (阿特玛普里)

我不知道,但是 Windows 手机中是否有通常的性能计数器?您可以查看诸如上下文切换/秒之类的内容。不知道是否有一个特定地测量上下文切换时间的计数器。





相关问题
热门标签