9
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

マージソートをマルチスレッドで爆速にする件

Last updated at Posted at 2018-06-29

動機

昔からマージソートを自前で書いてマルチスレッド化したら std::sort より早くなる可能性があるんじゃね?って思ってたので、やってみました。
ソースは最後にのせてあります。

マージソートとは

マージソートにもいくつか種類があるのですが、この記事では次のようなアルゴリズムを想定しています。

入力が

84736251

だったとします。2つずつ組みにして小さい順にします。

48 37 26 15

2 つずつの組みをマージしていきます。

3478 1256

同様に全体が一つになるまでマージしていきます。

12345678

できあがり、みたいなやつです。

長所

  • アルゴリズムが非常に簡単
  • 時間計算量が O(NlogN)(std::sortとほぼ一緒)
  • 再帰なしで書けるので安全。

欠点

  • 入力と同じ大きさの作業バッファを必要とする。
    (作業バッファを使わないインプリも可能なんですが、私の腕では速度的に満足するものが書けませんでした)

結果

以下の計測の入力データは、要素数をNとしたら、0 から N-1 までの数をシャッフルしたデータです。

環境1

とりあえず手元の慣れてる環境でやってみました。

  • MacPro Late2013 3.7 GHz Quad-Core Intel Xeon E5 (4コア)
  • clang++ -Os でコンパイル
  • マルチスレッドは Grand Central Dispatcher (GCD) で実現
アルゴリズム 64M要素 128M要素 256M要素
std::sort 4,999ms 10,431ms 21,631ms
MergeSort(シングル) 5,005ms 10,308ms 21,483ms
MergeSort(マルチ) 3,072ms 6,107ms 12,588ms

シングルスレッドのスピード、MergeSort と std::sort でほぼ一緒です。
GCD は 1024 要素がソート済みになったところから使っています。(ソース参照)
倍速とまではいかないですが、結構早くなってます。

環境2

なんか調子よさそうなので、36 vCPU の AWS の EC2 インスタンスで OpenMP やってみました。

  • AWS c4.8xlarge インスタンス(36 vCPU)
  • シングルスレッドは g++ -Os, マルチスレッドは g++ -Os -fopenmp でコンパイル
  • マルチスレッドは OpenMP で実現
アルゴリズム 64M要素 128M要素 256M要素
std::sort 6,100ms 12,577ms 26,118ms
MergeSort(シングル) 9,258ms 19,111ms 39,548ms
MergeSort(マルチ) 1,515ms 2,948ms 6,082ms

MergeSort のマルチ、std::sort の4倍速です。なんかちょっと嬉しいですw

でもって、OpenMP、めっちゃ簡単です。事実上

# pragma omp parallel for

って書いただけです。もうちょっといろいろやれる可能性もありそうです。

ちなみに、ec2 の費用、0.329 時間で $0.66 でした。

ソース

# include	<iostream>
# include	<algorithm>
# include	<vector>
# include	<map>
# include	<chrono>
# include	<random>

using		namespace std;
using		namespace chrono;

////////////////////////////////////////////////////////////////

template<typename T>	vector< T >
Data( T p ) {
	vector< T >	v( p );
	for ( auto i = 0; i < p; i++ ) v[ i ] = p - i - 1;
	return v;
}
template<typename T>	vector< T >
ShuffledData( T p ) {

	mt19937	wMT( (random_device())() );

	auto v = Data( p );
	shuffle( v.begin(), v.end(), wMT );
	return v;
}
template<typename T>	vector< T >
RandomData( T p ) {
	mt19937	wMT( (random_device())() );
	uniform_int_distribution< T >  wUID( 0, p );

	vector< T >	v( p );
	for ( auto i = 0; i < p; i++ ) v[ i ] = wUID( wMT );
	return v;
}

template<typename T>	void
Check( const vector< T >& p ) {
	for ( auto i = 0; i < p.size(); i++ ) if ( p[ i ] != i ) {
		cerr << "CHECK ERROR: " << i << ':' << p[ i ] << endl;
		throw 1;
	}
}

struct
StopWatch {
	string						tag;
	time_point< system_clock >	m;

	StopWatch( string pTag ) {
		tag = pTag;
		m = system_clock::now();
	}
	~
	StopWatch() {
		auto w = duration_cast< milliseconds >( system_clock::now() - m ).count();
		cerr << tag << ':' << w << endl;
	}
};


////////////////////////////////////////////////////////////////	Switch Buffer
template< typename T >	void
MergeOutplace( T* l, T* r, T* e, T* d ) {
	auto	wL = l;
	auto	wR = r;
	do {
		if ( wL == r ) {
			while ( wR < e ) *d++ = *wR++;
			break;
		}
		if ( wR == e ) {
			while ( wL < r ) *d++ = *wL++;
			break;
		}
		*d++ = *wL < *wR ? *wL++ : *wR++;
	} while ( true );
}

# undef	USE_GCD
# ifdef	USE_GCD
# include "dispatch/dispatch.h"
# endif

template< typename T >	void
MergeSort( T* l, T* r ) {

# ifdef	USE_GCD
cerr << "Using GCD" << endl;
# else
# ifdef _OPENMP
	cerr << "Using OpenMP" << endl;
# else
	cerr << "No parallelism" << endl;
# endif
# endif
	auto wSize = r - l;
	if ( wSize == 0 ) return;
	if ( wSize == 1 ) return;
	for ( auto i = 0; i < wSize / 2 * 2; i += 2 ) if ( l[ i + 1 ] < l[ i ] ) swap( l[ i ], l[ i + 1 ] );
	if ( wSize == 2 ) return;

	auto wSorted = 2;
	auto wInW = false;
	vector< T > w( wSize );
	
# ifdef	USE_GCD
	auto	q = dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 );
	auto	g = dispatch_group_create();
# endif
	do {
		auto wToBe = wSorted * 2;
		auto wBorder = wSize / wToBe * wToBe;

		T* wS = wInW ? &w[ 0 ] : l;
		T* wD = wInW ? l : &w[ 0 ];
		wInW = !wInW;

# ifdef	USE_GCD
		if ( wSorted < 1024 ) {
			for ( auto i = 0; i < wBorder; i += wToBe ) {
				auto w = wS + i;
				MergeOutplace( w, w + wSorted, w + wToBe, wD + i );
			}
			if ( wSize - wBorder > wSorted ) {
				auto w = wS + wBorder;
				MergeOutplace( w, w + wSorted, r, wD + wBorder );
			} else {
				for ( auto i = wBorder; i < wSize; i++ ) wD[ i ] = wS[ i ];
			}
		} else {
			for ( auto i = 0; i < wBorder; i += wToBe ) {
				dispatch_group_async( g, q, ^{
					auto w = wS + i;
					MergeOutplace( w, w + wSorted, w + wToBe, wD + i );
				} );
			}
			if ( wSize - wBorder > wSorted ) {
				dispatch_group_async( g, q, ^{
					auto w = wS + wBorder;
					MergeOutplace( w, w + wSorted, r, wD + wBorder );
				} );
			} else {
				for ( auto i = wBorder; i < wSize; i++ ) wD[ i ] = wS[ i ];
			}
			dispatch_group_wait( g, DISPATCH_TIME_FOREVER );
		}
# else
# ifdef _OPENMP
	#pragma omp parallel for
# endif
		for ( auto i = 0; i < wBorder; i += wToBe ) {
			auto	w = wS + i;
			MergeOutplace( w, w + wSorted, w + wToBe, wD + i );
		}
		if ( wSize - wBorder > wSorted ) {
			auto	w = wS + wBorder;
			MergeOutplace( w, w + wSorted, r, wD + wBorder );
		} else {
			for ( auto i = wBorder; i < wSize; i++ ) wD[ i ] = wS[ i ];
		}
# endif
		wSorted *= 2;
	} while ( wSorted < wSize );
	if ( wInW ) for ( auto i = 0; i < wSize; i++ ) l[ i ] = w[ i ];
}

int
main() {
	for ( unsigned wSize = 256 * 256; wSize <= 127 * 256 * 256 * 256; wSize *= 2 ) {
		auto	wTag = to_string( wSize );
		cerr << wTag << endl;
		{	auto wShuffledData = ShuffledData( wSize );
			cerr << "Start" << endl;
			{	auto wData = wShuffledData;
				{	StopWatch	wSW( wTag + " ShuffledData MergeSort" );
					MergeSort( &wData[ 0 ], &wData[ wData.size() ] );
				}
				Check( wData );
			}
			{	auto wData = wShuffledData;
				{	StopWatch	wSW( wTag + " ShuffledData std::sort" );
					sort( &wData[ 0 ], &wData[ wData.size() ] );
				}
				Check( wData );
			}
		}
		{	auto wRandomData = RandomData( wSize );
			cerr << "Start" << endl;
			auto wData1 = wRandomData;
			{	StopWatch	wSW( wTag + " RandomData MergeSort" );
				MergeSort( &wData1[ 0 ], &wData1[ wData1.size() ] );
			}
			auto wData2 = wRandomData;
			{	StopWatch	wSW( wTag + " RandomData std::sort" );
				sort( &wData2[ 0 ], &wData2[ wData2.size() ] );
			}
			for ( auto i = 0; i < wRandomData.size(); i++ ) if ( wData1[ i ] != wData2[ i ] ) throw 0;
		}
	}
	return 0;
}
9
7
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?