如何估计线程上下文切换开销?

时间:2008-11-20 09:21:23

标签: c++ c multithreading windows-mobile

我正在尝试使用实时截止日期来提高线程应用程序的性能。它在Windows Mobile上运行,用C / C ++编写。我怀疑高频率的线程切换可能会导致切实的开销,但既不能证明它也不能反驳它。众所周知,缺乏证据并不能证明相反:)。

因此我的问题有两个:

  • 如果存在,我在哪里可以找到切换线程上下文的成本的实际测量值?

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

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

9 个答案:

答案 0 :(得分:26)

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

  • CPU,因为必要的操作在不同的CPU类型上可能更容易或更难
  • 系统内核,因为不同的内核必须在每个交换机上执行不同的操作

其他因素包括如何进行切换。

时可以进行切换
  1. 线程已经使用了它所有的时间量。当一个线程被启动时,它可能会运行一段给定的时间,然后它必须将控制权返回到将决定下一个是谁的内核。

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

  3. 线程不再需要CPU时间,因为它在某些操作上阻塞或者只是调用sleep()(或类似的)来停止运行。

  4. 这三种情况理论上可能有不同的线程切换时间。例如。我希望最后一个是最慢的,因为调用sleep()意味着CPU被返回给内核,内核需要设置一个唤醒调用,以确保线程在关于之后被唤醒它请求休眠的时间量,然后它必须使线程退出调度过程,并且一旦线程被唤醒,它必须再次将线程添加到调度过程。所有这些陡坡都需要一些时间。因此,实际的睡眠呼叫可能比切换到另一个线程所需的时间更长。

    我认为如果你想确切知道,你必须进行基准测试。问题是您通常必须将线程置于睡眠状态,或者必须使用互斥锁同步它们。睡眠或锁定/解锁互斥锁本身就是一种开销。这意味着您的基准测试也将包括这些开销。如果没有强大的分析器,以后很难说实际交换机使用了多少CPU时间以及睡眠/互斥呼叫的使用时间。另一方面,在现实生活中,您的线程也会通过锁定进入睡眠或同步。纯粹测量上下文切换时间的基准是综合基准测试,因为它不会模拟任何现实生活场景。如果它们基于真实场景,那么基准就更加“现实”。有什么用的GPU基准测试告诉我,如果在真实的3D应用程序中永远无法实现这一结果,我的GPU理论上每秒可以处理20亿个多边形?知道真实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\n", COUNTER);
        return 0;
    }
    

    输出

    Number of thread switches in about one second was 108406
    

    超过100'000并不太糟糕,即使我们有锁定和有条件的等待。我想没有所有这些东西,至少有两倍的螺纹开关可能是一秒钟。

答案 1 :(得分:14)

你无法估计它。你需要测量它。它会根据设备中的处理器而有所不同。

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

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

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));
}

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

通过挂钩调度程序事件,您可以使用CeLog获得更准确的测量,但这远非简单易行,而且记录不完善。如果你真的想走那条路,Sue Loh上面有几个博客,搜索引擎可以找到它。

非代码路由是使用远程内核跟踪器。安装eVC 4.0或平台生成器的eval版本以获取它。它将提供内核正在执行的所有操作的图形显示,您可以使用提供的游标功能直接测量线程上下文切换。同样,我确信Sue也有一篇关于使用Kernel Tracker的博客文章。

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

答案 2 :(得分:13)

虽然您说您不想编写测试应用程序,但我在ARM9 Linux平台上进行了之前的测试,以找出开销是多少。只有两个线程会提升:: thread :: yield()(或者,你知道)并增加一些变量,大约一分钟左右(没有其他正在运行的进程,至少没有做任何事情),打印的应用程序它每秒可以执行多少个上下文切换。当然这不是很精确,但关键在于两个线程都相互产生了CPU,并且速度非常快,以至于考虑开销只是没有意义。 所以,只需继续编写一个简单的测试,而不是过多考虑可能不存在的问题。

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

哦,我记得在Windows CE 4.X上运行的应用程序,我们还有四个线程,有时会进行密集切换,并且从不遇到性能问题。我们还尝试在没有线程的情况下实现核心线程事项,并且没有看到性能改进(GUI响应速度慢得多,但其他一切都是相同的)。也许您可以尝试相同的方法,通过减少上下文切换的数量或完全删除线程(仅用于测试)。

答案 3 :(得分:7)

My 50 lines of C++显示Linux(QuadCore Q6600)上下文切换时间~0.9us(2个线程为0.75us,50个线程为0.95)。在这个基准测试线程中,当它们获得一段时间时,会立即调用yield。

答案 4 :(得分:6)

Context Switch是昂贵的,根据经验,它需要30μs的CPU开销http://blog.tsunanet.net/2010/11/how-long-does-it-take-to-make-context.html

答案 5 :(得分:5)

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

这将如何转化为我不知道的多核,多ghz现代处理器,但我猜想除非你通过线程切换完全超越顶部,其开销可以忽略不计。

请注意,线程创建/删除是比激活/停用线程更昂贵的CPU / OS hogger。对于线程严重的应用程序来说,一个好的策略是使用线程池并根据需要激活/停用。

答案 6 :(得分:3)

上下文切换的问题在于它们具有固定的时间。 GPU在线程之间实现了1个周期上下文切换。例如,以下不能进行线程化 在CPU上:

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

因为它的执行时间远小于上下文切换成本。在Core i7上这段代码 需要大约1微秒(取决于编译器)。因此,上下文切换时间很重要,因为它定义了小作业的线程化程度。我想这也提供了一种有效测量上下文切换的方法。检查数组(在上面的例子中)必须有多长时间,以便线程池中的两个线程开始显示与单个线程池相比的一些真正优势。这可能很容易变成10万个元素,因此有效的上下文切换时间将在同一应用程序中的20us范围内。

线程池使用的所有封装都必须计入线程切换时间,因为这就是最终归结为(最后)。

Atmapuri

答案 7 :(得分:1)

我不知道但是你在Windows Mobile中有通常的性能计数器吗?您可以查看上下文切换/秒等内容。我不知道是否有专门测量上下文切换时间的那个。

答案 8 :(得分:1)

上下文切换非常昂贵。不是因为CPU操作本身,而是因为缓存无效。如果您正在执行繁重的任务,它将填充CPU缓存(用于存储指令和数据),并且内存预取,TLB和RAM会优化RAM的某些区域的工作。

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

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