• Preemption Context Switches测量操作系统任务调度线程处理器上执行的次数,以及切换到较高-priority螺纹,数。
  • Synchronization context switches度量的是因为显式调用线程同步API而发生线程切换的次数。如给多线程共享的变量加锁,多线程共同去改动。有些线程要堵塞在lock。直至占用锁的线程释放lock。这个度量反映的是线程间竞争的程度。

以下的实验来自VTune。旨在探究Preemption Context Switches的来源。

实验一:多线程无锁保护

speedup-example-no-mutex.cpp

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h> #define N 4
#define M 30000 int nwait = 0; volatile long long sum;
long loops = 6e3; void set_affinity(int core_id) {
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(core_id, &cpuset);
assert(pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset) == 0);
} void* thread_func(void *arg) {
set_affinity((int)(long)arg);
for (int j = 0; j < M; j++) {
nwait++;
for (long i = 0; i < loops; i++)
sum += i;
for (long i = 0; i < loops; i++)
sum += i*i*i*i*i*i;
}
} int main(int argc, char *argv[]) {
set_affinity(23);
pthread_t th[N];
int ret; for(unsigned i=0; i<N; ++i) {
ret = pthread_create(&th[i], NULL, thread_func, (void*)i);
assert(!ret && "pthread_create() failed!");
} for(unsigned i=0; i<N; ++i)
pthread_join(th[i], NULL); exit(0);
}



VTune现象:

Preemption Context Switches由两部分组成:clone和Unknown stack frame(s)。

  • 后者的Preemption稳定在5:在这个程序中,共同拥有5个线程在执行,VTune显示每一个线程各占1,所以后者的Preemption才稳定在5上。为了验证,我们让N等于8,结果是每一个线程各占1。Unknown stack frame(s)处的Preemption稳定在9。



  • clone处的Preemption不是一个确定的数。有可能是6、7、8等。

通过上图能够发现clone处的Preemption都分布在四个子线程中。以下再来一组:


通过比較上面三幅图。我们发现四个子线程所占的Preemption数并不总是均等。

为了验证,我们让N等于8,结果例如以下:


果然clone处的Preemption并非由子线程均分。只是随着线程数添加,clone处Preemption的添加幅度要大于Unknown stack frame(s)处。

通过上面的现象,我们尝试做出结论:

由于没有锁,所以线程间是独立的,我们单独分析一个线程中Preemption Context Switches的来源就可以(事实上这样的如果是有问题的,由于我们上面提到随着线程数添加,Preemption并没有线性添加,如果各线程间相互独立。理应是线性添加的,只是我们先从简单情况入手)。我们尝试逐步降低子线程运行任务的办法:
  • void* thread_func(void *arg) {
    set_affinity((int)(long)arg);
    for (int j = 0; j < M; j++) {
    for (long i = 0; i < loops; i++)
    sum += i*i*i*i*i*i;
    }
    }

  • void* thread_func(void *arg) {
    set_affinity((int)(long)arg);
    for (int j = 0; j < M; j++) {
    for (long i = 0; i < loops; i++)
    sum += i;
    }
    }

  • void* thread_func(void *arg) {
    set_affinity((int)(long)arg);
    for (int j = 0; j < M; j++) {
    nwait++;
    }
    }

    无clone处的Preemption Context Switches

通过上面我们就断定当子线程计算任务变轻时。clone处的Preemption会变少,这是武断的。由于例如以下:
  • void* thread_func(void *arg) {
    set_affinity((int)(long)arg);
    for (int j = 0; j < M; j++) {
    for (long i = 0; i < loops; i++)
    sum += i;
    for (long i = 0; i < loops; i++)
    sum += i;
    }
    }

这个子线程的计算任务要比上面三个中的第一个要轻。但它的Preemption数却要多,所以我初步猜想是第二层for循环的个数决定了clone处的Preemption数,于是做下面验证:
  • void* thread_func(void *arg) {
    set_affinity((int)(long)arg);
    for (int j = 0; j < M; j++) {
    for (long i = 0; i < loops; i++)
    sum += i;
    for (long i = 0; i < loops; i++)
    sum += i;
    for (long i = 0; i < loops; i++)
    sum += i;
    }
    }

  • void* thread_func(void *arg) {
    set_affinity((int)(long)arg);
    for (int j = 0; j < M; j++) {
    for (long i = 0; i < loops; i++)
    sum += i;
    for (long i = 0; i < loops; i++)
    sum += i;
    for (long i = 0; i < loops; i++)
    sum += i;
    for (long i = 0; i < loops; i++)
    sum += i;
    }
    }

确实是随着for循环的增多,clone处的Preemption在增多,但以此下结论还是不妥,合理的验证还应有下面工作:
  • void* thread_func(void *arg) {
    set_affinity((int)(long)arg);
    for (int j = 0; j < M; j++) {
    for (long i = 0; i < loops; i++) {
    sum += i; sum += i; sum += i; sum += i;
    }
    }
    }

奇怪明明这个子线程的工作量和上面验证中的第二个一样,并且它仅仅有一个for。但clone处的Preemption却很多其它,于是继续做验证:
  • void* thread_func(void *arg) {
    set_affinity((int)(long)arg);
    for (int j = 0; j < M; j++) {
    for (long i = 0; i < loops; i++) {
    sum += i;
    sum += i;
    sum += i;
    sum += i;
    sum += i;
    sum += i;
    sum += i;
    }
    }
    }


终于结论:

也就是说随着第二层for的个数添加,clone处Preemption在添加。假设第二层仅仅有一个for,那么随着这个for中的子句(上面的实验仅仅能说明本例中出现的子句sum+=i有这样的情况)的增多,clone处的Preemption在添加。


分析:

假设说这是结论,那为什么?子线程在执行时,频繁被更高优先级的进程给抢占,可能是时间,执行时间,当子线程执行时间长时,系统中更高优先级的进程抢占它的情况很多其它。果然,我们又一次执行上述那些验证程序。发现——clone处Preemption多的程序,它的执行时间越长。
  • void* thread_func(void *arg) {
    set_affinity((int)(long)arg);
    for (int j = 0; j < M; j++) {
    for (long i = 0; i < loops; i++)
    sum += i;
    for (long i = 0; i < loops; i++)
    sum += i;
    for (long i = 0; i < loops; i++)
    sum += i;
    for (long i = 0; i < loops; i++)
    sum += i;
    }
    }

  • void* thread_func(void *arg) {
    set_affinity((int)(long)arg);
    for (int j = 0; j < M; j++) {
    for (long i = 0; i < loops; i++) {
    sum += i;
    sum += i;
    sum += i;
    sum += i;
    }
    }
    }


至于为什么,也许是由于编译器的优化。这里我们要专注于我们一開始的问题:Preemption Context Switches从何而来。

从运行时间而来。

当然这仅仅是针对多线程间无锁情况,以下给它加上锁。看看是否有哪个因素也会影响到Preemption Context Switches。


实验二:多线程加锁

speedup-example-mutex-only.cpp

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h> #define N 4
#define M 30000 int nwait = 0; volatile long long sum;
long loops = 6e3; pthread_mutex_t mutex; void set_affinity(int core_id) {
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(core_id, &cpuset);
assert(pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset) == 0);
} void* thread_func(void *arg) {
set_affinity((int)(long)arg);
for (int j = 0; j < M; j++) {
phtread_mutex_lock(&mutex);
nwait++;
for (long i = 0; i < loops; i++)
sum += i;
phtread_mutex_unlock(&mutex);
for (long i = 0; i < loops; i++)
sum += i*i*i*i*i*i;
}
} int main(int argc, char *argv[]) {
set_affinity(23);
pthread_t th[N];
int ret; for(unsigned i=0; i<N; ++i) {
ret = pthread_create(&th[i], NULL, thread_func, (void*)i);
assert(!ret && "pthread_create() failed!");
} for(unsigned i=0; i<N; ++i)
pthread_join(th[i], NULL); exit(0);
}



VTune现象:








接下来我们改变线程数。即N等于8:(我们期望Unknown处的Preemption添加类似线性,而clone处的添加幅度大。即与多线程无锁的情况类似)





Unkown stack frame(s)的对Preemption Context Switches的贡献率任然不如clone。且在同等数目线程下,加锁情况下的clone要比不加锁的制造很多其它的Preemption Context Switches。假设用我们上面的“时间理论”来解释——加锁的执行时间明显比不加锁要多,也能解释,只是这并不充分,让我们执行下面验证:

  • void* thread_func(void *arg) {
    set_affinity((int)(long)arg);
    for (int j = 0; j < M; j++) {
    phtread_mutex_lock(&mutex);
    nwait++;
    for (long i = 0; i < loops; i++)
    sum += i;
    phtread_mutex_unlock(&mutex);
    }
    }

  • void* thread_func(void *arg) {
    set_affinity((int)(long)arg);
    for (int j = 0; j < M; j++) {
    phtread_mutex_lock(&mutex);
    nwait++;
    phtread_mutex_unlock(&mutex);
    for (long i = 0; i < loops; i++)
    sum += i*i*i*i*i*i;
    }
    }

  • void* thread_func(void *arg) {
    set_affinity((int)(long)arg);
    for (int j = 0; j < M; j++) {
    phtread_mutex_lock(&mutex);
    nwait++;
    phtread_mutex_unlock(&mutex);
    }
    }

  • void* thread_func(void *arg) {
    set_affinity((int)(long)arg);
    for (int j = 0; j < M; j++) {
    phtread_mutex_lock(&mutex);
    nwait++;
    for (long i = 0; i < loops; i++)
    sum += i;
    for (long i = 0; i < loops; i++)
    sum += i;
    phtread_mutex_unlock(&mutex);
    }
    }

  • void* thread_func(void *arg) {
    set_affinity((int)(long)arg);
    for (int j = 0; j < M; j++) {
    phtread_mutex_lock(&mutex);
    nwait++;
    for (long i = 0; i < loops; i++)
    sum += i;
    for (long i = 0; i < loops; i++)
    sum += i;
    for (long i = 0; i < loops; i++)
    sum += i;
    for (long i = 0; i < loops; i++)
    sum += i;
    phtread_mutex_unlock(&mutex);
    }
    }

  • void* thread_func(void *arg) {
    set_affinity((int)(long)arg);
    for (int j = 0; j < M; j++) {
    phtread_mutex_lock(&mutex);
    nwait++;
    for (long i = 0; i < loops; i++) {
    sum += i;
    sum += i;
    sum += i;
    sum += i;
    }
    phtread_mutex_unlock(&mutex);
    }
    }


我们发现,基本上加锁情况与无锁情况一致。只是我们还需做下面验证:

  • void* thread_func(void *arg) {
    set_affinity((int)(long)arg);
    for (int j = 0; j < M; j++) {
    phtread_mutex_lock(&mutex);
    nwait++;
    phtread_mutex_unlock(&mutex);
    for (long i = 0; i < loops; i++)
    sum += i*i*i*i*i*i;
    for (long i = 0; i < loops; i++)
    sum += i*i*i*i*i*i;
    }
    }

  • void* thread_func(void *arg) {
    set_affinity((int)(long)arg);
    for (int j = 0; j < M; j++) {
    phtread_mutex_lock(&mutex);
    nwait++;
    phtread_mutex_unlock(&mutex);
    for (long i = 0; i < loops; i++)
    sum += i*i*i*i*i*i;
    for (long i = 0; i < loops; i++)
    sum += i*i*i*i*i*i;
    for (long i = 0; i < loops; i++)
    sum += i*i*i*i*i*i;
    for (long i = 0; i < loops; i++)
    sum += i*i*i*i*i*i;
    }
    }

  • void* thread_func(void *arg) {
    set_affinity((int)(long)arg);
    for (int j = 0; j < M; j++) {
    phtread_mutex_lock(&mutex);
    nwait++;
    phtread_mutex_unlock(&mutex);
    for (long i = 0; i < loops; i++) {
    sum += i*i*i*i*i*i;
    sum += i*i*i*i*i*i;
    sum += i*i*i*i*i*i;
    sum += i*i*i*i*i*i;
    }
    }
    }


果然。在一定误差可容忍下,for循环是不区别加锁for和不加锁for,它们取得的效果基本一样——随着第二层for的数目添加,clone处的Preemption在添加;只是这里,单个for中添加子句的效果和添加for数目的效果基本一样。这与无锁是不同的。并且,有一个比較重要的区别:
无锁的情况下,
A
void* thread_func(void *arg) {
set_affinity((int)(long)arg);
for (int j = 0; j < M; j++) {
nwait++;
for (long i = 0; i < loops; i++)
sum += i;
for (long i = 0; i < loops; i++)
sum += i*i*i*i*i*i;
}
}

B
void* thread_func(void *arg) {
set_affinity((int)(long)arg);
for (int j = 0; j < M; j++) {
nwait++;
for (long i = 0; i < loops; i++)
sum += i;
for (long i = 0; i < loops; i++)
sum += i;
}
}

clone处Preemption的数目基本一致,但在加锁的情况下:

C
void* thread_func(void *arg) {
set_affinity((int)(long)arg);
for (int j = 0; j < M; j++) {
phtread_mutex_lock(&mutex);
nwait++;
for (long i = 0; i < loops; i++)
sum += i;
phtread_mutex_unlock(&mutex);
for (long i = 0; i < loops; i++)
sum += i*i*i*i*i*i;
}
}



D
void* thread_func(void *arg) {
set_affinity((int)(long)arg);
for (int j = 0; j < M; j++) {
phtread_mutex_lock(&mutex);
nwait++;
for (long i = 0; i < loops; i++)
sum += i;
for (long i = 0; i < loops; i++)
sum += i;
phtread_mutex_unlock(&mutex);
}
}



clone处Preemption的数目不一样。前者要明显多于后者。可是假设我们将后者改为:

E
void* thread_func(void *arg) {
set_affinity((int)(long)arg);
for (int j = 0; j < M; j++) {
phtread_mutex_lock(&mutex);
nwait++;
for (long i = 0; i < loops; i++)
sum += i;
phtread_mutex_unlock(&mutex);
for (long i = 0; i < loops; i++)
sum += i;
}
}

则VTune分析有:



这就和C效果基本一样了。

而解释C、D、E三者之间的差异,也许也能够用我们的“时间理论”。运行三者:

C



D


E


尽管D的执行时比C和E稍小。但我们不能直接将无锁情况下的时间理论应用到加锁情况。

在说明原因之前。先看还有一个程序:


F
void* thread_func(void *arg) {
set_affinity((int)(long)arg);
for (int j = 0; j < M; j++) {
phtread_mutex_lock(&mutex);
nwait++;
phtread_mutex_unlock(&mutex);
for (long i = 0; i < loops; i++)
sum += i*i*i*i*i*i;
for (long i = 0; i < loops; i++)
sum += i*i*i*i*i*i;
}
}

和D在clone处拥有基本一样的Preemption数。但二者的执行时间却大不一样。


F


所以“执行时间不一样。clone处的Preemption数不一样”。在这里就不适用了。

看来无锁和加锁还是有个重要区别的。我们都知道在无锁情况下,全部子线程并行执行。VTune中有例如以下调度:


我们通过大量的观察发现,对于每一个线程。每相隔1s就会有一次Preemption Context Switches,所以无锁情况下。随着执行时间的添加。clone处的Preemption数会增多。

事实上“时间理论”也适用于加锁情况,那为什么会出现上面C、D、E的情况,以及D和F的情况?我们也从调度图入手:

C

D

F

事实上加锁和无锁的“时间理论”的差别在于:加锁情况中的C和D(基本串行化)。并非每个线程中每隔1s就有一个Preemption。而加锁情况中的F(拥有并行化),每个线程中每隔1s会有一个Preemption。
这样对于C和D。因为C的执行时较D长。当中包括的Preemption比D多;而F尽管执行时比D短,但每一个线程中的Preemption汇总就会和D一样多。


终于我们得出结论:
Preemption Context Switches的来源是——
对于拥有并行化的程序。执行时间越长,Preemption Context Switches越多;对于加锁导致串行化的程序,执行时间越长,Preemption Context Switches越多;对于加锁仍保留并行化的程序。执行时间越长,Preemption Context Switches越多。

版权声明:本文博客原创文章,博客,未经同意,不得转载。

Preemption Context Switches 和 Synchronization Context Switches的更多相关文章

  1. context:component-scan" 的前缀 "context" 未绑定。

    SpElUtilTest.testSpELLiteralExpressiontestSpELLiteralExpression(cn.zr.spring.spel.SpElUtilTest)org.s ...

  2. Android中,Context,什么是Context?

    注:本文翻译自Context, What Context?,原文链接在这里,作者是Dave Smith.ps:译者链接http://blog.csdn.net/race604/article/deta ...

  3. Android开发之Android Context,上下文(Activity Context, Application Context)

    转载:http://blog.csdn.net/lmj623565791/article/details/40481055 1.Context概念Context,相信不管是第一天开发Android,还 ...

  4. System.Drawing.Design.UITypeEditor自定义控件属性GetEditStyle(ITypeDescriptorContext context),EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.C ...

  5. Spring context:component-scan中使用context:include-filter和context:exclude-filter

    Spring context:component-scan中使用context:include-filter和context:exclude-filter XML: <?xml version= ...

  6. Android深入理解Context(一)Context关联类和Application Context创建过程

    前言 Context也就是上下文对象,是Android较为常用的类,但是对于Context,很多人都停留在会用的阶段,这个系列会带大家从源码角度来分析Context,从而更加深入的理解它. 1.Con ...

  7. Tomcat 的context.xml说明、Context标签讲解

    Tomcat的context.xml说明.Context标签讲解 1. 在tomcat 5.5之前 --------------------------- Context体现在/conf/server ...

  8. 元素 "context:component-scan" 的前缀 "context" 未绑定的解决方案

    在动态web项目(Dynamic Web Project)中,使用SpringMVC框架,新建Spring的配置文件springmvc.xml,添加扫描控制器 <context:componen ...

  9. Tomcat的context.xml说明、Context标签讲解

    Tomcat的context.xml说明.Context标签讲解 1. 在tomcat 5.5之前 --------------------------- Context体现在/conf/server ...

随机推荐

  1. PHP 类属性 类静态变量的访问

    php的类属性其实有两种,一种是类常量,一种是类静态变量.两种容易引起混淆. 如同静态类方法和类实例方法一样,静态类属性和实例属性不能重定义(同名),但静态属性可以和类常量同名. <?php c ...

  2. UVA 11427 - Expect the Expected(概率递归预期)

    UVA 11427 - Expect the Expected 题目链接 题意:玩一个游戏.赢的概率p,一个晚上能玩n盘,假设n盘都没赢到总赢的盘数比例大于等于p.以后都不再玩了,假设有到p就结束 思 ...

  3. JDK源码学习系列05----LinkedList

                                             JDK源码学习系列05----LinkedList 1.LinkedList简介 LinkedList是基于双向链表实 ...

  4. Linux date -s(转)

    修改linux的时间可以使用date指令 修改日期: 时间设定成2009年5月10日的命令如下: #date -s 05/10/2009 修改时间: 将系统时间设定成上午10点18分0秒的命令如下.  ...

  5. 数据结构 - trie

    #include <cstring> #include <iostream> #include <map> #include <cstdio> usin ...

  6. 轮播图片 高效图片轮播,两个imageView实现

    该轮播框架的优势: 文件少,代码简洁 不依赖任何其他第三方库,耦合度低 同时支持本地图片及网络图片 可修改分页控件位置,显示或隐藏 自定义分页控件的图片,就是这么个性 自带图片缓存,一次加载,永久使用 ...

  7. WEB安全实战(一)SQL盲注

    前言 好长时间没有写过东西了,不是不想写,仅仅只是是一直静不下心来写点东西.当然,拖了这么长的时间,也总该写点什么的.近期刚刚上手安全方面的东西,作为一个菜鸟,也本着学习的目的,就谈谈近期接触到的安全 ...

  8. 构造函数为什么不能为虚函数 &amp; 基类的析构函数为什么要为虚函数

    一.构造函数为什么不能为虚函数 1. 从存储空间角度,虚函数相应一个指向vtable虚函数表的指针,这大家都知道,但是这个指向vtable的指针事实上是存储在对象的内存空间的.问题出来了,假设构造函数 ...

  9. Visual Studio 2012中使用Zen Coding,写html的神器!

    点工具 -扩展和更新的联机库中 找到以下俩插件 安装后重新启动 新建一个html文件.将下行代码拷贝到页面里. div>(header>div)+(section>ul>li. ...

  10. 栈上分配存储器的方法 alloca 抽样

    声明一个局部变量,必须分配在堆栈上,但有或没有它的方法 当然,,那是 alloca 下面的代码显示了可变长度参数转换,alloca 要使用 int main(int argc, char ** arg ...