固定长度为6的整型数组中最快的排序方法

418

回答另一个Stack Overflow的问题(this one),我偶然发现了一个有趣的子问题。如何最快地对包含6个整数的数组进行排序?

由于这个问题非常低级:

  • 我们不能假定库是可用的(而且调用本身也有成本),只能使用纯C。
  • 为了避免清空指令流水线(这是一种非常高的成本),我们应该尽可能减少分支、跳转和其他任何控制流中断(例如在&&||中隐藏的那些)。
  • 空间受限,最小化寄存器和内存使用是一个问题,最好采用原地排序。

实际上,这个问题是一种高尔夫游戏,目标不是使源长度最小化,而是执行时间最短。我称之为“禅”的代码,正如Michael Abrash的书《Zen of Code optimization》及其sequels中所使用的标题。

至于为什么它很有趣,有几个层次:

  • 这个例子简单易懂,容易衡量,不需要太多的C语言技巧
  • 它展示了选择一个好的算法对问题的影响,也展示了编译器和底层硬件的影响。

这是我的参考(天真的,未经优化的)实现和测试集。

#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);
}

原始结果

由于变量数量越来越多,我将它们全部收集到一个测试套件中,可以在这里找到。实际使用的测试比上面展示的要聪明一些,感谢Kevin Stock。您可以在自己的环境中编译和执行它。我对不同目标架构/编译器的行为非常感兴趣。(好的,伙计们,把它放在答案中,我会+1新结果集的每个贡献者)。

我一年前向Daniel Stutzbach(为了高尔夫)给出了答案,因为他当时是最快解决方案的源头(排序网络)。

Linux 64位,gcc 4.6.1 64位,Intel Core 2 Duo E8400,-O2

  • 直接调用qsort库函数:689.38
  • 朴素实现(插入排序):285.70
  • 插入排序(Daniel Stutzbach):142.12
  • 展开的插入排序:125.47
  • 排名排序:102.26
  • 寄存器排名排序:58.03
  • 排序网络(Daniel Stutzbach):111.68
  • 排序网络(Paul R):66.36
  • 带快速交换的排序网络12:58.86
  • 重排的排序网络12交换:53.74
  • 重排的简单交换排序网络12:31.54
  • 带快速交换的重排排序网络:31.54
  • 带快速交换的重排排序网络V2:33.63
  • 内联冒泡排序(Paolo Bonzini):48.85
  • 展开的插入排序(Paolo Bonzini):75.30

Linux 64位,gcc 4.6.1 64位,Intel Core 2 Duo E8400,-O1

  • 调用qsort库函数:705.93
  • 朴素实现(插入排序):135.60
  • 插入排序(Daniel Stutzbach):142.11
  • 展开的插入排序:126.75
  • 等级排序:46.42
  • 带寄存器的等级排序:43.58
  • 排序网络(Daniel Stutzbach):115.57
  • 排序网络(Paul R):64.44
  • 使用快速交换的12位排序网络:61.98
  • 重新排序的12位排序网络交换:54.67
  • 重新排序的12位排序网络简单交换:31.54
  • 带快速交换的重新排序排序网络:31.24
  • 带快速交换的重新排序排序网络V2:33.07
  • 内联冒泡排序(Paolo Bonzini):45.79
  • 展开的插入排序(Paolo Bonzini):80.15

我包含了-O1和-O2的结果,因为令人惊讶的是对于几个程序,O2比O1效率更低。我想知道具体哪种优化会产生这种影响?

关于提出的解决方案的评论

插入排序(Daniel Stutzbach)

如预期的那样,最小化分支确实是一个好主意。

排序网络(Daniel Stutzbach)

比插入排序更好。我想知道主要效果是否不是通过避免外部循环获得的。我尝试了展开插入排序以进行检查,确实我们得到了大致相同的数据(代码在这里)。
排序网络(Paul R)
到目前为止最好的。我用来测试的实际代码在这里。还不知道为什么它几乎比其他排序网络实现快两倍。参数传递?快速最大值?
带快速交换的12交换排序网络
正如Daniel Stutzbach建议的那样,我将他的12个交换排序网络与无分支快速交换组合在一起(代码在这里)。确实更快,迄今为止具有小幅度的优势(大约5%),因为使用1次较少的交换是可以预期的。
另外,有趣的是,分支交换似乎比在PPC架构上使用if的简单交换效率低得多(4倍)。
调用库qsort
为了再给一个参考点,我也试着按建议调用库函数qsort(代码在这里)。正如预期的那样,它慢得多:10到30倍……随着新的测试套件的出现,主要问题似乎是第一次调用后库的初始加载,而且与其他版本相比表现并不差。在我的Linux上,它只比其他版本慢3到20倍。在其他人测试时使用的某些架构上,它甚至似乎更快(我对这个结果感到非常惊讶,因为库函数qsort使用了更复杂的API)。 排名顺序 Rex Kerr提出了另一种完全不同的方法:针对数组的每个项直接计算其最终位置。这很有效,因为计算排名顺序不需要分支。但该方法的缺点是需要数组大小三倍的内存(一个数组副本和变量来存储排名顺序)。性能结果非常令人惊讶(也很有趣)。在我的32位操作系统和Intel Core2 Quad E8300参考架构上,循环计数略低于1000(像带分支交换的排序网络一样)。但是,当在我的64位系统(Intel Core2 Duo)上编译和执行时,它表现得更好:到目前为止,它成为最快的。我最终找到了真正的原因。我的32位系统使用gcc 4.4.1,而我的64位系统使用gcc 4.4.3,后者在优化这个特定代码方面要好得多(对其他提案几乎没有什么区别)。 更新: 如上面发布的数字表明,这个效果仍然受到后来版本的gcc的增强,排名顺序变得比任何其他替代方案都快两倍。

重新排序的交换排序网络12

Rex Kerr提议使用gcc 4.4.3实现的惊人效率让我想知道:一个内存使用量是无分支排序网络三倍的程序怎么可能更快呢?我的假设是它具有更少的读写依赖关系,从而允许更好地利用x86的超标量指令调度器。这给了我一个想法:重新排序交换以最小化读写依赖关系。简单来说,当你执行SWAP(1,2); SWAP(0,2);时,必须等待第一个交换完成后才能执行第二个,因为两个都访问同一内存单元。当你执行SWAP(1,2); SWAP(4,5);时,处理器可以并行执行两个操作。我尝试了一下,效果符合预期,排序网络运行速度提高了约10%。

简单交换排序网络12

在原始帖子发表一年后,Steinar H. Gunderson建议我们不要试图聪明地超越编译器,保持交换代码的简单性。这确实是个好主意,因为结果代码大约快了40%!他还提出了一种优化手动使用x86内联汇编代码的交换方法,可以节省更多的时间。最令人惊讶的是(它表明了程序员心理的很多东西),一年前我们都没有尝试过那个版本的交换。我用来测试的代码在这里。其他人建议以其他方式编写C快速交换,但使用合适的编译器时,它们与简单的方法产生相同的性能。

现在,“最佳”代码如下:

static inline void sort6_sorting_network_simple_swap(int * d){
#define min(x, y) (x<y?x:y)
#define max(x, y) (x<y?y:x) 
#define SWAP(x,y) { const int a = min(d[x], d[y]); \
                    const int b = max(d[x], d[y]); \
                    d[x] = a; d[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
#undef min
#undef max
}

如果我们相信我们的测试集(是的,它相当差,它的唯一好处是简短、简单并且易于理解我们正在测量的内容),则一个排序的结果代码的平均循环次数低于40个周期(执行了6个测试)。这使得每个交换的平均时间为4个周期。我认为这非常快。还有其他改进可能吗?

2
你对整数有一些限制吗?例如,我们可以假设任何两个x、y的x-yx+y不会导致下溢或上溢吗? - Matthieu M.
3
你应该尝试将我的12次交换排序网络与Paul的无分支交换函数结合起来。他的解决方案将所有参数作为独立元素传递到堆栈上,而不是传递一个指向数组的单个指针。这可能也会有所不同。 - Daniel Stutzbach
2
请注意,在64位上正确实现rdtsc的方法是__asm__ volatile (".byte 0x0f, 0x31; shlq $32, %%rdx; orq %%rdx, %0" : "=a" (x) : : "rdx");,因为rdtsc将答案放在EDX:EAX中,而GCC希望它在一个64位寄存器中。您可以通过在-O3下编译来查看错误。另请参见我对Paul R关于更快速的SWAP的评论。 - Paolo Bonzini
3
@Tyler: 在汇编语言中,如果没有使用分支指令,你如何实现它? - Loren Pechtel
4
@Loren说:CMP EAX, EBX; SBB EAX, EAX会根据EAXEBX的大小关系,在EAX中放入0或0xFFFFFFFF。 SBB是"带借位减法",与ADC("带进位加法")相对应; 您所指的状态位 就是 进位位。然而,我记得在 Pentium 4 上,ADCSBB的延迟和吞吐量很差,与ADDSUB相比仍然慢两倍,并且在Core CPU上也是如此。自从80386以来,还有SETcc条件存储和CMOVcc条件移动指令,但它们也很慢。 - j_random_hacker
显示剩余37条评论
25个回答

0

也许我来晚了,但至少我的贡献是一种新的方法。

  • 代码应该真正地内联
  • 即使内联,分支太多
  • 分析部分基本上是O(N(N-1)),对于N=6似乎还可以接受
  • 如果swap的成本比compare高,那么代码可能会更有效
  • 我相信静态函数会被内联。
  • 该方法与排名排序有关
    • 使用相对排名(偏移量)而不是排名。
    • 每个置换群中的每个循环的排名总和为零。
    • 追踪循环而不是交换两个元素,只需要一个临时变量和一个(寄存器->寄存器)交换(新->旧)。

更新:代码稍作修改,有些人使用C++编译器来编译C代码...
#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);
}

看起来像是冒泡排序。可能是最慢的实现之一,但如果在代码上工作会有多大的差异仍然值得关注。请将您的代码放在与其他人相同的格式中,这样我们就可以对其进行基准测试。 - kriss
@kriss https://zh.wikipedia.org/wiki/%E6%8E%92%E5%88%97%E7%BB%84 这绝对不是冒泡排序:代码检测给定排列中的循环,并遍历这些循环,将每个元素放在其最终位置。 最终的 wsort6() 函数具有正确的接口。 - joop
@joop:我的错,确实没有冒泡排序。话虽如此,在这种情况下,我仍然期望代码比任何其他当前实现都要糟糕得多。顺便说一句,排名顺序解决方案在交换数量方面是最优的,因为它直接找到了每个项目的最终位置。当我们删除所有排序数字都不同的假设时,walksort甚至是否有效并不清楚。为了对代码进行基准测试,我们应该使用跟踪代码。另外,由于我通常在C++编译器上编译,所以代码不会起作用,因为OP调用了一个变量“new”(这破坏了语法高亮)。 - kriss
这种方法非常接近于排名顺序,只有最终的分配是原地完成的。除了排名o1..o5之外,没有必要使用第二个临时数组e[6]。还有:在C++编译器上编译C代码,然后责怪代码? - joop
@greybeard:谢谢,我在#include之前加了一个空格。问题解决了。 - wildplasser
1
你的代码缩进真是与众不同(例如,尝试使用indent(1)来生成它):你从哪里得到的? - greybeard

0
//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
}

无论速度如何,你确定它能正常工作吗?在暴力排序中,你的循环有些可疑。如果我们在排序后的值中有一个零,它们似乎不会起作用。 - kriss
1
t[6] 数组被初始化为 0x0。因此,无论在哪里或是否写入一个值为 0x0 的键都没有关系。 - FranG

0

尝试使用“合并排序列表”排序。 :) 使用两个数组。对于小型和大型数组,速度最快。
如果您进行连接,则仅检查插入位置。您不需要比较其他更大的值(cmp = a-b>0)。
对于4个数字,您可以使用系统4-5 cmp(~4.6)或3-6 cmp(~4.9)。冒泡排序使用6 cmp(6)。对于大数字,许多cmp会使代码变慢。
此代码使用5 cmp(非MSL排序):

if (cmp(arr[n][i+0],arr[n][i+1])>0) {swap(n,i+0,i+1);}
if (cmp(arr[n][i+2],arr[n][i+3])>0) {swap(n,i+2,i+3);}
if (cmp(arr[n][i+0],arr[n][i+2])>0) {swap(n,i+0,i+2);}
if (cmp(arr[n][i+1],arr[n][i+3])>0) {swap(n,i+1,i+3);}
if (cmp(arr[n][i+1],arr[n][i+2])>0) {swap(n,i+1,i+2);}`

主要 MSL

9 8 7 6 5 4 3 2 1 0
89 67 45 23 01 ... concat two sorted lists, list length = 1
6789 2345 01 ... concat two sorted lists, list length = 2
23456789 01 ... concat two sorted lists, list length = 4
0123456789 ... concat two sorted lists, list length = 8

JS 代码

function sortListMerge_2a(cmp)  
{
var step, stepmax, tmp, a,b,c, i,j,k, m,n, cycles;
var start = 0;
var end   = arr_count;
//var str = '';
cycles = 0;
if (end>3)
    {
    stepmax = ((end - start + 1) >> 1) << 1;
    m = 1;
    n = 2;
    for (step=1;step<stepmax;step<<=1)  //bounds 1-1, 2-2, 4-4, 8-8...
        {
        a = start;
        while (a<end)
            {
            b = a + step;
            c = a + step + step;
            b = b<end ? b : end;
            c = c<end ? c : end;
            i = a;
            j = b;
            k = i;
            while (i<b && j<c)
                {
                if (cmp(arr[m][i],arr[m][j])>0)
                    {arr[n][k] = arr[m][j]; j++; k++;}
                else    {arr[n][k] = arr[m][i]; i++; k++;}
                }
            while (i<b)
                {arr[n][k] = arr[m][i]; i++; k++;
}
            while (j<c)
                {arr[n][k] = arr[m][j]; j++; k++;
}
            a = c;
            }
        tmp = m; m = n; n = tmp;
        }
    return m;
    }
else
    {
    // sort 3 items
    sort10(cmp);
    return m;
    }
}

-1

如果只有6个元素,并且您可以利用并行性,想要最小化条件分支等。为什么不生成所有组合并测试顺序呢?我敢说在某些架构中,它可能非常快(只要您预先分配了内存)


9
共有720种排序方式,其中快速版本的时间远低于100个周期。即使利用大规模并行计算,由于时间规模太小,创建和同步线程的成本很可能超过了在一个核心上对数组进行排序的成本。 - Kevin Stock

-1

使用cmp==0对4个项目进行排序。 cmp的数量约为4.34(FF本机约为4.52),但比合并列表需要3倍的时间。但是,如果您有大量数字或大量文本,则最好减少cmp操作。 编辑:修复了错误

在线测试http://mlich.zam.slu.cz/js-sort/x-sort-x2.htm

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;
}

1
使用情况与问题的初始上下文略有不同。对于固定长度排序,细节很重要,计算交换的cmp是不够的。我甚至不会感到惊讶,如果实际上消耗时间的不是排序本身,而是在init中调用typeof()的完全不同的东西。我不知道如何使用Javascript进行实际的时钟时间测量。也许可以使用node? - kriss

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接