IOCP性能优化:使用环形缓冲提升IOCP效率(无锁缓冲)

原创 2012年08月18日 16:11:58

最近在重构之前写的网络底层时,从各个方面认真考虑了每一个细节实现。其中,在提交I/O(WSASend/WSARecv)和I/O完成(GetQueuedCompletionStatus)时,难免出现一个缓冲区需要两个线程公用的问题。


假设主线程不断发送该消息,这些消息被堆叠在一个缓冲区里,定时使用WSASend提交发送I/O请求,在GetQueuedCompletionStatus返回后,才能按照已发送的字节数去删掉该缓冲区里相应字节数的数据。不明白?好吧我说的简单一些。

WSASend调用后,你传递的参数只是说明:我希望发送这么多数据。但请求提交后,你的要求未必能够被全部满足,也就是说也许你想发送1024字节的东西,但也许GetQueuedCompletionStatus返回,操作完成后,本次只成功发送了1000个字节,也就是说剩余的24字节的数据,你还需要再调用WSASend,直到都发送成功为止。所以在这种情况下,一定要等待GetQueuedCompletionStatus返回,才知道究竟发送成功了多少,也才能从之前的发送缓冲里删掉数据。否则,如果你在提交WSASend时就把数据删掉了,而GetQueuedCompletionStatus返回后却告诉你只发了1000字节,那就杯具了-----那24字节的数据永远地离开了我们。

注:上面两个自然段锁阐述地情况我没有测试出来过,也没有专门对此测试。但因为文档上并未说明WSASend一定会发完全部数据,所以我一直认为要等操作完成后才知道。但也有不同观点。但即使如此,相信按照我下述方法处理也不会损失效率(至少我的应用是满足了)


而在这种情况下,我们发送消息,也就是向这个缓冲区后面堆放要发送的数据,是主线程中执行的,而GetQueuedCompletionStatus完成后,从缓冲区内弹出数据,却是IOCP的工作线程做的。当然,最简单的办法就是------加个锁呗。但是,在I/O频繁的情况下,可以想象会出现多少线程争用的情况。于是,就有了本文要说的东西:环形缓冲。


环形缓冲的原理并不难理解,只适用于一个线程写,一个线程读的情况。环形缓冲的原理我就不再赘述,可以自行搜索。


废话不多说。下面给出我在这次优化中写的一个环形缓冲类,该环形缓冲完美地在基于IOCP的网络框架中工作了起来,实实在在地解决了线程争用引发地效率低下。

2012.9.1 0:57 重贴代码
修改了一个可能出现的误置Full标志的BUG,之前的代码中,是先增加写指针,再判断是否等于读指针,等于则置Full标志,但若在该判断之前,读线程将数据读空,此时写线程继续工作,进行该判断时,就会发现写指针 = 读指针(但是是由于读空造成的),于是错误地将状态置为Full。


2014.8.4 2:08 重贴代码

重写代码,解决掉xiaolizi提出的可能会引发数据被覆盖的BUG,详细请见回复:(十分感谢您提出这个问题)


XRingBuffer.h 

#pragma once

#include "XBaseDefine.h"

const BYTE XRING_BUFFER_READ_POS_AND_WRITE_POS_SIZE = 2;

class XRingBuffer
{
public:
	XRingBuffer(const DWORD size);
	~XRingBuffer();

	bool pushData(const void* data, const DWORD size);

	bool copyData(void* dest, const DWORD destSize, const DWORD copySize);
	bool popData(void* dest, const DWORD destSize, const DWORD popSize);
	bool popData(const DWORD popSize);
	const DWORD getUsedSize() const;
	const DWORD getFreeSize() const;
private:
	bool copyDataWithAddReadPosOption(void* dest, const DWORD destSize, const DWORD popSize, bool addReadPos);
private:
	char* _buffer;
	const DWORD _size;
	volatile DWORD _write_pos;
	volatile DWORD _read_pos;
};

XRingBuffer.cpp

#include "XRingBuffer.h"
#include "XDebug.h"

XRingBuffer::XRingBuffer(const DWORD size) : 
_size(size + XRING_BUFFER_READ_POS_AND_WRITE_POS_SIZE),
_write_pos(1),
_read_pos(0),
_buffer(NULL)
{
	_buffer = new char[_size];
}

XRingBuffer::~XRingBuffer()
{
	delete [] _buffer;
}

bool XRingBuffer::pushData(const void* data, const DWORD size)
{
	const DWORD freeSize = getFreeSize();
	if(freeSize < size)
	{
		return false;
	}
	const DWORD readPos =_read_pos;
	const DWORD writePos = _write_pos;
	if(writePos > readPos)
	{
		const DWORD lenFromWritePosToBufferEnd = _size - writePos;
		if(size <= lenFromWritePosToBufferEnd)
		{
			memcpy(_buffer + _write_pos, data, size);
			_write_pos += size;
			if(_write_pos == _size)
			{
				_write_pos = 0;
			}
			else if(_write_pos > _size)
			{
				assert_fail("wirtepos cannot bigger than size");
				return false;
			}
			return true;
		}
		else
		{	// 先拷贝前一部分到缓冲区尾部
			memcpy(_buffer + _write_pos, data, lenFromWritePosToBufferEnd);
			const DWORD secondPartLen = size - lenFromWritePosToBufferEnd;
			// 拷贝后一部分到缓冲区前部
			memcpy(_buffer, ((char*)data) + lenFromWritePosToBufferEnd, secondPartLen);
			_write_pos = secondPartLen;
			return true;
		}
	}
	else if(writePos < readPos)
	{
		memcpy(_buffer + writePos, data, size);
		_write_pos += size;
		return true;
	}
	else
	{
		assert_fail("write pos equal read pos, it's an error");
		return false;
	}
}


bool XRingBuffer::copyData(void* dest, const DWORD destSize, const DWORD copySize)
{
	return copyDataWithAddReadPosOption(dest, destSize, copySize, false);
}

bool XRingBuffer::popData(void* dest, const DWORD destSize, const DWORD popSize)
{
	return copyDataWithAddReadPosOption(dest, destSize, popSize, true);
}

bool XRingBuffer::popData(const DWORD popSize)
{
	return copyDataWithAddReadPosOption(NULL, 0, popSize, true);
}

const DWORD XRingBuffer::getUsedSize() const
{
	const DWORD writePos = _write_pos;
	const DWORD readPos = _read_pos;
	if(writePos > readPos)
	{
		return writePos - readPos - 1;
	}
	else if(writePos < readPos)
	{
		return (_size - readPos - 1) + _write_pos;
	}
	else
	{
		assert_fail("write pos equal read pos, it's an error");
		return 0;
	}
}

const DWORD XRingBuffer::getFreeSize() const
{
	const DWORD usedSize = getUsedSize();
	return _size - (usedSize + XRING_BUFFER_READ_POS_AND_WRITE_POS_SIZE);
}

bool XRingBuffer::copyDataWithAddReadPosOption(void* dest, const DWORD destSize, const DWORD copySize, bool addReadPos)
{
	const DWORD usedSize = getUsedSize();
	if(usedSize < copySize)
	{
		assert_fail("data is not enought to copy");
		return false;
	}
	if(dest != NULL)
	{
		if(destSize < copySize)
		{
			assert_fail("dest buffer size is smaller than copy size");
			return false;
		}	
	}
	const DWORD writePos = _write_pos;
	const DWORD readPos = _read_pos;
	if(writePos > readPos)
	{
		if(dest != NULL)
		{
			memcpy(dest, _buffer + readPos + 1, copySize);
		}
		if(addReadPos)
		{
			_read_pos += copySize;
		}
		return true;
	}
	else if(writePos < readPos)
	{
		const DWORD lenFromReadPosToBufferEnd = _size - readPos - 1;
		if(copySize <= lenFromReadPosToBufferEnd)
		{
			if(dest != NULL)
			{
				memcpy(dest, _buffer + readPos + 1, copySize);
			}
			if(addReadPos)
			{
				_read_pos += copySize;
				assert(_read_pos < _size);
			}
			return true;
		}
		else
		{
			const DWORD secondPartLen = copySize - lenFromReadPosToBufferEnd;
			if(dest != NULL)
			{
				memcpy(dest, _buffer + readPos + 1, lenFromReadPosToBufferEnd);
				memcpy(((char*)dest) + lenFromReadPosToBufferEnd, _buffer, secondPartLen);
			}
			if(addReadPos)
			{
				_read_pos = secondPartLen - 1;
			}
			return true;
		}
	}
	else
	{
		assert_fail("write pos equal read pos, it's an error");
		return false;
	}
}


版权声明:本文为博主原创文章,未经博主允许不得转载。

iocp性能分析

iocp性能分析

IOCP+WinSock2新函数打造高性能SOCKET池(转)

在前一篇文章《WinSock2编程之打造完整的SOCKET池 》中,介绍了WinSock2的一些新函数,并重点详细介绍了什么是SOCKET池,有了这个概念,现在就接着展开更深入的讨论。 首先这里...

高性能服务器开发-iocp

在windows下开发高性能服务器,通常采用的是iocp的模式。下面讨论几个常见的问题、解决方式。 1、WSANOBUF问题 假如我们有10000个客户端socket连接,为了接收他们发送过来...

比较C#中几种常见的复制字节数组方法的效率

在日常编程过程中,我们可能经常需要Copy各种数组,一般来说有以下几种常见的方法:Array.Copy,IList.Copy,BinaryReader.ReadBytes,Buffer.BlockCo...

C# 数组Copy的效率问题

在C#中数组Copy是比较常用的,网上有很多帖子,但是写的也不是很完整,今天我对一些比较常用的方法做了一下总结。 1、  数组自带的CopyTo方法 private static void NewMe...

实现读写无加锁的不定长的数据环形缓冲

在很多情况下需要用到多线程编程,而多线程很多时候要用到生产者、消费者模型,比如处理网络数据,至少需要一个数据接收线程进行网络数据的接收,但一般不会也在该线程中进行数据的处理,因为这处理可能会比较耗时,...

Delphi版 环形无锁缓冲

{*******************************************************} { ...

如何利用Nginx的缓冲、缓存优化提升性能

转自:http://www.ahlinux.com/nginx/22271.html 使用缓冲释放后端服务器反向代理的一个问题是代理大量用户时会增加服务器进程的性能冲击影响。在大多数...

在PHP中使用Memcache优化缓冲性能

PHP的Memcache   //连接$mem = new Memcache;$mem->connect("192.168.0.200", 12000); //保存数据$mem->set('key...

DELPHI高性能大容量SOCKET并发(十):IOCP完成端口性能优化

IOCP性能优化主要是集中在每个处理接收数据和发送数据的对象锁,如果能降低锁的调用次数和提高锁的效率,对IOCP的整理效率和吞吐量都非常有帮助。有很多开发人员在优化IOCP的时候,对于如何提高锁的效率...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:IOCP性能优化:使用环形缓冲提升IOCP效率(无锁缓冲)
举报原因:
原因补充:

(最多只允许输入30个字)