Click here to Skip to main content
15,884,388 members
Articles / Programming Languages / C++

Testing simple concurrent containers

Rate me:
Please Sign up or sign in to vote.
5.00/5 (12 votes)
25 Oct 2009CPOL10 min read 50.3K   404   40  
This article illustrates simple approaches and test results when creating containers with concurrent flavor and running on a multi-core PC.
#include "stdafx.h"

#include "perf_counter.h" 
#include "rwlocks.h"
#include "slimrwlock.h"
#include "map_tbb_t.h"
#include "map_container_locker_t.h"
#include "map_spinlocks_t.h"
#include "map_critsectable_t.h"


HANDLE g_start_event;
volatile long g_stop_test; 


/*
Builder threads change structure of the container 
(insert/delete container elements)
*/
const int builder_test_iterations = 4000000;
const int number_of_builders = 1;
/*
Writer threads may read and change elements of the container
*/
const int writer_test_iterations = 4000000;
const int number_of_writers = 16;
/*
Reader threads only read elements of the container
*/
const int reader_test_iterations = 4000000;
const int number_of_readers = 16;

const int number_of_elements = 1000000;

#pragma warning(disable:4324)
class test_container_element
{
public:
	void calculate_checksum()
	{
		for(int i = 0; i < sizeof(m_buffer)/sizeof(m_buffer[0]); i ++)
		{
			m_buffer[i] = rand();
		}
		m_checksum = checksum(m_buffer, sizeof(m_buffer));
	}

	bool validate_checksum()
	{
		return (m_checksum == checksum(m_buffer, sizeof(m_buffer)));
	}
private:
	USHORT checksum( LPVOID data, int len )
	//
	// The function checksum came from http://ava.org.ua/?2&17&get=87&read=87
	//
	{
		ULONG sum = 0;
		USHORT * dp =  (USHORT *) data;
		USHORT sum_s;
		int words = len >> 1;
		while( words -- )  sum += * dp ++;
		if( len & 1 ) sum += *(UCHAR*) dp;
		sum   = (USHORT) sum + (sum >> 16) & 0xffff;
		sum_s = (USHORT) sum + (USHORT)(sum >> 16);
		return sum_s != 0xffff ? ~sum_s : sum_s;
	}

	enum {BUF_SIZE = 64};
	int m_buffer[BUF_SIZE];
	USHORT m_checksum;
};
#pragma warning(default:4324)


#ifdef TEST_CS_TABLE_CONTAINER
	CST_map_t<long, test_container_element,LOCK_TYPE> g_map;
#elif defined TEST_SPINLOCK_CONTAINER
	SRWL_map_t<long, test_container_element,LOCK_TYPE> g_map;
#elif defined TEST_POOLLOCK_CONTAINER
	Map_t<long, test_container_element, LOCK_TYPE, number_of_writers + number_of_readers> g_map;
#elif defined TEST_TBB_CONCURRENT_CONTAINER
	TBB_map_t<long, test_container_element> g_map;
#endif

template <const int test_iteration>
class Test_worker{
public:
	HANDLE start(unsigned long rand_seed)
	{
		m_randSeed = rand_seed;
		::ResumeThread(m_handle);
		return m_handle;
	}
	double test_duration(void)
	{
		return m_perf_counter.get_duration();
	}
	LONGLONG test_iteration_done(void)
	{
		return m_perf_counter.get_counter();
	}
private:
	static DWORD WINAPI worker_proc(LPVOID lpParam)
	{
		Test_worker& worker = *reinterpret_cast<Test_worker*>(lpParam);
		::WaitForSingleObject(g_start_event, INFINITE );
		worker.m_perf_counter.start();
		int i;

		for (i = 0; i < test_iteration; i++)
		{
			if( false == worker.simulate_work() )
			{
				::RaiseException( STATUS_NONCONTINUABLE_EXCEPTION, 0, 0, 0);
 			}
			else
			{
			}

			if (TRUE == g_stop_test)
			{
				break;
			}
			else
			{
			}

		}
		
		worker.m_perf_counter.end();
		::InterlockedExchange(&g_stop_test, TRUE);
		worker.m_perf_counter.set_iteration_done(i);
		return 0;
	}

	virtual bool simulate_work(void)=0;

	HANDLE m_handle;
	Performance_counter_meter m_perf_counter;
	unsigned long m_randSeed;

	Test_worker & operator=( const Test_worker & ) {}
protected:

	// The function rand() came from the MSDN article 
	// "Multiprocessor Optimizations: Fine-Tuning Concurrent Access to Large Data Collections"
	// by Ian Emmons, http://msdn.microsoft.com/en-ca/magazine/cc301503.aspx
	unsigned long rand(unsigned long hi)
	{
		const int k_randMax = 1771875ul,
		k_randCoeff = 2416ul,
		k_randOffset = 374441ul;

		assert(hi < k_randMax);
		m_randSeed = (m_randSeed * k_randCoeff + k_randOffset) % k_randMax;
		return (hi * m_randSeed) / (k_randMax - 1);
	}

	Test_worker()
	{
		m_randSeed = 0;
		m_handle = ::CreateThread(NULL,0, worker_proc, (LPVOID)this, CREATE_SUSPENDED, NULL);
	}
};


class Container_builder: public Test_worker <builder_test_iterations>
{
public:
	/*
	One of the builders setup the container g_map
	*/
	static void setup_test_container(void)
	{
		for (long key = 0; key < number_of_elements; key++)
		{
			test_container_element value;
			value.calculate_checksum();
			g_map.insert(std::pair<long, test_container_element>(key, value));
		}
	}
	Container_builder()
	{
		m_deleted_element = NOT_DELETED;
	}
private:

	virtual bool simulate_work(void)
	{
		if ( m_deleted_element == NOT_DELETED ) 
		{
			m_deleted_element = rand(number_of_elements);
			g_map.erase(m_deleted_element);
		}
		else
		{
			test_container_element value;
			value.calculate_checksum();
			g_map.insert(std::pair<long, test_container_element>(m_deleted_element, value));
			m_deleted_element = NOT_DELETED;
		}
		return true;
	}
	enum {NOT_DELETED = -1};
	long m_deleted_element;
};


class Writer: public Test_worker <writer_test_iterations>
{
	virtual bool simulate_work(void)
	{
		long element_to_update = rand(number_of_elements);
		test_container_element value;
		value.calculate_checksum();
		g_map.update(element_to_update, value);
		return true;
	}
};


class Reader: public Test_worker <reader_test_iterations>
{
	virtual bool simulate_work(void)
	{
		long element_to_read = rand(number_of_elements);
		test_container_element value;
		if(g_map.find(element_to_read, value))
		{
			return value.validate_checksum();
		}
		else
		{
			return true;
		}
	}
};

int _tmain(int, _TCHAR*)
{
	Container_builder::setup_test_container();
	g_start_event = ::CreateEvent(NULL,TRUE,FALSE,NULL);
	HANDLE threads[number_of_builders + number_of_writers + number_of_readers];

	Writer* writer[number_of_writers];
	for (int i = 0; i < number_of_writers; i++)
	{
		writer[i] = new Writer();
		threads[i] = writer[i]->start(::rand());
	}

	Reader* reader[number_of_readers];
	for (int i = 0; i < number_of_readers; i++)
	{
		reader[i] = new Reader();
		threads[i + number_of_writers] = reader[i]->start(::rand());
	}

	Container_builder* builder[number_of_builders];
	for (int i = 0; i < number_of_builders; i++)
	{
		builder[i] = new Container_builder();
		threads[i + number_of_writers + number_of_readers ] = builder[i]->start(::rand());
	}

	::SetEvent(g_start_event);
	::WaitForMultipleObjects(sizeof(threads)/sizeof(threads[0]), threads, TRUE, INFINITE);

	for (int i = 0; i < number_of_readers; i++)
	{
		printf("reader iterations done | per operation(ms),%lld,%f\n",
			reader[i]->test_iteration_done(),
			1000*reader[i]->test_duration()/reader[i]->test_iteration_done());
	}

	for (int i = 0; i < number_of_writers; i++)
	{
		printf("writer iterations done | per operation(ms),%lld,%f\n",
			writer[i]->test_iteration_done(),
			1000*writer[i]->test_duration()/writer[i]->test_iteration_done());
	}

	for (int i = 0; i < number_of_builders; i++)
	{
		printf("builder: iterations done | per operation(ms),%lld,%f\n",
			builder[i]->test_iteration_done(),
			1000*builder[i]->test_duration()/builder[i]->test_iteration_done());
	}

	return 0;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Canada Canada
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions