Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version
Go to top

Testing simple concurrent containers

, 25 Oct 2009
This article illustrates simple approaches and test results when creating containers with concurrent flavor and running on a multi-core PC.
container_locks.zip
Container_locks
src
Testing_simple_concurrent_containers.pdf
VC9
container_locks.vcproj.val-PC.val.user
Debug
tbb_debug.dll
Debug64
tbb_debug.dll
Release
tbb.dll
Release64
tbb.dll
#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)

Share

About the Author

Valery Grebnev
Software Developer
Canada Canada
No Biography provided

| Advertise | Privacy | Mobile
Web03 | 2.8.140926.1 | Last Updated 25 Oct 2009
Article Copyright 2009 by Valery Grebnev
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid