筛子排序(SieveSort) - 7

先前提到的想法已经实现了。

也就是说,彻底的不用memcpy,不折腾内存。

首先我们可以把这种算法称为“乒乓归并”(ping-pong merge)。

具体是什么原理呢?

对于给定的数组(可以非常大),首先做一个和它一样大的数组(result),然后把原来的数组(a)按照先前的归并思路进行划分,递归分配任务到排序函数中,如果还能继续划分就继续直到达到基本单位(比如256)。然后对数据进行排序,并把排序的结果,放在对侧的数组(result)中。递归返回到上一层的时候,在上面划分的每个区域都已经完成了16路归并,每个区域中的元素都是有序的。这时候再在这个结果数组中进行16路归并,并把结果写回到原数组(a),然后继续递归返回到上一层。在上面这一层中(a),16个区域显然也已经有序,对16个区域进行归并,结果放在数组(result)中……这样一直向上,最终可能有两种情况:1,结果归结在a里面,则释放result数组,返回a数组;2,结果在result数组中,释放a数组,并把原来指向a数组的指针指向result数组,并返回。我们可以用对指针的引用或者输入参数设定为指向数组的指针。

这样来回反复的归并,就避免了反复的向同一个数组复制数据。所有数据操作过程都是on-the-fly完成的。由此性能也得到了极大的提升。如果把前面用筛子排序法实现256数据排序的函数调用,换成std::sort,性能还能略微提升。

实测的结果如下:


i=8,t=256
==================================
samples:256
repeats:1
omp: 16 threads
sieve sort speed:50.2513K/s
std sort speed:  147.059K/s
t1(seive):1.99e-05 s
t2(std::):6.8e-06 s
ratio:34.1709%

i=12,t=4096
==================================
samples:4096
repeats:1
omp: 16 threads
sieve sort speed:10.8225K/s
std sort speed:  6.89655K/s
t1(seive):9.24e-05 s
t2(std::):0.000145 s
ratio:156.926%

i=16,t=65536
==================================
samples:65536
repeats:1
omp: 16 threads
sieve sort speed:0.621195K/s
std sort speed:  0.304813K/s
t1(seive):0.0016098 s
t2(std::):0.0032807 s
ratio:203.796%

i=20,t=1048576
==================================
samples:1048576
repeats:1
omp: 16 threads
sieve sort speed:0.0284363K/s
std sort speed:  0.0143527K/s
t1(seive):0.0351663 s
t2(std::):0.0696735 s
ratio:198.126%

i=24,t=16777216
==================================
samples:16777216
repeats:1
omp: 16 threads
sieve sort speed:0.00224595K/s
std sort speed:  0.000867732K/s
t1(seive):0.445247 s
t2(std::):1.15243 s
ratio:258.829%

i=28,t=268435456
==================================
samples:268435456
repeats:1
omp: 16 threads
sieve sort speed:0.000144218K/s
std sort speed:  4.5778e-05K/s
t1(seive):6.93396 s
t2(std::):21.8446 s
ratio:315.037%

可见实测效果普遍达到std::sort的两倍到3倍。

修改后的核心代码如下:

bool sieve_sort_core(uint32_t* a, size_t n, uint32_t* result, int depth, int omp_depth);
bool sieve_sort_omp(uint32_t* a, size_t n, uint32_t* result, int depth, int omp_depth) {
	size_t loops = 0, stride = 0, reminder = 0;
	__mmask16 mask = 0;
	if (!get_config(n, loops, stride, reminder, mask)) return false;
	if (omp_depth > 0 && depth >= 2) {
#pragma omp parallel for
		for (int i = 0; i < loops; i++) {
			sieve_sort_core(a + i * stride,
				(i == loops - 1 && reminder > 0) ? reminder : stride,
				result + i * stride,
				depth - 1, omp_depth - 1);
		}
	}
	else {
		for (int i = 0; i < loops; i++) {
			sieve_sort_core(a + i * stride,
				(i == loops - 1 && reminder > 0) ? reminder : stride,
				result + i * stride,
				depth - 1, omp_depth - 1);
		}
	}
	if (depth >= 4 && ((depth - 3) & 1) == 1) {
		std::swap(result, a);
	}
	return sieve_collect(n, loops, stride, reminder, mask, result, a);
}
bool sieve_sort_core(uint32_t* a, size_t n, uint32_t* result, int depth, int omp_depth) {
	return (n <= _256)
		? sieve_sort_256(a, n, result)
		: sieve_sort_omp(a, n, result, depth, omp_depth)
		;
}

bool sieve_sort(uint32_t** pa, size_t n, int omp_depth = 32)
{
	bool done = false;
	//max(n)==256P (2^60)
	if (pa == nullptr || *pa == nullptr || n > _256P)
		return false;
	else if (n == 0)
		return true;
	else if (n == 1) {
		return true;
	}
	else if (n == 2) {
		uint32_t a0 = *pa[0], a1 = *pa[1];
		*pa[0] = std::min(a0, a1);
		*pa[1] = std::max(a0, a1);
		return true;
	}
	else {
		uint32_t* result = new uint32_t[n];
		if (result != nullptr) {
			int max_depth = get_depth(n);
			done = sieve_sort_core(*pa, n, result, max_depth, omp_depth);
			if (max_depth >= 4 && ((max_depth & 1) == 0)) {
				std::swap(*pa, result);
			}
			delete[] result;
		}
	}
	return done;
}

P.S. 为什么要考虑排序的问题?

排序看似是很基本的问题,但是当数据的数量极其巨大的时候,它就变成了一个非常困难的问题。对于人来说,可以错,可以遗漏,但是对于机器来说,没法错,没法遗漏。所有的数据之间彼此的关联性共同构建了整个数组的顺序。所以它实际上是一个巨大的整体。对于这种巨大的整体进行排序显然是十分缓慢的。在多核多线程或者SIMD的条件下,显然我们希望能够把事情处理到可以大而化之的程度,这样的话各种并行性就能够用得上了。所以说,怎么把这些数据分开,怎么在小范围内排序,又如何从小范围的有序实现大范围的有序,才是这个问题中最有价值的部分。

不多说了,具体实现请参阅github上的源码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值