dynamic&锁竞争系统开销分析
dynamic_cast
#include <iostream>
#include <ctime>
using namespace std;
class Base
{
public:
virtual void show(){};
};
class Derived : public Base
{
public:
virtual void show(){};
};
int main(int argc, char* argv[])
{
Base* pTmpBase = new Derived();
Derived* pTmpDerived = NULL;
time_t begin = time(NULL);
// 10亿次
int count = 1000*1000*1000;
for (int i=0; i<count; i++)
{
// 20 秒
//pTmpDerived = dynamic_cast<Derived*>(pTmpBase);
// 2秒
//pTmpDerived = static_cast<Derived*>(pTmpBase);
// 3秒
//pTmpBase->show();
// 空循环,运行时间也是2~3秒内
}
time_t end = time(NULL);
printf("Using seconds %d.\n ", end - begin);
system("pause");
return 0;
}
10亿次操作,空循环、static_cast、虚函数消耗都不大,只有3秒左右,dynamic_cast有20秒左右。说dynamic_cast系统开销大都是相对的,一般情况下,对于少量的操作,那么用dynamic_cast影响也不是很大,对于简单的类操作(双核cpu),如下面,1秒钟可以处理五千万次。从汇编角度来看,dynamic_cast消耗的原因是因为翻译成汇编后的汇编语句条数较多,从而cpu指令会变多。
pTmpDerived = dynamic_cast<Derived*>(pTmpBase); 对应的汇编
00401091 push 0
00401093 push offset Derived `RTTI Type Descriptor' (004150d8)
00401098 push offset Base `RTTI Type Descriptor' (004150c0)
0040109D push 0
0040109F mov ecx,dword ptr [pTmpBase]
004010A2 push ecx
004010A3 call ___RTDynamicCast (00404af3)
004010A8 add esp,14h
004010AB mov dword ptr [pTmpDerived],eax
___RTDynamicCast 这个子程序里面有几百行,而且还有很多的call
pTmpBase->show();对应的汇编,加起来没有10行
00401091 mov ecx,dword ptr [pTmpBase]
00401094 mov edx,dword ptr [ecx]
00401096 mov ecx,dword ptr [pTmpBase]
00401099 call dword ptr [edx]
call dword ptr 汇编为:
004011A0 push ebp
004011A1 mov ebp,esp
004011A3 push ecx
004011A4 mov dword ptr [ebp-4],ecx
004011A7 mov esp,ebp
004011A9 pop ebp
004011AA ret
锁竞争与线程切换
#include <iostream>
#include <ctime>
#include "ace/Task.h"
using namespace std;
class MyTask : public ACE_Task_Base
{
public:
MyTask(){
begin = time(NULL);
nCount = 0;
};
virtual int svc (void)
{
while(1)
{
//ACE_GUARD_RETURN(ACE_Recursive_Thread_Mutex, obj, m_lock, -1);
ACE_READ_GUARD_RETURN(ACE_RW_Thread_Mutex, obj, m_rwLock, -1);
nCount++;
// 1亿次
if (nCount >= 1000*1000*100)
{
time_t end = time(NULL);
printf("Using seconds %d.\n ", end - begin);
Sleep(1000);
}
}
}
private:
ACE_Recursive_Thread_Mutex m_lock;
ACE_RW_Thread_Mutex m_rwLock;
time_t begin;
unsigned long nCount;
};
int main(int argc, char* argv[])
{
MyTask task;
int threadNum = 2;
task.activate(THR_NEW_LWP | THR_JOINABLE |THR_INHERIT_SCHED, threadNum);
system("pause");
return 0;
}
测试数据: 双核机器 1千万数据 单线程, 带锁: 0秒 1亿数据 单线程, 带锁: 7秒 与没有锁比,消耗在锁的获取与释放。 双线程, 带锁: 14秒 3线程, 带锁: 14秒 10线程, 带锁: 12秒 100线程, 带锁: 11秒 1000线程,带锁: 11秒 cpu大概50到60 线程竞争同一个锁,没有竞争到的会排队进入sleep状态。 1亿数据,双线程,读写锁 27秒
对于读写锁,性能上看比互斥锁差2到3倍,所以读写锁的使用条件并不只是“当读远远次数多于写的时候”,当读的操作耗时较长的时候,而且写的次数较少的时候,才更适用。
1亿数据 单线程, 不带锁:0秒 1亿数据 一百线程,不带锁:0秒 100亿数据 单线程 不带锁 2秒 100亿数据 双线程 不带锁 3秒 100亿数据 10线程 不带锁 10秒 cpu负载99% 100亿数据 100线程 不带锁 31秒 100个线程,cpu负载99% 100亿数据 1000线程 不带锁 同上,cpu负载99%,严重影响系统其他功能
1)当锁竞争与线程切换的次数不是很大的时候,这种开销可以忽略,从“双线程,带锁:14秒”来分析,多线程1千万次的锁竞争,从时间上看话费大概1秒钟多一点。
2)线程切换、锁竞争主要是消耗cpu,从时间上看,锁的竞争、获取与释放,才是花费时间的。
3)与没有锁操作比,带锁的开销从时间上看,还是差很多的,1000倍的时间开销。
4)线程切换也是很消耗cpu的,100亿数据的情况下,单线程只需2秒,100线程由于线程切换消耗太多cpu用了30多秒。
迭代器
#include <iostream>
#include <ctime>
#include "ace/Task.h"
#include <list>
using namespace std;
int main(int argc, char* argv[])
{
std::list<int> listTmp;
for (int i=0; i<100; i++)
{
listTmp.push_back(i);
}
time_t begin = time(NULL);
// 1亿次
int count = 1000*1000*10;
for (int j=0; j<count; j++)
{
list<int>::iterator iter = listTmp.begin();
for(; iter != listTmp.end(); ++iter)
{
}
}
time_t end = time(NULL);
printf("Using seconds %d.\n ", end - begin);
system("pause");
return 0;
}
对一个10个元素的list进行1亿次遍历,30秒
对一个100个元素的list进行1千万次遍历,27秒
1亿次的迭代操作大概只花费3秒。虽然在代码编写时候,从时间上运行时间差不太多,但是cpu差距很大,不要以为用list与用map的时间差不多就用list,cpu也是一种资源。
如果1秒钟有1w的数据,那么要分析出处理一条数据用到多少锁竞争、dynamic_cast、线程切换通常这些在处理一条数据都不会很多,对系统的影响基本不会很大。最需要注意的就是程序逻辑上,比如迭代器操作有多少,假如一条数据有1000次迭代器操作,那么就是每秒1千万的迭代器操作,即时处理上cpu绰绰有余,但是对cpu消耗很大。
如果1秒钟有1千万的数据,那么很多问题都会凸显出来,dynamic的系统消耗,锁竞争造成的时间浪费,任何多余的迭代器遍历操作。带宽问题。1千万的数据,假如1条数据100字节,那么需要带宽(100100010000/8)bit/s 有120兆的带宽了,一般网卡上行600兆,再大可能带宽就是问题了。
一个简单对象,new和delete 1千万次,大概耗费1s。