Algorithm 最快的固定长度6整数数组排序
在回答另一个堆栈溢出问题时,我偶然发现了一个有趣的子问题。对6个整数数组进行排序的最快方法是什么 由于问题的层次很低: 我们不能假设库是可用的,调用本身也有成本,只有普通的C 为了避免清空成本非常高的指令管道,我们应该尽可能减少分支、跳转和其他类型的控制流中断,比如隐藏在&&或| |中的序列点后面的那些。 空间受限,最小化寄存器和内存使用是一个问题,理想情况下就地排序可能是最好的。 实际上,这个问题是一种高尔夫,目标不是最小化源代码长度,而是最小化执行时间。我称之为“Zening”代码,正如作者及其同事在书的标题中使用的那样 至于它为什么有趣,有几个层次: 这个例子很简单,易于理解和测量,不需要太多的C语言技巧 它显示了为问题选择好算法的效果,也显示了编译器和底层硬件的效果。 这里是我的参考幼稚,没有优化的实现和我的测试集Algorithm 最快的固定长度6整数数组排序,algorithm,sorting,optimization,gpgpu,sorting-network,Algorithm,Sorting,Optimization,Gpgpu,Sorting Network,在回答另一个堆栈溢出问题时,我偶然发现了一个有趣的子问题。对6个整数数组进行排序的最快方法是什么 由于问题的层次很低: 我们不能假设库是可用的,调用本身也有成本,只有普通的C 为了避免清空成本非常高的指令管道,我们应该尽可能减少分支、跳转和其他类型的控制流中断,比如隐藏在&&或| |中的序列点后面的那些。 空间受限,最小化寄存器和内存使用是一个问题,理想情况下就地排序可能是最好的。 实际上,这个问题是一种高尔夫,目标不是最小化源代码长度,而是最小化执行时间。我称之为“Zening”代码,正如作者
#include <stdio.h>
static __inline__ int sort6(int * d){
char j, i, imin;
int tmp;
for (j = 0 ; j < 5 ; j++){
imin = j;
for (i = j + 1; i < 6 ; i++){
if (d[i] < d[imin]){
imin = i;
}
}
tmp = d[j];
d[j] = d[imin];
d[imin] = tmp;
}
}
static __inline__ unsigned long long rdtsc(void)
{
unsigned long long int x;
__asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
return x;
}
int main(int argc, char ** argv){
int i;
int d[6][5] = {
{1, 2, 3, 4, 5, 6},
{6, 5, 4, 3, 2, 1},
{100, 2, 300, 4, 500, 6},
{100, 2, 3, 4, 500, 6},
{1, 200, 3, 4, 5, 600},
{1, 1, 2, 1, 2, 1}
};
unsigned long long cycles = rdtsc();
for (i = 0; i < 6 ; i++){
sort6(d[i]);
/*
* printf("d%d : %d %d %d %d %d %d\n", i,
* d[i][0], d[i][6], d[i][7],
* d[i][8], d[i][9], d[i][10]);
*/
}
cycles = rdtsc() - cycles;
printf("Time is %d\n", (unsigned)cycles);
}
如果我们相信我们的测试集,是的,它是非常差的,它的唯一好处是短,简单,易于理解我们正在测量的内容,一种类型的结果代码的平均周期数低于40个周期6个测试被执行。这使得每笔掉期平均有4个周期。我称之为惊人的快。还有其他可能的改进吗?下面是一个使用以下工具的实现: 为此,您确实需要非常高效的无分支最小和最大实现,因为这就是这段代码的基本内容——总共13个最小和最大操作序列。我将此作为练习留给读者 请注意,此实现易于矢量化,例如SIMD-大多数SIMD ISA具有向量最小/最大指令,也适用于GPU实现,例如CUDA-无分支,因此不存在扭曲发散等问题
另请参见:对于任何优化,总是最好测试、测试、测试。我会尝试至少排序网络和插入排序。如果我是在打赌,我会根据过去的经验把钱放在插入排序上 你知道输入数据吗?某些算法在处理某些类型的数据时会表现得更好。例如,插入排序在已排序或几乎排序的数据上执行得更好,因此,如果几乎排序的数据的概率高于平均值,则插入排序将是更好的选择 您发布的算法类似于插入排序,但看起来您已经以更多比较为代价最小化了交换的数量。不过,比较要比交换昂贵得多,因为分支会导致指令管道暂停 下面是一个插入排序实现:
static __inline__ int sort6(int *d){
int i, j;
for (i = 1; i < 6; i++) {
int tmp = d[i];
for (j = i; j >= 1 && tmp < d[j-1]; j--)
d[j] = d[j-1];
d[j] = tmp;
}
}
下面是我如何建立一个分类网络。首先,使用为适当长度的网络生成一组最小的交换宏。将其包装到函数中,可以得到:
static __inline__ int sort6(int * d){
#define SWAP(x,y) if (d[y] < d[x]) { int tmp = d[x]; d[x] = d[y]; d[y] = tmp; }
SWAP(1, 2);
SWAP(0, 2);
SWAP(0, 1);
SWAP(4, 5);
SWAP(3, 5);
SWAP(3, 4);
SWAP(0, 3);
SWAP(1, 4);
SWAP(2, 5);
SWAP(2, 4);
SWAP(1, 3);
SWAP(2, 3);
#undef SWAP
}
既然这些都是整数,比较起来也很快,为什么不直接计算每个整数的秩序呢:
inline void sort6(int *d) {
int e[6];
memcpy(e,d,6*sizeof(int));
int o0 = (d[0]>d[1])+(d[0]>d[2])+(d[0]>d[3])+(d[0]>d[4])+(d[0]>d[5]);
int o1 = (d[1]>=d[0])+(d[1]>d[2])+(d[1]>d[3])+(d[1]>d[4])+(d[1]>d[5]);
int o2 = (d[2]>=d[0])+(d[2]>=d[1])+(d[2]>d[3])+(d[2]>d[4])+(d[2]>d[5]);
int o3 = (d[3]>=d[0])+(d[3]>=d[1])+(d[3]>=d[2])+(d[3]>d[4])+(d[3]>d[5]);
int o4 = (d[4]>=d[0])+(d[4]>=d[1])+(d[4]>=d[2])+(d[4]>=d[3])+(d[4]>d[5]);
int o5 = 15-(o0+o1+o2+o3+o4);
d[o0]=e[0]; d[o1]=e[1]; d[o2]=e[2]; d[o3]=e[3]; d[o4]=e[4]; d[o5]=e[5];
}
XOR交换在交换函数中可能很有用
void xorSwap (int *x, int *y) {
if (*x != *y) {
*x ^= *y;
*y ^= *x;
*x ^= *y;
}
}
if可能会导致代码中出现太多分歧,但如果您能保证所有INT都是唯一的,这可能会很方便。我将测试套件移植到一台PPC体系结构机器上,我无法识别,不必接触代码,只需增加测试的迭代次数,使用8个测试用例避免mods污染结果,并替换x86特定rdtsc: 直接调用qsort库函数:101 朴素实现插入排序:299 插入排序Daniel Stutzbach:108 展开的插入排序:51 分类网络Daniel Stutzbach:26 分拣网络Paul R:85 具有快速交换功能的分拣网络12:117 排序网络12重新排序的交换:116
排名顺序:56而我非常喜欢提供的swap宏:
#define min(x, y) (y ^ ((x ^ y) & -(x < y)))
#define max(x, y) (x ^ ((x ^ y) & -(x < y)))
#define SWAP(x,y) { int tmp = min(d[x], d[y]); d[y] = max(d[x], d[y]); d[x] = tmp; }
我看到一个好的编译器可能会有一个改进:
#define SWAP(x,y) { int tmp = ((x ^ y) & -(y < x)); y ^= tmp; x ^= tmp; }
我们注意到min和max是如何工作的,并显式地拉取公共子表达式。这将完全消除最小和最大宏。如果插入排序在这里具有相当的竞争力,我建议尝试shellsort。我担心6个元素可能太少了,它不可能是最好的,但它可能值得一试 示例代码、未测试、未调试等。例如,您希望调整inc=4和inc-=3序列以找到最佳try inc=2,inc-=1
static __inline__ int sort6(int * d) {
char j, i;
int tmp;
for (inc = 4; inc > 0; inc -= 3) {
for (i = inc; i < 5; i++) {
tmp = a[i];
j = i;
while (j >= inc && a[j - inc] > tmp) {
a[j] = a[j - inc];
j -= inc;
}
a[j] = tmp;
}
}
}
我不认为这会赢,但是如果有人发布了一个关于排序10个元素的问题,谁知道呢
根据维基百科的说法,这甚至可以与排序网络相结合:
普拉特诉1979年。Shellsort和sorting networks是计算机科学领域的优秀论文。花环。ISBN 0-824-04406-1,
如果只有6个元素,您可以利用并行性,希望最小化条件分支,等等。为什么不生成所有组合并测试顺序?我敢说,在某些体系结构中,只要预先分配了内存,速度可能会非常快看起来我参加聚会晚了一年,但现在我们开始 通过查看GCC4.5.2生成的程序集,我发现每次交换都会加载和存储,这实际上是不需要的。最好将6个值加载到寄存器中,对它们进行排序,然后将它们存储回内存。我命令仓库里的货物尽可能靠近那里,寄存器是第一个需要的,也是最后一个使用的。我还使用了Steinar H.Gunderson的SWAP宏。更新:我切换到Paolo Bonzini的SWAP宏,gcc将其转换为与Gunderson类似的东西,但gcc能够更好地排序指令,因为它们不是作为显式程序集提供的 我使用了与重新排序的交换网络相同的交换顺序,将其作为性能最好的交换网络,尽管可能有更好的顺序。如果我找到更多的时间,我将生成并测试一组排列
我更改了测试代码,考虑了4000个数组,并显示了每一个排序所需的平均周期数。在i5-650上,我使用-O3获得了约34.1个循环/排序,而原始重新排序排序网络使用-O1、beats-O2和-O3获得了约65.3个循环/排序
#include <stdio.h>
static inline void sort6_fast(int * d) {
#define SWAP(x,y) { int dx = x, dy = y, tmp; tmp = x = dx < dy ? dx : dy; y ^= dx ^ tmp; }
register int x0,x1,x2,x3,x4,x5;
x1 = d[1];
x2 = d[2];
SWAP(x1, x2);
x4 = d[4];
x5 = d[5];
SWAP(x4, x5);
x0 = d[0];
SWAP(x0, x2);
x3 = d[3];
SWAP(x3, x5);
SWAP(x0, x1);
SWAP(x3, x4);
SWAP(x1, x4);
SWAP(x0, x3);
d[0] = x0;
SWAP(x2, x5);
d[5] = x5;
SWAP(x1, x3);
d[1] = x1;
SWAP(x2, x4);
d[4] = x4;
SWAP(x2, x3);
d[2] = x2;
d[3] = x3;
#undef SWAP
#undef min
#undef max
}
static __inline__ unsigned long long rdtsc(void)
{
unsigned long long int x;
__asm__ volatile ("rdtsc; shlq $32, %%rdx; orq %%rdx, %0" : "=a" (x) : : "rdx");
return x;
}
void ran_fill(int n, int *a) {
static int seed = 76521;
while (n--) *a++ = (seed = seed *1812433253 + 12345);
}
#define NTESTS 4096
int main() {
int i;
int d[6*NTESTS];
ran_fill(6*NTESTS, d);
unsigned long long cycles = rdtsc();
for (i = 0; i < 6*NTESTS ; i+=6) {
sort6_fast(d+i);
}
cycles = rdtsc() - cycles;
printf("Time is %.2lf\n", (double)cycles/(double)NTESTS);
for (i = 0; i < 6*NTESTS ; i+=6) {
if (d[i+0] > d[i+1] || d[i+1] > d[i+2] || d[i+2] > d[i+3] || d[i+3] > d[i+4] || d[i+4] > d[i+5])
printf("d%d : %d %d %d %d %d %d\n", i,
d[i+0], d[i+1], d[i+2],
d[i+3], d[i+4], d[i+5]);
}
return 0;
}
测试代码非常糟糕;它溢出了初始数组。这里的人没有读到编译器警告吗?printf打印出了错误的元素,它使用了.byte作为rdtsc。没有什么好的理由,只有一次运行!,没有什么可以检查最终结果是否正确,所以很容易“优化”到一些微妙的错误,包括的测试是非常基本的没有负数?没有什么可以阻止编译器将整个函数作为死代码丢弃 也就是说,改进双音网络解决方案也很容易;只需将最小/最大/交换内容更改为
#define SWAP(x,y) { int tmp; asm("mov %0, %2 ; cmp %1, %0 ; cmovg %1, %0 ; cmovg %2, %1" : "=r" (d[x]), "=r" (d[y]), "=r" (tmp) : "0" (d[x]), "1" (d[y]) : "cc"); }
对于我来说,使用-O2、amd64、Core i7的Debian gcc 4.4.5比使用-O2、amd64和Core i7的Debian gcc 4.4.5快了约65%。如果不进行基准测试和查看实际编译器生成的程序集,就永远不会优化最小值/最大值。如果我让GCC使用条件移动指令优化min,我将获得33%的加速:
#define SWAP(x,y) { int dx = d[x], dy = d[y], tmp; tmp = d[x] = dx < dy ? dx : dy; d[y] ^= dx ^ tmp; }
这里是插入排序:
//#define ITER(x) { if (t < d[x]) { d[x+1] = d[x]; d[x] = t; } }
//Faster on x86, probably slower on ARM or similar:
#define ITER(x) { d[x+1] ^= t < d[x] ? d[x] ^ d[x+1] : 0; d[x] = t < d[x] ? t : d[x]; }
static inline void sort6_insertion_sort_unrolled_v2(int * d){
int t;
t = d[1]; ITER(0);
t = d[2]; ITER(1); ITER(0);
t = d[3]; ITER(2); ITER(1); ITER(0);
t = d[4]; ITER(3); ITER(2); ITER(1); ITER(0);
t = d[5]; ITER(4); ITER(3); ITER(2); ITER(1); ITER(0);
对于六个元素,插入排序与排序网络竞争12次交换与15次迭代,平衡4条指令/交换与3条指令/迭代;当然,泡沫是比较慢的。但是,当大小增加时,情况就不一样了,因为插入排序在^2上,而排序网络在日志n上。期待着尝试这一点并从这些示例中学习,但首先从我的1.5 GHz PPC Powerbook G4 w/1 GB DDR RAM中进行一些计时。我借用了一个类似rdtsc的计时器来计时PPC。我运行了几次程序,绝对结果各不相同,但始终最快的测试是插入排序Daniel Stutzbach,插入排序展开了一秒 以下是最后一组时间:
**Direct call to qsort library function** : 164
**Naive implementation (insertion sort)** : 138
**Insertion Sort (Daniel Stutzbach)** : 85
**Insertion Sort Unrolled** : 97
**Sorting Networks (Daniel Stutzbach)** : 457
**Sorting Networks (Paul R)** : 179
**Sorting Networks 12 with Fast Swap** : 238
**Sorting Networks 12 reordered Swap** : 236
**Rank Order** : 116
我相信你的问题有两个部分 首先是确定最优算法。至少在这种情况下,这是通过循环遍历每个可能的顺序来完成的。没有那么多的顺序允许您计算比较和交换的精确最小、最大、平均和标准偏差。还有一两个亚军在手。 二是优化算法。要将教科书上的代码示例转换为平均和精简的实际算法,可以做很多工作。如果你意识到一个算法不能被优化到所需的程度,试试亚军。
假设当前的x86:branch预测已经取得了长足的进步,我不会太担心清空管道。我会担心的是,确保代码和数据放在一个缓存线中,每个缓存线可能有两个。一旦出现回迁延迟,回迁延迟就会非常低,这将补偿任何暂停。这也意味着你的内部循环可能是10条指令左右,这是正确的,在我的排序算法中有两个不同的内部循环,它们分别是10条指令/22字节和9/22长。假设代码不包含任何div,您可以确定它的速度会非常快。以下是我对这个线程的贡献:为包含唯一值的6成员int-vector valp优化了1,4 gap shell排序
void shellsort (int *valp)
{
int c,a,*cp,*ip=valp,*ep=valp+5;
c=*valp; a=*(valp+4);if (c>a) {*valp= a;*(valp+4)=c;}
c=*(valp+1);a=*(valp+5);if (c>a) {*(valp+1)=a;*(valp+5)=c;}
cp=ip;
do
{
c=*cp;
a=*(cp+1);
do
{
if (c<a) break;
*cp=a;
*(cp+1)=c;
cp-=1;
c=*cp;
} while (cp>=valp);
ip+=1;
cp=ip;
} while (ip<ep);
}
在配备双核Athlon M300@2 Ghz DDR2内存的HP dv7-3010so笔记本电脑上,它以165个时钟周期执行。这是从计时每个唯一序列6开始计算的平均值/总共720个。使用OpenWatcom 1.8编译到Win32。循环本质上是一种插入排序,长度为16条指令/37字节
我没有一个64位的编译环境。几天前,我从谷歌无意中发现了这个问题,因为我还需要快速排序一个固定长度的arr 是6个整数的整数。然而,在我的例子中,我的整数只有8位而不是32位,并且我没有只使用C的严格要求。我想我无论如何都会分享我的发现,以防它们可能对某人有所帮助 我在汇编中实现了一个网络排序的变体,它尽可能使用SSE将比较和交换操作矢量化。完成数组排序需要六次过程。我使用了一种新的机制直接将PCMPGTB矢量化的结果与PSHUFB矢量化交换的洗牌参数进行转换,仅使用PADDB矢量化的add,在某些情况下还使用PAND位and指令 这种方法也产生了副作用,产生了真正的无分支功能。没有任何跳转指令 这一实现似乎比使用简单交换的问题排序网络12中当前标记为最快选项的实现快38%。我修改了该实现,在测试期间使用了char数组元素,以使比较公平 我应该注意到,这种方法可以应用于任何大小为16个元素的数组。我预计,与其他替代方案相比,对于更大的阵列,相对速度优势会变得更大 代码是用MASM编写的,适用于带有SSSE3的x86_64处理器。该函数使用新的Windows x64调用约定。这是
PUBLIC simd_sort_6
.DATA
ALIGN 16
pass1_shuffle OWORD 0F0E0D0C0B0A09080706040503010200h
pass1_add OWORD 0F0E0D0C0B0A09080706050503020200h
pass2_shuffle OWORD 0F0E0D0C0B0A09080706030405000102h
pass2_and OWORD 00000000000000000000FE00FEFE00FEh
pass2_add OWORD 0F0E0D0C0B0A09080706050405020102h
pass3_shuffle OWORD 0F0E0D0C0B0A09080706020304050001h
pass3_and OWORD 00000000000000000000FDFFFFFDFFFFh
pass3_add OWORD 0F0E0D0C0B0A09080706050404050101h
pass4_shuffle OWORD 0F0E0D0C0B0A09080706050100020403h
pass4_and OWORD 0000000000000000000000FDFD00FDFDh
pass4_add OWORD 0F0E0D0C0B0A09080706050403020403h
pass5_shuffle OWORD 0F0E0D0C0B0A09080706050201040300h
pass5_and OWORD 0000000000000000000000FEFEFEFE00h
pass5_add OWORD 0F0E0D0C0B0A09080706050403040300h
pass6_shuffle OWORD 0F0E0D0C0B0A09080706050402030100h
pass6_add OWORD 0F0E0D0C0B0A09080706050403030100h
.CODE
simd_sort_6 PROC FRAME
.endprolog
; pxor xmm4, xmm4
; pinsrd xmm4, dword ptr [rcx], 0
; pinsrb xmm4, byte ptr [rcx + 4], 4
; pinsrb xmm4, byte ptr [rcx + 5], 5
; The benchmarked 38% faster mentioned in the text was with the above slower sequence that tied up the shuffle port longer. Same on extract
; avoiding pins/extrb also means we don't need SSE 4.1, but SSSE3 CPUs without SSE4.1 (e.g. Conroe/Merom) have slow pshufb.
movd xmm4, dword ptr [rcx]
pinsrw xmm4, word ptr [rcx + 4], 2 ; word 2 = bytes 4 and 5
movdqa xmm5, xmm4
pshufb xmm5, oword ptr [pass1_shuffle]
pcmpgtb xmm5, xmm4
paddb xmm5, oword ptr [pass1_add]
pshufb xmm4, xmm5
movdqa xmm5, xmm4
pshufb xmm5, oword ptr [pass2_shuffle]
pcmpgtb xmm5, xmm4
pand xmm5, oword ptr [pass2_and]
paddb xmm5, oword ptr [pass2_add]
pshufb xmm4, xmm5
movdqa xmm5, xmm4
pshufb xmm5, oword ptr [pass3_shuffle]
pcmpgtb xmm5, xmm4
pand xmm5, oword ptr [pass3_and]
paddb xmm5, oword ptr [pass3_add]
pshufb xmm4, xmm5
movdqa xmm5, xmm4
pshufb xmm5, oword ptr [pass4_shuffle]
pcmpgtb xmm5, xmm4
pand xmm5, oword ptr [pass4_and]
paddb xmm5, oword ptr [pass4_add]
pshufb xmm4, xmm5
movdqa xmm5, xmm4
pshufb xmm5, oword ptr [pass5_shuffle]
pcmpgtb xmm5, xmm4
pand xmm5, oword ptr [pass5_and]
paddb xmm5, oword ptr [pass5_add]
pshufb xmm4, xmm5
movdqa xmm5, xmm4
pshufb xmm5, oword ptr [pass6_shuffle]
pcmpgtb xmm5, xmm4
paddb xmm5, oword ptr [pass6_add]
pshufb xmm4, xmm5
;pextrd dword ptr [rcx], xmm4, 0 ; benchmarked with this
;pextrb byte ptr [rcx + 4], xmm4, 4 ; slower version
;pextrb byte ptr [rcx + 5], xmm4, 5
movd dword ptr [rcx], xmm4
pextrw word ptr [rcx + 4], xmm4, 2 ; x86 is little-endian, so this is the right order
ret
simd_sort_6 ENDP
END
您可以将其编译为可执行对象,并将其链接到您的C项目中。有关如何在VisualStudio中执行此操作的说明,请阅读。您可以使用以下C原型从C代码调用函数:
void simd_sort_6(char *values);
这个问题已经很老了,但最近我不得不解决同样的问题:对小数组进行排序的快速算法。我认为分享我的知识是个好主意。当我第一次开始使用排序网络时,我终于找到了其他算法,对6个值的每个排列进行排序所执行的比较总数比使用排序网络时小,比使用插入排序时小。我没有计算掉期的数量;我希望它大致相当,有时可能更高一点 算法sort6使用算法sort4,该算法使用算法sort3。这里是在一些轻C++形式下实现的,它是模板,它可以与任意的随机存取迭代器和任何合适的比较函数一起工作。 以下算法是展开插入排序。当必须执行两次掉期6分配时,它使用4个分配:
void sort3(int* array)
{
if (array[1] < array[0]) {
if (array[2] < array[0]) {
if (array[2] < array[1]) {
std::swap(array[0], array[2]);
} else {
int tmp = array[0];
array[0] = array[1];
array[1] = array[2];
array[2] = tmp;
}
} else {
std::swap(array[0], array[1]);
}
} else {
if (array[2] < array[1]) {
if (array[2] < array[0]) {
int tmp = array[2];
array[2] = array[1];
array[1] = array[0];
array[0] = tmp;
} else {
std::swap(array[1], array[2]);
}
}
}
}
该算法执行3到6次比较,最多5次交换。展开插入排序很容易,但我们将对最后一个排序使用另一种算法
排序6个值
这一个使用了我称之为双插入排序的展开版本。这个名字不是很好,但是很有描述性,下面是它的工作原理:
对数组中除第一个和最后一个元素以外的所有元素进行排序。
如果第一个大于最后一个,则交换数组的第一个和元素。
从前面将第一个元素插入排序序列,然后从后面将最后一个元素插入排序序列。
交换后,第一个元素总是比最后一个元素小,这意味着在将它们插入排序序列时,在最坏的情况下插入两个元素的比较不会超过N次:例如,如果第一个元素已插入到第三个位置,那么最后一个位置不能低于第四个位置插入
void sort6(int* array)
{
// Sort everything but first and last elements
sort4(array+1);
// Switch first and last elements if needed
if (array[5] < array[0]) {
std::swap(array[0], array[5]);
}
// Insert first element from the front
if (array[1] < array[0]) {
std::swap(array[0], array[1]);
if (array[2] < array[1]) {
std::swap(array[1], array[2]);
if (array[3] < array[2]) {
std::swap(array[2], array[3]);
if (array[4] < array[3]) {
std::swap(array[3], array[4]);
}
}
}
}
// Insert last element from the back
if (array[5] < array[4]) {
std::swap(array[4], array[5]);
if (array[4] < array[3]) {
std::swap(array[3], array[4]);
if (array[3] < array[2]) {
std::swap(array[2], array[3]);
if (array[2] < array[1]) {
std::swap(array[1], array[2]);
}
}
}
}
}
我对6个值的每个排列进行的测试表明,该算法总是执行6到13次比较。我没有计算执行的掉期数量,但我不希望在最坏的情况下它会高于11
我希望这会有所帮助,即使这个问题可能不再是一个实际问题:
编辑:在将其放入提供的基准测试后,它显然比大多数有趣的备选方案慢。它的性能往往比展开插入排序要好一点,但基本上就是这样。基本上,它不是整数的最佳排序,但对于具有昂贵比较操作的类型可能会很有趣。我知道我已经迟到了,但我有兴趣尝试一些不同的解决方案。首先,我清理了该粘贴,使其编译,并将其放入存储库。我保留了一些不受欢迎的解决方案作为死胡同,这样其他人就不会尝试了。其中包括我的第一个解决方案,它试图确保x1>x2只计算一次。经过优化后,它并不比其他简单版本快 我添加了一个循环版本的秩序排序,因为我自己在这项研究中的应用是对2-8项进行排序,所以由于参数的数量可变,所以循环是必要的。这也是我忽略排序网络解决方案的原因 测试代码没有测试是否正确处理了重复项,因此,尽管现有的解决方案 完全正确,我在测试代码中添加了一个特例,以确保正确处理重复项 然后,我编写了一个完全在AVX寄存器中的插入排序。在我的机器上,它比其他插入排序快25%,但比秩顺序慢100%。我这样做纯粹是为了实验,由于插入排序中的分支,我并不期望这会更好
static inline void sort6_insertion_sort_avx(int* d) {
__m256i src = _mm256_setr_epi32(d[0], d[1], d[2], d[3], d[4], d[5], 0, 0);
__m256i index = _mm256_setr_epi32(0, 1, 2, 3, 4, 5, 6, 7);
__m256i shlpermute = _mm256_setr_epi32(7, 0, 1, 2, 3, 4, 5, 6);
__m256i sorted = _mm256_setr_epi32(d[0], INT_MAX, INT_MAX, INT_MAX,
INT_MAX, INT_MAX, INT_MAX, INT_MAX);
__m256i val, gt, permute;
unsigned j;
// 8 / 32 = 2^-2
#define ITER(I) \
val = _mm256_permutevar8x32_epi32(src, _mm256_set1_epi32(I));\
gt = _mm256_cmpgt_epi32(sorted, val);\
permute = _mm256_blendv_epi8(index, shlpermute, gt);\
j = ffs( _mm256_movemask_epi8(gt)) >> 2;\
sorted = _mm256_blendv_epi8(_mm256_permutevar8x32_epi32(sorted, permute),\
val, _mm256_cmpeq_epi32(index, _mm256_set1_epi32(j)))
ITER(1);
ITER(2);
ITER(3);
ITER(4);
ITER(5);
int x[8];
_mm256_storeu_si256((__m256i*)x, sorted);
d[0] = x[0]; d[1] = x[1]; d[2] = x[2]; d[3] = x[3]; d[4] = x[4]; d[5] = x[5];
#undef ITER
}
然后,我使用AVX编写了一个排名排序。这与其他秩顺序解的速度相匹配,但并不快。这里的问题是,我只能用AVX计算指数,然后我必须制作一个指数表。这是因为计算是基于目标的,而不是基于源的。看
可以在此处找到回购协议:尝试“合并排序列表”排序:使用两个数组。对于小型和大型阵列,速度最快。 如果选择插入,则只检查插入的位置。不需要比较cmp=a-b>0的其他较大值。 对于4个数字,可以使用系统4-5 cmp~4.6或3-6 cmp~4.9。气泡排序使用6cmp6。大量cmp用于大数字和较慢的代码。 此代码使用5 cmp非MSL排序: 如果cmparr[n][i+0],arr[n][i+1]>0{swapn,i+0,i+1;} 如果cmparr[n][i+2],arr[n][i+3]>0{swapn,i+2,i+3;} 如果cmparr[n][i+0],arr[n][i+2]>0{swapn,i+0,i+2;} 如果cmparr[n][i+1],arr[n][i+3]>0{swapn,i+1,i+3;} 如果cmparr[n][i+1],arr[n][i+2]>0{swapn,i+1,i+2;} 主MSL 9 8 7 6 5 4 3 2 1 0 89 67 45 23 01 ... concat两个排序列表,列表长度=1 6789 2345 01 ... concat两个排序列表,列表长度=2 23456789 01 ... concat两个排序列表,列表长度=4 0123456789 ... concat两个排序列表,列表长度=8 js代码 函数sortListMerge_2acmp { var阶跃、阶跃最大值、tmp、a、b、c、i、j、k、m、n、循环; var start=0; var end=arr_计数; //var-str=; 周期=0; 如果结束>3 {
stepmax=end-start+1>>1使用cmp==0对4个项目进行排序。 cmp的数量约为4.34 FF本机约为4.52,但所需时间是合并列表的3倍。但如果您有大数字或大文本,则最好减少cmp操作。 编辑:修复了错误 在线测试
我发现,至少在我的系统上,下面定义的函数sort6_iterator和sort6_iterator_local的运行速度至少与上述当前记录保持者的运行速度一样快,而且常常明显快:
#define MIN(x, y) (x<y?x:y)
#define MAX(x, y) (x<y?y:x)
template<class IterType>
inline void sort6_iterator(IterType it)
{
#define SWAP(x,y) { const auto a = MIN(*(it + x), *(it + y)); \
const auto b = MAX(*(it + x), *(it + y)); \
*(it + x) = a; *(it + y) = b; }
SWAP(1, 2) SWAP(4, 5)
SWAP(0, 2) SWAP(3, 5)
SWAP(0, 1) SWAP(3, 4)
SWAP(1, 4) SWAP(0, 3)
SWAP(2, 5) SWAP(1, 3)
SWAP(2, 4)
SWAP(2, 3)
#undef SWAP
}
请注意,如下定义SWAP有时会导致性能稍好一些,尽管大多数情况下它会导致性能稍差或性能差异可以忽略不计
#define SWAP(x,y) { const auto a = MIN(data##x, data##y); \
data##y = MAX(data##x, data##y); \
data##x = a; }
如果您只需要一个排序算法,它可以在基本数据类型上运行,那么无论对排序函数的调用出现在什么上下文中,gcc-O3都始终擅长于优化。然后,根据您传递输入的方式,尝试以下两种算法之一:
template<class T> inline void sort6(T it) {
#define SORT2(x,y) {if(data##x>data##y){auto a=std::move(data##y);data##y=std::move(data##x);data##x=std::move(a);}}
#define DD1(a) register auto data##a=*(it+a);
#define DD2(a,b) register auto data##a=*(it+a);register auto data##b=*(it+b);
#define CB1(a) *(it+a)=data##a;
#define CB2(a,b) *(it+a)=data##a;*(it+b)=data##b;
DD2(1,2) SORT2(1,2)
DD2(4,5) SORT2(4,5)
DD1(0) SORT2(0,2)
DD1(3) SORT2(3,5)
SORT2(0,1) SORT2(3,4) SORT2(2,5) CB1(5)
SORT2(1,4) SORT2(0,3) CB1(0)
SORT2(2,4) CB1(4)
SORT2(1,3) CB1(1)
SORT2(2,3) CB2(2,3)
#undef CB1
#undef CB2
#undef DD1
#undef DD2
#undef SORT2
}
或者,如果您希望通过引用传递变量,则使用此函数。以下函数与上面的前5行不同:
template<class T> inline void sort6(T& e0, T& e1, T& e2, T& e3, T& e4, T& e5) {
#define SORT2(x,y) {if(data##x>data##y)std::swap(data##x,data##y);}
#define DD1(a) register auto data##a=e##a;
#define DD2(a,b) register auto data##a=e##a;register auto data##b=e##b;
#define CB1(a) e##a=data##a;
#define CB2(a,b) e##a=data##a;e##b=data##b;
DD2(1,2) SORT2(1,2)
DD2(4,5) SORT2(4,5)
DD1(0) SORT2(0,2)
DD1(3) SORT2(3,5)
SORT2(0,1) SORT2(3,4) SORT2(2,5) CB1(5)
SORT2(1,4) SORT2(0,3) CB1(0)
SORT2(2,4) CB1(4)
SORT2(1,3) CB1(1)
SORT2(2,3) CB2(2,3)
#undef CB1
#undef CB2
#undef DD1
#undef DD2
#undef SORT2
}
使用register关键字的原因是,这是您知道需要在寄存器中使用这些值的少数情况之一。如果没有register,编译器将在大多数情况下解决此问题,但有时无法解决。使用register关键字有助于解决此问题。但是,通常不要使用register关键字,因为它是mo您可能会减慢代码的速度,而不是加快代码的速度
另外,请注意模板的使用。这是有意的,因为即使使用内联关键字,gcc通常也会比普通C函数更积极地优化模板函数。这与gcc需要处理普通C函数的函数指针有关,但与模板函数无关
void xorSwap (int *x, int *y) {
if (*x != *y) {
*x ^= *y;
*y ^= *x;
*x ^= *y;
}
}
在对各种排序函数进行计时时,我注意到,调用排序函数的上下文(即周围代码)对性能有显著影响,这可能是由于函数被内联,然后进行了优化。例如,如果程序足够简单,则通常没有太大差异在给排序函数传递指针和给它传递迭代器之间的性能差异;否则,使用迭代器通常会导致明显更好的性能,而且在我的经验中,到目前为止,至少没有任何明显更差的性能。我怀疑这可能是因为g++可以全局优化足够简单的代码。
我知道这是个老问题 但我只是写了一种不同的解决方案,我想与大家分享。 只使用嵌套的最小值和最大值 它不是很快,因为它使用了114个, 可以简单地将其减少到75-> 但它不再是纯粹的最小-最大值 使用AVX一次对多个整数执行min/max可能有效 也许我参加聚会迟到了,但至少我的贡献是一种新的方式 代码应该是内联的 即使是内联的,分支也太多了 分析部分基本上是N-1,对于N=6,这似乎是正常的 如果互换成本更高,代码可能更有效 这不是比较的成本 我相信静态函数是内联的。 该方法与秩排序有关 使用相对列组偏移量代替列组。 在任何置换群中,每个循环的秩和为零。
不是交换两个元素,而是追踪循环,只需要一个临时值和一个寄存器->寄存器交换新值就可以对min/max进行一些位破解:@Paul:在真正的CUDA使用环境中,这肯定是最好的答案。我将检查它是否也是,以及在golf x64上下文和发布结果中是多少。无论如何,如果您注意到a+b+c-min+max是中心数字,Sort3在大多数体系结构上都会更快。@Rex:我明白了-看起来不错。对于像AltiVec和SSE这样的SIMD体系结构,最大和最小的指令周期数应该是相同的,但对于普通的标量CPU,你的方法看起来更好。如果我让GCC使用条件移动指令优化最小值,我会得到33%的加速:定义SWAPx,y{int dx=d[x],dy=d[y],tmp;tmp=d[x]=dx
.我尝试过交换,但局部优化在更大的层面上有负面影响,我想它会引入依赖关系。结果比另一种交换要慢。但正如您所看到的,新的解决方案提出后,确实有很多性能需要优化交换。共有720个订单,快速版本的周期远远低于100个。即使可以利用大规模并行性,在如此小的时间范围内,创建和同步线程的成本也可能超过在一个内核上对阵列进行排序的成本。请随意提出一些实现:-建议已添加。享受这些虫子,真的很有趣。看来无分支交换在PPC上是个坏主意。它也可能是与编译器相关的效果。使用了哪一个?它是gcc编译器的一个分支-最小、最大逻辑可能不是无分支的-我将检查反汇编并让您知道,但是除非编译器足够聪明,包括x
案例是关于11.14次比较和6次交换。我没有得到规则的随机分布——我所做的是测试每一个可能的组合,并确定最小/平均/最大统计数据。Shellsort是一系列递减增量的插入排序,因此最终增量-1所做的工作比在纯插入排序中单独执行要少得多。至于时钟计数,我的算法平均需要406个时钟周期,这包括收集统计数据和对实际排序例程进行两次调用-每个间隔一次。这是在Athlon M300移动编译器OpenWatcom上。规则随机分布意味着被排序的实际数据的每个组合的概率可能不相等。如果每个组合的概率都不相等,那么您的统计数据将被破坏,因为平均值需要考虑给定分布可能发生的次数。对于时钟计数,如果您尝试上面提供的这种排序链接的任何其他实现,并在您的测试系统上运行它,我们将有一个比较的基础,看看您选择的一个执行得有多好。很好。我将把它添加到较长的测试套件中,以便将您的测试套件与其他装配级方案进行比较。实现的比较性能不包括它们。无论如何,使用SSE听起来不错。未来的另一个研究领域将是应用新的Intel AVX指令解决这个问题。较大的256位向量足够大,可以容纳8个DWORD。使用movd代替pxor/pinsrd xmm4、mem、0!这些很好。由于这个问题已经解决了几十年,可能和C语言编程一样古老,所以这个问题现在已经有将近5年的时间了,这看起来没有多大关系。你应该看看其他答案的计时方式。关键是,对于如此小的数据集计数比较,甚至是比较和交换,实际上并不能说明算法的速度有多快,基本上排序6个整数总是O1,因为O6*6是O1。在先前提出的解决方案中,当前最快的是使用RexKerr的大型比较立即找到每个值的位置。@kris现在是最快的吗?从我对结果的阅读来看,排序网络的方法是最快的,我的不好。同样,我的解决方案来自我的泛型库,我并不总是比较整数,也不总是使用运算符。RexKerr Order Rank的解决方案成为X86体系结构上最快的解决方案,自gcc编译器4.2.3以来,gcc编译器4.9的速度几乎是次优的两倍。但这在很大程度上取决于编译器优化,在其他体系结构上可能不正确。@kris这是一个有趣的问题。而且我确实可以用-O3再次获得更多的差异。我想我会为我的排序库采用另一种策略:提供三种算法,要么比较次数少,要么交换次数少,要么性能可能最好。至少,发生的事情对读者来说是透明的。感谢您的见解:或多或少相关:我提交给GCC,以便它可以直接在编译器中实现优化。不确定它是否会完成,但至少你可以了解它是如何发展的。用例与问题的初始上下文略有不同。在固定长度的情况下,仅计算掉期的cmp是不够的。如果它根本不是消耗时间的实际类型,我甚至不会感到惊讶,而是init中完全不同的light-calling-typeof。我不知道如何使用Javascript执行实际的时钟时间测量。也许是node?总是很高兴看到新的解决方案。看起来可以进行一些简单的优化。最后,它可能与排序网络没有太大区别。是的,MIN和MAX的数量可能会减少,例如MINAB、CD会重复几次,但我认为很难减少它们。我添加了您的测试用例。pmin/maxsw对压缩的16位有符号整数int16\t进行操作。但是您的C函数声明它对int数组进行排序,在所有支持asm语法的C实现中,int数组是32位的。你有没有用只有高半部只有0的小正整数来测试它?那会有用的。。。对于int,您需要SSE4.1 pmin/maxsd d=dword。或者,uint32_t的pminusd看起来像一个冒泡排序。对于最慢的实现来说,这可能是一个很好的竞争者,但是了解代码是否会产生如此大的差异仍然是一个有趣的问题。请将您的代码放在与其他代码相同的格式上,这样我们就可以在其上运行基准测试。@kris它肯定不是冒泡排序:代码检测给定排列中的循环,并遍历这些循环,将每个元素放在其最终位置。最后一个wsort6函数具有正确的接口。@joop:my bad,no冒泡排序。尽管如此,我仍然希望代码比当前的任何其他实现都糟糕。顺便说一句,排名顺序解决方案在互换数量方面是最优的,因为它直接找到最终的pos
每一件物品的包装。当我们去掉所有排序的数字都是不同的假设时,walksort是否起作用还不清楚。为了对代码进行基准测试,我们应该跟踪代码。另外,正如我通常在C++编译器上编译的那样,代码不能工作,因为OP称为变量NeXT,它打破了语法高亮。该方法非常接近于排序,只有最终的赋值是在适当的位置完成的。除了列组o1..o5之外,不需要第二个临时e[6]数组。以及:在C++编译器上编译C代码,并将代码归咎于?@ GrayBeLeD:谢谢,我在包含之前添加了一个空间。速度固定不变你确定它能工作吗?在蛮力排序中,您的循环是可疑的。在我看来,如果排序值中有一个零,它们将不起作用。t[6]数组被初始化为0x0。因此,在何处以及是否写入0x0值的键并不重要。您可以对带强制转换的整数向量使用vmovmskps,以保持内部函数满意,避免需要右移位扫描ffs结果。您可以通过减去cmpgt结果有条件地加1,而不是使用set11屏蔽它。e、 g.索引=_mm256_sub_epi32索引,gt索引-=-1或0;eq=_mm256_insert_epi32eq,0,I不是将元素归零的有效方法,如果它按照编写的方式编译,尤其是针对low 4之外的元素,因为vpinsrd仅在XMM目标中可用;必须模拟高于3的索引。取而代之的是_mm256 _blend _epi32vpblendd和零向量。vpblendd是一条在任何端口上运行的单uop指令,而不是在Intel CPU上需要端口5的随机播放指令。此外,您可以考虑生成来自同一源的不同洗牌的RoT向量,或者至少并行运行2个DEP链,交替使用,而不是通过车道交叉洗牌3周期延迟的一个单DEP链。这将在单个排序中增加ILP。2个dep链将向量常量的数量限制为合理的数量,一个旋转仅为2:1,两个旋转步骤组合为一个。
static inline void sort6_insertion_sort_avx(int* d) {
__m256i src = _mm256_setr_epi32(d[0], d[1], d[2], d[3], d[4], d[5], 0, 0);
__m256i index = _mm256_setr_epi32(0, 1, 2, 3, 4, 5, 6, 7);
__m256i shlpermute = _mm256_setr_epi32(7, 0, 1, 2, 3, 4, 5, 6);
__m256i sorted = _mm256_setr_epi32(d[0], INT_MAX, INT_MAX, INT_MAX,
INT_MAX, INT_MAX, INT_MAX, INT_MAX);
__m256i val, gt, permute;
unsigned j;
// 8 / 32 = 2^-2
#define ITER(I) \
val = _mm256_permutevar8x32_epi32(src, _mm256_set1_epi32(I));\
gt = _mm256_cmpgt_epi32(sorted, val);\
permute = _mm256_blendv_epi8(index, shlpermute, gt);\
j = ffs( _mm256_movemask_epi8(gt)) >> 2;\
sorted = _mm256_blendv_epi8(_mm256_permutevar8x32_epi32(sorted, permute),\
val, _mm256_cmpeq_epi32(index, _mm256_set1_epi32(j)))
ITER(1);
ITER(2);
ITER(3);
ITER(4);
ITER(5);
int x[8];
_mm256_storeu_si256((__m256i*)x, sorted);
d[0] = x[0]; d[1] = x[1]; d[2] = x[2]; d[3] = x[3]; d[4] = x[4]; d[5] = x[5];
#undef ITER
}
static inline void sort6_rank_order_avx(int* d) {
__m256i ror = _mm256_setr_epi32(5, 0, 1, 2, 3, 4, 6, 7);
__m256i one = _mm256_set1_epi32(1);
__m256i src = _mm256_setr_epi32(d[0], d[1], d[2], d[3], d[4], d[5], INT_MAX, INT_MAX);
__m256i rot = src;
__m256i index = _mm256_setzero_si256();
__m256i gt, permute;
__m256i shl = _mm256_setr_epi32(1, 2, 3, 4, 5, 6, 6, 6);
__m256i dstIx = _mm256_setr_epi32(0,1,2,3,4,5,6,7);
__m256i srcIx = dstIx;
__m256i eq = one;
__m256i rotIx = _mm256_setzero_si256();
#define INC(I)\
rot = _mm256_permutevar8x32_epi32(rot, ror);\
gt = _mm256_cmpgt_epi32(src, rot);\
index = _mm256_add_epi32(index, _mm256_and_si256(gt, one));\
index = _mm256_add_epi32(index, _mm256_and_si256(eq,\
_mm256_cmpeq_epi32(src, rot)));\
eq = _mm256_insert_epi32(eq, 0, I)
INC(0);
INC(1);
INC(2);
INC(3);
INC(4);
int e[6];
e[0] = d[0]; e[1] = d[1]; e[2] = d[2]; e[3] = d[3]; e[4] = d[4]; e[5] = d[5];
int i[8];
_mm256_storeu_si256((__m256i*)i, index);
d[i[0]] = e[0]; d[i[1]] = e[1]; d[i[2]] = e[2]; d[i[3]] = e[3]; d[i[4]] = e[4]; d[i[5]] = e[5];
}
function sort4DG(cmp,start,end,n) // sort 4
{
var n = typeof(n) !=='undefined' ? n : 1;
var cmp = typeof(cmp) !=='undefined' ? cmp : sortCompare2;
var start = typeof(start)!=='undefined' ? start : 0;
var end = typeof(end) !=='undefined' ? end : arr[n].length;
var count = end - start;
var pos = -1;
var i = start;
var cc = [];
// stabilni?
cc[01] = cmp(arr[n][i+0],arr[n][i+1]);
cc[23] = cmp(arr[n][i+2],arr[n][i+3]);
if (cc[01]>0) {swap(n,i+0,i+1);}
if (cc[23]>0) {swap(n,i+2,i+3);}
cc[12] = cmp(arr[n][i+1],arr[n][i+2]);
if (!(cc[12]>0)) {return n;}
cc[02] = cc[01]==0 ? cc[12] : cmp(arr[n][i+0],arr[n][i+2]);
if (cc[02]>0)
{
swap(n,i+1,i+2); swap(n,i+0,i+1); // bubble last to top
cc[13] = cc[23]==0 ? cc[12] : cmp(arr[n][i+1],arr[n][i+3]);
if (cc[13]>0)
{
swap(n,i+2,i+3); swap(n,i+1,i+2); // bubble
return n;
}
else {
cc[23] = cc[23]==0 ? cc[12] : (cc[01]==0 ? cc[30] : cmp(arr[n][i+2],arr[n][i+3])); // new cc23 | c03 //repaired
if (cc[23]>0)
{
swap(n,i+2,i+3);
return n;
}
return n;
}
}
else {
if (cc[12]>0)
{
swap(n,i+1,i+2);
cc[23] = cc[23]==0 ? cc[12] : cmp(arr[n][i+2],arr[n][i+3]); // new cc23
if (cc[23]>0)
{
swap(n,i+2,i+3);
return n;
}
return n;
}
else {
return n;
}
}
return n;
}
#define MIN(x, y) (x<y?x:y)
#define MAX(x, y) (x<y?y:x)
template<class IterType>
inline void sort6_iterator(IterType it)
{
#define SWAP(x,y) { const auto a = MIN(*(it + x), *(it + y)); \
const auto b = MAX(*(it + x), *(it + y)); \
*(it + x) = a; *(it + y) = b; }
SWAP(1, 2) SWAP(4, 5)
SWAP(0, 2) SWAP(3, 5)
SWAP(0, 1) SWAP(3, 4)
SWAP(1, 4) SWAP(0, 3)
SWAP(2, 5) SWAP(1, 3)
SWAP(2, 4)
SWAP(2, 3)
#undef SWAP
}
template<class IterType>
inline void sort6_iterator_local(IterType it)
{
#define SWAP(x,y) { const auto a = MIN(data##x, data##y); \
const auto b = MAX(data##x, data##y); \
data##x = a; data##y = b; }
//DD = Define Data
#define DD1(a) auto data##a = *(it + a);
#define DD2(a,b) auto data##a = *(it + a), data##b = *(it + b);
//CB = Copy Back
#define CB(a) *(it + a) = data##a;
DD2(1,2) SWAP(1, 2)
DD2(4,5) SWAP(4, 5)
DD1(0) SWAP(0, 2)
DD1(3) SWAP(3, 5)
SWAP(0, 1) SWAP(3, 4)
SWAP(1, 4) SWAP(0, 3) CB(0)
SWAP(2, 5) CB(5)
SWAP(1, 3) CB(1)
SWAP(2, 4) CB(4)
SWAP(2, 3) CB(2) CB(3)
#undef CB
#undef DD2
#undef DD1
#undef SWAP
}
#define SWAP(x,y) { const auto a = MIN(data##x, data##y); \
data##y = MAX(data##x, data##y); \
data##x = a; }
template<class T> inline void sort6(T it) {
#define SORT2(x,y) {if(data##x>data##y){auto a=std::move(data##y);data##y=std::move(data##x);data##x=std::move(a);}}
#define DD1(a) register auto data##a=*(it+a);
#define DD2(a,b) register auto data##a=*(it+a);register auto data##b=*(it+b);
#define CB1(a) *(it+a)=data##a;
#define CB2(a,b) *(it+a)=data##a;*(it+b)=data##b;
DD2(1,2) SORT2(1,2)
DD2(4,5) SORT2(4,5)
DD1(0) SORT2(0,2)
DD1(3) SORT2(3,5)
SORT2(0,1) SORT2(3,4) SORT2(2,5) CB1(5)
SORT2(1,4) SORT2(0,3) CB1(0)
SORT2(2,4) CB1(4)
SORT2(1,3) CB1(1)
SORT2(2,3) CB2(2,3)
#undef CB1
#undef CB2
#undef DD1
#undef DD2
#undef SORT2
}
template<class T> inline void sort6(T& e0, T& e1, T& e2, T& e3, T& e4, T& e5) {
#define SORT2(x,y) {if(data##x>data##y)std::swap(data##x,data##y);}
#define DD1(a) register auto data##a=e##a;
#define DD2(a,b) register auto data##a=e##a;register auto data##b=e##b;
#define CB1(a) e##a=data##a;
#define CB2(a,b) e##a=data##a;e##b=data##b;
DD2(1,2) SORT2(1,2)
DD2(4,5) SORT2(4,5)
DD1(0) SORT2(0,2)
DD1(3) SORT2(3,5)
SORT2(0,1) SORT2(3,4) SORT2(2,5) CB1(5)
SORT2(1,4) SORT2(0,3) CB1(0)
SORT2(2,4) CB1(4)
SORT2(1,3) CB1(1)
SORT2(2,3) CB2(2,3)
#undef CB1
#undef CB2
#undef DD1
#undef DD2
#undef SORT2
}
#include <stdio.h>
static __inline__ int MIN(int a, int b){
int result =a;
__asm__ ("pminsw %1, %0" : "+x" (result) : "x" (b));
return result;
}
static __inline__ int MAX(int a, int b){
int result = a;
__asm__ ("pmaxsw %1, %0" : "+x" (result) : "x" (b));
return result;
}
static __inline__ unsigned long long rdtsc(void){
unsigned long long int x;
__asm__ volatile (".byte 0x0f, 0x31" :
"=A" (x));
return x;
}
#define MIN3(a, b, c) (MIN(MIN(a,b),c))
#define MIN4(a, b, c, d) (MIN(MIN(a,b),MIN(c,d)))
static __inline__ void sort6(int * in) {
const int A=in[0], B=in[1], C=in[2], D=in[3], E=in[4], F=in[5];
in[0] = MIN( MIN4(A,B,C,D),MIN(E,F) );
const int
AB = MAX(A, B),
AC = MAX(A, C),
AD = MAX(A, D),
AE = MAX(A, E),
AF = MAX(A, F),
BC = MAX(B, C),
BD = MAX(B, D),
BE = MAX(B, E),
BF = MAX(B, F),
CD = MAX(C, D),
CE = MAX(C, E),
CF = MAX(C, F),
DE = MAX(D, E),
DF = MAX(D, F),
EF = MAX(E, F);
in[1] = MIN4 (
MIN4( AB, AC, AD, AE ),
MIN4( AF, BC, BD, BE ),
MIN4( BF, CD, CE, CF ),
MIN3( DE, DF, EF)
);
const int
ABC = MAX(AB,C),
ABD = MAX(AB,D),
ABE = MAX(AB,E),
ABF = MAX(AB,F),
ACD = MAX(AC,D),
ACE = MAX(AC,E),
ACF = MAX(AC,F),
ADE = MAX(AD,E),
ADF = MAX(AD,F),
AEF = MAX(AE,F),
BCD = MAX(BC,D),
BCE = MAX(BC,E),
BCF = MAX(BC,F),
BDE = MAX(BD,E),
BDF = MAX(BD,F),
BEF = MAX(BE,F),
CDE = MAX(CD,E),
CDF = MAX(CD,F),
CEF = MAX(CE,F),
DEF = MAX(DE,F);
in[2] = MIN( MIN4 (
MIN4( ABC, ABD, ABE, ABF ),
MIN4( ACD, ACE, ACF, ADE ),
MIN4( ADF, AEF, BCD, BCE ),
MIN4( BCF, BDE, BDF, BEF )),
MIN4( CDE, CDF, CEF, DEF )
);
const int
ABCD = MAX(ABC,D),
ABCE = MAX(ABC,E),
ABCF = MAX(ABC,F),
ABDE = MAX(ABD,E),
ABDF = MAX(ABD,F),
ABEF = MAX(ABE,F),
ACDE = MAX(ACD,E),
ACDF = MAX(ACD,F),
ACEF = MAX(ACE,F),
ADEF = MAX(ADE,F),
BCDE = MAX(BCD,E),
BCDF = MAX(BCD,F),
BCEF = MAX(BCE,F),
BDEF = MAX(BDE,F),
CDEF = MAX(CDE,F);
in[3] = MIN4 (
MIN4( ABCD, ABCE, ABCF, ABDE ),
MIN4( ABDF, ABEF, ACDE, ACDF ),
MIN4( ACEF, ADEF, BCDE, BCDF ),
MIN3( BCEF, BDEF, CDEF )
);
const int
ABCDE= MAX(ABCD,E),
ABCDF= MAX(ABCD,F),
ABCEF= MAX(ABCE,F),
ABDEF= MAX(ABDE,F),
ACDEF= MAX(ACDE,F),
BCDEF= MAX(BCDE,F);
in[4]= MIN (
MIN4( ABCDE, ABCDF, ABCEF, ABDEF ),
MIN ( ACDEF, BCDEF )
);
in[5] = MAX(ABCDE,F);
}
int main(int argc, char ** argv) {
int d[6][6] = {
{1, 2, 3, 4, 5, 6},
{6, 5, 4, 3, 2, 1},
{100, 2, 300, 4, 500, 6},
{100, 2, 3, 4, 500, 6},
{1, 200, 3, 4, 5, 600},
{1, 1, 2, 1, 2, 1}
};
unsigned long long cycles = rdtsc();
for (int i = 0; i < 6; i++) {
sort6(d[i]);
}
cycles = rdtsc() - cycles;
printf("Time is %d\n", (unsigned)cycles);
for (int i = 0; i < 6; i++) {
printf("d%d : %d %d %d %d %d %d\n", i,
d[i][0], d[i][1], d[i][2],
d[i][3], d[i][4], d[i][5]);
}
}
static void sort6(int *o) {
const int
A=o[0],B=o[1],C=o[2],D=o[3],E=o[4],F=o[5];
const unsigned char
AB = A>B, AC = A>C, AD = A>D, AE = A>E,
BC = B>C, BD = B>D, BE = B>E,
CD = C>D, CE = C>E,
DE = D>E,
a = AB + AC + AD + AE + (A>F),
b = 1 - AB + BC + BD + BE + (B>F),
c = 2 - AC - BC + CD + CE + (C>F),
d = 3 - AD - BD - CD + DE + (D>F),
e = 4 - AE - BE - CE - DE + (E>F);
o[a]=A; o[b]=B; o[c]=C; o[d]=D; o[e]=E;
o[15-a-b-c-d-e]=F;
}
#include <stdio.h>
#if WANT_CHAR
typedef signed char Dif;
#else
typedef signed int Dif;
#endif
static int walksort (int *arr, int cnt);
static void countdifs (int *arr, Dif *dif, int cnt);
static void calcranks(int *arr, Dif *dif);
int wsort6(int *arr);
void do_print_a(char *msg, int *arr, unsigned cnt)
{
fprintf(stderr,"%s:", msg);
for (; cnt--; arr++) {
fprintf(stderr, " %3d", *arr);
}
fprintf(stderr,"\n");
}
void do_print_d(char *msg, Dif *arr, unsigned cnt)
{
fprintf(stderr,"%s:", msg);
for (; cnt--; arr++) {
fprintf(stderr, " %3d", (int) *arr);
}
fprintf(stderr,"\n");
}
static void inline countdifs (int *arr, Dif *dif, int cnt)
{
int top, bot;
for (top = 0; top < cnt; top++ ) {
for (bot = 0; bot < top; bot++ ) {
if (arr[top] < arr[bot]) { dif[top]--; dif[bot]++; }
}
}
return ;
}
/* Copied from RexKerr ... */
static void inline calcranks(int *arr, Dif *dif){
dif[0] = (arr[0]>arr[1])+(arr[0]>arr[2])+(arr[0]>arr[3])+(arr[0]>arr[4])+(arr[0]>arr[5]);
dif[1] = -1+ (arr[1]>=arr[0])+(arr[1]>arr[2])+(arr[1]>arr[3])+(arr[1]>arr[4])+(arr[1]>arr[5]);
dif[2] = -2+ (arr[2]>=arr[0])+(arr[2]>=arr[1])+(arr[2]>arr[3])+(arr[2]>arr[4])+(arr[2]>arr[5]);
dif[3] = -3+ (arr[3]>=arr[0])+(arr[3]>=arr[1])+(arr[3]>=arr[2])+(arr[3]>arr[4])+(arr[3]>arr[5]);
dif[4] = -4+ (arr[4]>=arr[0])+(arr[4]>=arr[1])+(arr[4]>=arr[2])+(arr[4]>=arr[3])+(arr[4]>arr[5]);
dif[5] = -(dif[0]+dif[1]+dif[2]+dif[3]+dif[4]);
}
static int walksort (int *arr, int cnt)
{
int idx, src,dst, nswap;
Dif difs[cnt];
#if WANT_REXK
calcranks(arr, difs);
#else
for (idx=0; idx < cnt; idx++) difs[idx] =0;
countdifs(arr, difs, cnt);
#endif
calcranks(arr, difs);
#define DUMP_IT 0
#if DUMP_IT
do_print_d("ISteps ", difs, cnt);
#endif
nswap = 0;
for (idx=0; idx < cnt; idx++) {
int newval;
int step,cyc;
if ( !difs[idx] ) continue;
newval = arr[idx];
cyc = 0;
src = idx;
do {
int oldval;
step = difs[src];
difs[src] =0;
dst = src + step;
cyc += step ;
if(dst == idx+1)idx=dst;
oldval = arr[dst];
#if (DUMP_IT&1)
fprintf(stderr, "[Nswap=%d] Cyc=%d Step=%2d Idx=%d Old=%2d New=%2d #### Src=%d Dst=%d[%2d]->%2d <-- %d\n##\n"
, nswap, cyc, step, idx, oldval, newval
, src, dst, difs[dst], arr[dst]
, newval );
do_print_a("Array ", arr, cnt);
do_print_d("Steps ", difs, cnt);
#endif
arr[dst] = newval;
newval = oldval;
nswap++;
src = dst;
} while( cyc);
}
return nswap;
}
/*************/
int wsort6(int *arr)
{
return walksort(arr, 6);
}
//Bruteforce compute unrolled count dumbsort(min to 0-index)
void bcudc_sort6(int* a)
{
int t[6] = {0};
int r1,r2;
r1=0;
r1 += (a[0] > a[1]);
r1 += (a[0] > a[2]);
r1 += (a[0] > a[3]);
r1 += (a[0] > a[4]);
r1 += (a[0] > a[5]);
while(t[r1]){r1++;}
t[r1] = a[0];
r2=0;
r2 += (a[1] > a[0]);
r2 += (a[1] > a[2]);
r2 += (a[1] > a[3]);
r2 += (a[1] > a[4]);
r2 += (a[1] > a[5]);
while(t[r2]){r2++;}
t[r2] = a[1];
r1=0;
r1 += (a[2] > a[0]);
r1 += (a[2] > a[1]);
r1 += (a[2] > a[3]);
r1 += (a[2] > a[4]);
r1 += (a[2] > a[5]);
while(t[r1]){r1++;}
t[r1] = a[2];
r2=0;
r2 += (a[3] > a[0]);
r2 += (a[3] > a[1]);
r2 += (a[3] > a[2]);
r2 += (a[3] > a[4]);
r2 += (a[3] > a[5]);
while(t[r2]){r2++;}
t[r2] = a[3];
r1=0;
r1 += (a[4] > a[0]);
r1 += (a[4] > a[1]);
r1 += (a[4] > a[2]);
r1 += (a[4] > a[3]);
r1 += (a[4] > a[5]);
while(t[r1]){r1++;}
t[r1] = a[4];
r2=0;
r2 += (a[5] > a[0]);
r2 += (a[5] > a[1]);
r2 += (a[5] > a[2]);
r2 += (a[5] > a[3]);
r2 += (a[5] > a[4]);
while(t[r2]){r2++;}
t[r2] = a[5];
a[0]=t[0];
a[1]=t[1];
a[2]=t[2];
a[3]=t[3];
a[4]=t[4];
a[5]=t[5];
}
static __inline__ void sort6(int* a)
{
#define wire(x,y); t = a[x] ^ a[y] ^ ( (a[x] ^ a[y]) & -(a[x] < a[y]) ); a[x] = a[x] ^ t; a[y] = a[y] ^ t;
register int t;
wire( 0, 1); wire( 2, 3); wire( 4, 5);
wire( 3, 5); wire( 0, 2); wire( 1, 4);
wire( 4, 5); wire( 2, 3); wire( 0, 1);
wire( 3, 4); wire( 1, 2);
wire( 2, 3);
#undef wire
}