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。

Table of Contents