1. 程式人生 > >定時器:4叉堆與2叉堆的效率比較

定時器:4叉堆與2叉堆的效率比較

在學習muduo,看到定時器的實現方式,提到有一下幾種實現方式。

1.優先順序佇列,即二叉堆。

2.4叉堆,libev中使用的模型即是如此,比二叉堆更高效,因為具有更好的區域性性。

3.二叉搜尋樹。

4.使用set,以pair<timerstamp,Timer*>為key,為了區分兩個timer時間相同的情況。

 

本文探討2叉堆與4叉堆的效率問題。自實現了一個4-heap,與stl自帶priorityqueue進行對比,統計插入5萬個、10萬個、20萬個定時器的時間和全部彈出的時間,在linux環境下進行測試。

原始碼檔案結構如下:

timer.h    timer.cpp    自實現簡易定時器

Four_heap.h    Four_heap.cpp    自實現4-heap

main.cpp    主程式

timer.h

#pragma once
#include <iostream>
#include <assert.h> 
class TimerNode
{
public:
	TimerNode( int timeout);
	~TimerNode();
	size_t getExpTime() const { return expiredTime_; }
private:
	size_t expiredTime_;
};

timer.cpp

#include "timer.h"
TimerNode::TimerNode(int timeout)
{
	expiredTime_ = timeout;
}
TimerNode::~TimerNode()
{
}

Four_heap.h

#include "timer.h"
#include <vector>
#include <algorithm>
#include <deque>
#include <cassert>
using namespace std;
class Four_heap
{
public:
	Four_heap();
	~Four_heap();
	bool empty();
	TimerNode*top();
	void pop();
	void push(TimerNode *&Node);
private:
	void fixdown();
	void fixup();
	int getsonIdx(int &cur,int &time);//獲取子節點中最小值對應的下標。
private:
	vector<TimerNode*> vec;
};

Four_heap.cpp

#include "Four_heap.h"
//#define getsondix  減少函式呼叫開銷
Four_heap::Four_heap()
{
	vec.reserve(100);
}
Four_heap::~Four_heap()
{
	for (auto i : vec)
		delete i;
}

bool Four_heap::empty()
{
	return vec.empty();
}

TimerNode * Four_heap::top()
{
	assert(!vec.empty());
	return vec[0];
}

void Four_heap::pop()
{
	swap(vec[0], vec.back());
	TimerNode*p = vec.back();
	vec.pop_back();
	delete p;
	if (vec.size() > 1)
		fixdown();
}

void Four_heap::push(TimerNode* &Node)
{
	vec.push_back(Node);
	fixup();
}
#ifdef 	getsondix
void Four_heap::fixdown()//用於pop操作,交換首位元素,彈出尾部元素,然後fixdown()
{
	int curIdx = 0;
	TimerNode*key = vec[curIdx];
	int key_time = key->getExpTime();
	int sIdx = getsonIdx(curIdx, key_time);
	while (sIdx != -1)
	{
		if (vec[sIdx]->getExpTime() < key_time)
		{
			vec[curIdx] = vec[sIdx];
			curIdx = sIdx;
			sIdx = getsonIdx(curIdx, key_time);
		}
		else
			break;
	}
	vec[curIdx] = key;
}
#else
void Four_heap::fixdown()//用於pop操作,交換首位元素,彈出尾部元素,然後fixdown()
{
	int curIdx = 0;
	TimerNode*key = vec[curIdx];
	int key_time = key->getExpTime();
	int sIdx = -1;
	while ((curIdx<<2) + 1 < vec.size())
	{
		int mintime = key_time;
		for (int i = (curIdx << 2) + 1; i <= (curIdx << 2) + 4 && i < vec.size(); i++)
		{
			int curtime = vec[i]->getExpTime();
			mintime = min(curtime, mintime);
			if (curtime == mintime)
				sIdx = i;
		}
		if (sIdx == -1)
			break;
		vec[curIdx] = vec[sIdx];
		curIdx = sIdx;
		sIdx = -1;
	}
	vec[curIdx] = key;
}
#endif

void Four_heap::fixup()
{
	int curIdx = vec.size() - 1;
	if (curIdx == 0)
		return;
	int fIdx = (curIdx - 1) / 4;
	TimerNode*key = vec[curIdx];
	int key_time = key->getExpTime();
	while (curIdx)
	{
		if (vec[fIdx]->getExpTime() > key_time)
		{
			vec[curIdx] = vec[fIdx];
			curIdx = fIdx;
			fIdx = (curIdx - 1) / 4;
		}
		else
			break;
	}
	vec[curIdx] = key;
}

int Four_heap::getsonIdx(int &cur,int &time)
{
	int i = 4 * cur + 1, mintime = time, idx = -1;
	while (i <= 4*cur + 4 && i < vec.size())
	{
		int curtime = vec[i]->getExpTime();
		mintime = min(curtime, mintime);
		if (curtime == mintime)
			idx = i;
		i++;
	}
	return idx;
}

main.cpp

#include <iostream>
#include <map>
#include <set>
#include <vector>
#include <queue>
#include <functional>
#include <string>
#include <assert.h>
#include "Four_heap.h"
#include <sys/time.h>
#include <unistd.h>
using namespace std;
const int COUNT = 100000;
struct TimerCmp
{
	bool operator()(TimerNode* &a, TimerNode*  &b) const
	{
		return a->getExpTime() > b->getExpTime();
	}
};
long long gettime()
{
	struct timeval now;
	gettimeofday(&now, NULL);
	return (((now.tv_sec % 10000) * 1000) + (now.tv_usec / 1000));
}
long long heap_inserttime;
void FourHeap_timeCount()
{
	Four_heap fh;
	long long start = gettime();
	for (int i = 0; i < COUNT; i++)
	{
		TimerNode*p = new TimerNode(i);
		fh.push(p);
	}
	// 以毫秒計
	long long end = gettime();
	heap_inserttime = end - start;

 	while (!fh.empty())
	{
		TimerNode*p = fh.top();
		cout << p->getExpTime() << endl;
		fh.pop();
	}
}
long long priority_inserttime;
void Priority_timeCount()
{
	priority_queue<TimerNode*, vector<TimerNode*>, TimerCmp> timerNodeQueue;//儲存定時器的容器-優先順序佇列
	long long start = gettime();
	for (int i = 0; i < COUNT; i++)
	{
		TimerNode*p = new TimerNode(i);
		timerNodeQueue.push(p);
	}
	long long end = gettime();
	priority_inserttime = end - start;
	while (!timerNodeQueue.empty())
	{
		TimerNode*p = timerNodeQueue.top();
		cout << p->getExpTime() << endl;
		delete p;
		timerNodeQueue.pop();
	}

}
long long start1;
long long end1;
long long start2;
long long end2;
void test1()
{
	start1 = gettime();
	Priority_timeCount();
	end1 = gettime();

}
void test2()
{
	start2 = gettime();
	FourHeap_timeCount();
    end2 = gettime();

}
int main() 
{	
//	test1();
	test2();
    cout <<"優先順序佇列插入時間 "<< priority_inserttime << endl;
	cout <<"優先順序佇列插入、彈出時間和"<< end1 - start1 << endl;
	cout << "4-heap插入時間 " << heap_inserttime << endl;
	cout << "4-heap插入、彈出時間和 " << end2 - start2 << endl;
	return 0;
}

對test1()和test2()分別進行測試。

結果如下:

 第一個表示插入時間,第二個表示插入時間與彈出時間的總和。

插入5萬個定時器:

發現差異並不明顯。

插入10萬個定時器:

可以看到4叉堆效率高於2叉堆。

插入20萬個定時器:

 可以看到4叉堆效率高於2叉堆。