C/C++常用排序性能测试(Morn、GSL、qsort、std::sort)
简介
Morn是一个C语言的基础工具和基础算法库,包括数据结构、图像处理、音频处理、机器学习等,具有简单、通用、高效的特点。
GSL(GNU Scientific Library) 是一个用于科学计算的 C 语言类库。
qsort是C语言自带的排序函数,定义在stdlib.h里。
stl(Standard Template Library)C++标准模板库。
性能测试程序源码为:test_sort2.cpp。编译使用以下命令:
g++ -O2 -fopenmp -DNDEBUG test_sort2.cpp -o test_sort2.exe -lgsl -lgslcblas -lmorn
排序性能
这里,把Morn的排序与另外三种排序方法做了对比,它们分别是:①C语言标准库函数里的qsort
函数,②科学计算库GSL里的gsl_sort
函数,③C++ STL里的std::sort
函数。测试程序如下:
#include <algorithm>
#include <gsl/gsl_sort_double.h>
#include "morn_math.h"
int compare(const void *v1, const void *v2) {return ((*((double *)v1))>(*((double *)v2)))?1:-1;}
void test1()
{
double *data1= (double *)mMalloc(10000000* sizeof(double));
double *data2= (double *)mMalloc(10000000* sizeof(double));
double *data3= (double *)mMalloc(10000000* sizeof(double));
double *data4= (double *)mMalloc(10000000* sizeof(double));
for(int n=1000;n<=10000000;n*=10)
{
printf("\n%d data sort for %d times:\n",n,10000000/n);
for(int i=0;i<10000000;i++)
{
data1[i]=((double)mRand(-10000000,10000000))/((double)mRand(1,10000));
data2[i]=data1[i];data3[i]=data1[i];data4[i]=data1[i];
}
mTimerBegin("qsort");
for(int i=0;i<10000000;i+=n) qsort(data1+i,n,sizeof(double),compare);
mTimerEnd("qsort");
mTimerBegin("gsl");
for(int i=0;i<10000000;i+=n) gsl_sort(data2+i,1,n);
mTimerEnd("gsl");
mTimerBegin("stl");
for(int i=0;i<10000000;i+=n) std::sort(data3+i,data3+i+n);
mTimerEnd("stl");
mTimerBegin("Morn");
for(int i=0;i<10000000;i+=n) mAscSort(data4+i,n);
mTimerEnd("Morn");
}
mFree(data1); mFree(data2); mFree(data3); mFree(data4);
}
以上对随机生成的双精度浮点:①1000个数据排序,计时10000次;②10000个数据排序,计时1000次;③100000个数据排序,计时100次;④1000000个数据排序,计时10次;⑤10000000个数据排序,计时1次。其运行结果如下:
可以看到:std::sort
和Morn的mAscSort
速度最快(std::sort
比mAscSort
稍快,差距约2%),在排序数据量小时gsl_sort
快于qsort
,在数据量大时qsort
快于gsl_sort
。
带索引排序的性能
这里比较的是Morn的mAscSort
和GSL的gsl_sort_index
函数。测试程序如下:
void test2()
{
double *data1 = (double *)mMalloc(10000000* sizeof(double));
double *data2 = (double *)mMalloc(10000000* sizeof(double));
size_t *index1= (size_t *)mMalloc(10000000* sizeof(size_t));
int *index2= (int *)mMalloc(10000000* sizeof(int ));
for(int n=1000;n<=10000000;n*=10)
{
printf("\n%d data sort with index for %d times:\n",n,10000000/n);
for(int i=0;i<10000000;i++)
{
data1[i]=((double)mRand(-10000000,10000000))/((double)mRand(1,10000));
data2[i]=data1[i];
}
mTimerBegin("gsl");
for(int i=0;i<10000000;i+=n) gsl_sort_index(index1,data1+i,1,n);
mTimerEnd("gsl");
mTimerBegin("Morn");
for(int i=0;i<10000000;i+=n) mAscSort(data2+i,NULL,index2,n);
mTimerEnd("Morn");
}
mFree(data1); mFree(data2);mFree(index1);mFree(index2);
}
以上对随机生成的双精度浮点:①1000个数据排序,计时10000次;②10000个数据排序,计时1000次;③100000个数据排序,计时100次;④1000000个数据排序,计时10次;⑤10000000个数据排序,计时1次。其运行结果如下:
显然:相比GSL,Morn的排序更快。且随着数据量的增加,差距拉大。
这里gsl_sort_index
与mAscSort
函数功能有区别。gsl_sort_index
只输出排序后的索引,对数据不会排序,mAscSort
对数据和索引都会排序。
最大最小值子集的性能
首先比较Morn的mMinSubset
函数和stl的std::nth_element
函数。测试程序如下:
void test3_1()
{
double *data1= (double *)mMalloc(10000000*sizeof(double));
double *data2= (double *)mMalloc(10000000*sizeof(double));
for(int n=100000;n<=10000000;n*=10)
for(int m=n/10;m<n;m+=n/5)
{
printf("\nselect %d from %d data for %d times\n",m,n,10000000/n);
for(int i=0;i<10000000;i++)
{
data1[i]=((double)mRand(-1000000,1000000))/((double)mRand(1,1000));
data2[i]=data1[i];
}
mTimerBegin("stl");
for(int i=0;i<10000000;i+=n) std::nth_element(data1+i,data1+i+m-1,data1+i+n);
mTimerEnd("stl");
mTimerBegin("Morn");
for(int i=0;i<10000000;i+=n) mMinSubset(data2+i,n,m);
mTimerEnd("Morn");
}
mFree(data1);mFree(data2);
}
以上对随机生成的双精度浮点:①从100000数据中分别择出10000、30000、50000、70000、90000个最小子集,计时100次;②从1000000数据中分别择出100000、300000、500000、700000、900000个最小子集,计时10次;③从10000000数据中分别择出1000000、3000000、5000000、7000000、9000000个最小子集,计时1次;
测试结果如下:
可见,mMinSubset
与std::nth_element
性能基本处于同一水平。
这里mMinSubset
与std::nth_element
函数功能有细微差别,在解决top n问题时,两者输出的最小子集都是无序的,但是std::nth_element
会将临界值写入数组的位置n,而mMinSubset
不会将临界值写入位置n,而是以返回值的形式输出。
其次比较Morn的mMinSubset
函数和GSL库的gsl_sort_smallest
函数。测试程序如下:
void test3_2()
{
int n=1000000;int m;
double *in = (double *)mMalloc(n * sizeof(double));
double *out1= (double *)mMalloc(n * sizeof(double));
double *out2= (double *)mMalloc(n * sizeof(double));
for (int i=0;i<n;i++) in[i] = ((double)mRand(-10000,10000))/10000.0;
for(m=100000;m<n;m+=200000)
{
printf("\nselect %d from %d data\n",m,n);
mTimerBegin("gsl" ); gsl_sort_smallest(out1,m,in,1,n); mTimerEnd("gsl" );
mTimerBegin("Morn"); mMinSubset(in,n,out2,m); mTimerEnd("Morn");
}
mFree(in); mFree(out1); mFree(out2);
}
这里,从1000000个随机生成的double数据中,分别取出100000、300000、500000、700000、900000个数据,
可以看到,两者的耗时差距巨大。
不过这里需要说明的是:gsl_sort_smallest
和mMinSubset
函数的功能不完全相同,它们虽然都是从m个数据中取出最小的n个,但是gsl_sort_smallest
取出的数据是按照顺序排列好的,其取值的阈值就是out1[n]
;mMinSubset
取出的数据是无序的,其取值的阈值是函数的返回值。(gsl_sort_smallest
的功能更类似std::partial_sort
)。
带索引的最大最小值子集的性能
这里比较了Morn的mMaxSubset
函数和GSL的gsl_sort_largest_index
函数,测试程序如下:
void test4()
{
int n=1000000;int m;
double *in = (double *)mMalloc(n * sizeof(double));
size_t *out1= (size_t *)mMalloc(n * sizeof(size_t));
int *out2= (int *)mMalloc(n * sizeof(int ));
for (int i=0;i<n;i++) in[i] = ((double)mRand(-10000,10000))/10000.0;
for(m=100000;m<n;m+=200000)
{
printf("\nselect %d from %d data with index\n",m,n);
mTimerBegin("gsl" ); gsl_sort_largest_index(out1,m,in,1,n); mTimerEnd("gsl" );
mTimerBegin("Morn"); mMaxSubset(in,n,NULL,out2,m); mTimerEnd("Morn");
}
mFree(in); mFree(out1); mFree(out2);
}
这里,从1000000个随机生成的double数据中,分别取出100000、300000、500000、700000、900000个数据,测试结果如下:
显然Morn的排序快的多。
这里gsl_sort_largest_index
与mMaxSubset
也是有区别的。gsl_sort_largest_index
只输出排序后的索引,且索引对应的数据是有序的。mAscSort
会输出索引和数据,且数据是无序的。
总结:
项目 | Morn | std::sort | GSL | qsort |
---|---|---|---|---|
速度 | 快 | 快 | 慢 | 较慢 |
数据类型 | 任意数值类型,其他类型使用mListSort | 任意类型 | double | 任意类型(使用void*) |
索引 | 有 | 无(但可借助回调函数实现) | 有 | 无(但可借助回调函数实现) |
支持回调 | mListSort 支持 | 支持 | 不支持 | 支持 |
降序排序 | 支持 | 借助回调函数 | 不支持 | 借助回调函数 |
最大最小子集 | 支持 | 支持 | 支持 | 不支持 |