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

Ign*_*kas 57 c c++ multithreading windows-mobile

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

因此我的问题是双重的:

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

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

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

Mec*_*cki 26

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

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

其他因素包括如何进行切换.可以在何时进行切换

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

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

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

理论上,这3种情况可能具有不同的线程切换时间.例如,我希望最后一个是最慢的,因为对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;
}
Run Code Online (Sandbox Code Playgroud)

产量

Number of thread switches in about one second was 108406
Run Code Online (Sandbox Code Playgroud)

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

  • "遗憾的是我对Windows编程一无所知......我只能为你提供POSIX的一些源代码." 你不明白吗? (16认同)
  • 不,我完全理解,但你的回答并没有帮助那个问原问题的人,而且重点是帮助那些提问的人. (5认同)
  • 如果你认为它是伪代码,它会这样做. (5认同)

cta*_*cke 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));
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

总而言之,您将发现CE进程内线程上下文切换真的非常非常快.这是过程开关很昂贵,因为它需要在RAM中交换活动进程然后进行迁移.


Ore*_*ost 13

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

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

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

  • 谢谢,这个肯定我的需要是切换时间最短的. (2认同)
  • 使用不填充缓存的进程对上下文切换进行基准测试是没有意义的。 (2认同)

bob*_*bah 7

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

  • .9*NANOSECONDS*?你确定吗?... <rummages ...>你的代码似乎是计算miilliseconds/switch*1000->微秒. (3认同)

Sor*_*ush 6

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


bok*_*kan 6

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

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

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


Tim*_*ing 5

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

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

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