筛子排序(SieveSort) - 3

就这样,我们可以几乎最快速的获得16个32位整数的序列。

那么如何获得更多整数的序列呢?

一条SIMD指令最多能检查一个512位寄存器中的16个32位整数(也可以是8个64位整数或者32个16位整数,64个8位整数等等)获得这些整数中的最大或者最小值;那么如果要检测多于16个整数的情况,该怎么处理?比如要检测256个整数中的最大和最小值,该怎么做?

很容易想到的是,16个整数一组,有一个最大和最小值(也可能有多个),那么256个整数就可以分成16组,每一组的最大最小值,先记录下来,然后这16组的最大最小值再比较,从中获取最大和最小值,那就是这256个整数的最大和最小值。这个想法显然也是很朴素的。

具体实现的话,有如下代码,

void seive_get_min_max(
	int use_mask,
	uint32_t& p_min,
	uint32_t& p_max,
	uint32_t& _min_count,
	uint32_t& _max_count,
	__mmask16& _all_masks,
	__mmask16 masks[16],
	__m512i values[16]) {

	_min_count = 0;
	_max_count = 0;
	if (_all_masks == 0) return;
	uint32_t  _mines[16] = { 0 };
	__mmask16 mask_mines[16] = { 0 };
	uint32_t  _maxes[16] = { 0 };
	__mmask16 mask_maxes[16] = { 0 };
	for (int i = 0; i < 16; i++) {
		if ((_all_masks & (1 << i)) != 0) {
			if (sieve_get_min_max(
				masks[i],
				values[i],
				_mines[i], _maxes[i], mask_mines[i], mask_maxes[i]))
			{
				//OK
			}
		}
	}
	__mmask16 found_mask = 0;
	if ((use_mask & 1) != 0 && sieve_get_min(_all_masks, _mm512_loadu_epi32(_mines), p_min, found_mask)) {
		__m256i __mask_mines = _mm256_loadu_epi16(mask_mines);
		_min_count = _mm512_reduce_add_epu16(_mm512_castsi256_si512(
			_mm256_maskz_popcnt_epi16(found_mask, __mask_mines)));
		__m512i _mask_mines = _mm512_cvtepu16_epi32(__mask_mines);
		__m512i _masks = _mm512_cvtepu16_epi32(_mm256_loadu_epi16(masks));
		_masks = _mm512_mask_andnot_epi32(_masks, found_mask, _mask_mines, _masks);
		_all_masks &= ~_mm512_cmpeq_epu32_mask(
			_masks, zero);
		_mm256_storeu_epi16(masks, _mm512_cvtepi32_epi16(_masks));
	}
	if ((use_mask & 2) != 0 && sieve_get_max(_all_masks, _mm512_loadu_epi32(_maxes), p_max, found_mask)) {
		__m256i __mask_maxes = _mm256_loadu_epi16(mask_maxes);
		_max_count = _mm512_reduce_add_epu16(_mm512_castsi256_si512(
			_mm256_maskz_popcnt_epi16(found_mask, __mask_maxes)));
		__m512i _mask_maxes = _mm512_cvtepu16_epi32(__mask_maxes);
		__m512i _masks = _mm512_cvtepu16_epi32(_mm256_loadu_epi16(masks));
		_masks = _mm512_mask_andnot_epi32(_masks, found_mask, _mask_maxes, _masks);
		_all_masks &= ~_mm512_cmpeq_epu32_mask(
			_masks, zero);
		_mm256_storeu_epi16(masks, _mm512_cvtepi32_epi16(_masks));
	}
}

代码并不清晰,因为它是高度优化的。循环的部分获取了16对最大最小值,以及对应的掩码。后面两个部分(use_mask&1,use_mask&2)处理如下问题:获得了整体的最大值和最小值之后,要把那些具有最大值和最小值的掩码进一步掩掉。如果某一个16个整数的段都已经检测过,则把对应的整体掩码掩掉(对应的位置0)。

这就使得每一次调用都能获得256中的最大和最小值,当然也同时报出了最大最小值出现的次数。

最后,只需要把最大和最小值一一求出,然后在结果中首尾各放一个,就获得了256个整数排序的能力。

void sieve_sort_256_dual(uint32_t a[_256], uint32_t* result = nullptr) {
	__m512i values[16];
	for (size_t i = 0; i < 16; i++) {
		values[i] = _mm512_loadu_epi32(a + (i << 4));
	}
	__mmask16 masks[16];
	memset(masks, 0xff, sizeof(masks));

	result = result == nullptr ? a : result;

	__mmask16 all_masks = 0xffff;
	uint32_t _min = 0, _max = 0;
	uint32_t _min_count = 0, _max_count = 0;
	int i = 0, j = 255;
	while (i <= j) {
		seive_get_min_max(
			3,
			_min, _max,
			_min_count, _max_count,
			all_masks, masks, values);

		for (size_t t = 0; t < _min_count; t++) {
			result[i++] = _min;
		}
		for (size_t t = 0; t < _max_count; t++) {
			result[j--] = _max;
		}
	}
}

这里的里外两层循环都没有做优化。因为一个数出现16次以上的概率并不大。

上面的代码没有处理mask的部分,因为相应部分已经在seive_get_min_max处理好了。

此时,我们就有了处理256个整数的排序的能力了。

顺便说一句,如果要处理128个整数呢 ?目前我们还没有涉及到倍增的处理方法,所以可以想先用填充法来实现。比如先开辟一块内存,复制这128个整数到里面,然后在后面填充整数的最大值。计算完成之后,再截取前128个作为结果。这一点无需证明。因为最大值都是一样的。即便这128个里面含有最大值(0xffffffff)它也只是在排序过程中被排到后面去了。在尾部填0也行,只是需要在获得结果的时候,倒着数128个做为结果的开始即可。0和0xffffffff之外的别的数就不要添加了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值