【发布时间】:2017-09-14 18:06:48
【问题描述】:
我有一个二维数组,其中每行包含 6 个整数,这些整数已经按升序排序。示例:
1 2 3 4 5 6
6 8 9 10 13 15
1 4 5 6 7 9
1 4 5 6 7 8
3 18 19 20 25 34
预期输出:
1 2 3 4 5 6
1 4 5 6 7 8
1 4 5 6 7 9
3 18 19 20 25 34
6 8 9 10 13 15
实际数据包含 8m 到 33m 条这样的记录。我正在尝试确定对该数组进行排序的最快方法。我目前有一些使用 qsort 的工作代码:
qsort 调用:
qsort(allRecords, lineCount, sizeof(int*), cmpfunc);
cmpfunc:
int cmpfunc (const void * a, const void * b)
{
const int *rowA = *(const int **)a;
const int *rowB = *(const int **)b;
if (rowA[0] > rowB[0]) return 1;
if (rowA[0] < rowB[0]) return -1;
if (rowA[1] > rowB[1]) return 1;
if (rowA[1] < rowB[1]) return -1;
if (rowA[2] > rowB[2]) return 1;
if (rowA[2] < rowB[2]) return -1;
if (rowA[3] > rowB[3]) return 1;
if (rowA[3] < rowB[3]) return -1;
if (rowA[4] > rowB[4]) return 1;
if (rowA[4] < rowB[4]) return -1;
if (rowA[5] > rowB[5]) return 1;
if (rowA[5] < rowB[5]) return -1;
return 0;
}
对于 3300 万条记录的样本,大约需要 35.6 秒 (gcc -O1),这非常快,但我想知道是否有更快的方法来处理每行中预先排序的值。
这自然适用于最常见的第一个数字是 1 的数据,因此在 33m 记录文件中,可能有 12m 记录以 1 开头,然后 8m 记录以 2 开头,5m 记录以 3 开头,等等...我不确定这是否适合一种特定类型的排序而不是另一种(例如堆排序)。
我的理解是 qsort 有相当多的开销,因为它必须调用函数的所有时间,所以我希望有更快的性能。
我通常不编写 C 代码,因此我非常愿意接受建议和批评,因为我是从教程和其他 StackOverflow 问题/答案中拼凑起来的。
编辑: 根据要求,我的初始化代码:
// Empty record
int recArray[6] = {0,0,0,0,0,0};
// Initialize allRecords
int** allRecords;
allRecords = (int**) malloc(lineCount*sizeof(int*));
for(i=0; i < lineCount; i++)
{
allRecords[i] = (int*) malloc(6*sizeof(int));
}
// Zero-out all records
for(i=0; i < lineCount; i++)
{
memcpy(allRecords[i], recArray, 6 * sizeof(int));
}
我仍在学习做事的正确方法,所以如果我做错了,我不会感到惊讶。我们将不胜感激。
其他人询问了值的范围 - 我不确定该范围将来是否会改变,但目前的值在 1 到 99 之间。
另外,对于分析 - 我构建了一个小函数,它使用 gettimeofday() 来提取秒/微秒,然后比较前后。我愿意接受更好的方法。输出如下:
// <-- Here I capture the gettimeofday() structure output
Sorting...
Sorted.
Time Taken: 35.628882s // <-- Capture it again, show the difference
编辑: 根据@doynax - 我现在将每行的 6 个值“打包”到一个 unsigned long long int 中:
// Initialize allRecords
unsigned long long int* allRecords;
allRecords = (unsigned long long int*) malloc(lineCount*sizeof(unsigned long long int));
for(i=0; i < lineCount; i++)
{
allRecords[i] = 0;
}
...
// "Pack" current value (n0) into an unsigned long long int
if(recPos == 0) { lineSum += n0 * UINT64_C(1); }
else if(recPos == 1) { lineSum += n0 * UINT64_C(100); }
else if(recPos == 2) { lineSum += n0 * UINT64_C(10000); }
else if(recPos == 3) { lineSum += n0 * UINT64_C(1000000); }
else if(recPos == 4) { lineSum += n0 * UINT64_C(100000000); }
else if(recPos == 5) { lineSum += n0 * UINT64_C(10000000000); }
...
allRecords[linecount] = lineSum;
lineSum = 0;
我也可以稍后将其中一个 unsigned long long int 值成功地“解包”回原来的 6 个整数。
但是,当我尝试排序时:
qsort(allRecords, lineCount, sizeof(unsigned long long int), cmpfunc);
...
int cmpfunc (const void * a, const void * b)
{
if (*(unsigned long long int*)a > *(unsigned long long int*)b) return 1;
if (*(unsigned long long int*)a < *(unsigned long long int*)b) return -1;
return 0;
}
...结果未按预期排序。如果我在排序前后显示第一行和最后一行:
printf("[%i] = %llu = %i,%i,%i,%i,%i,%i\n", j, lineSum, recArray[0]...recArray[5]);
输出是:
First and last 5 rows before sorting:
[#] = PACKED INT64 = UNPACKED
[0] = 462220191706 = 6,17,19,20,22,46
[1] = 494140341005 = 5,10,34,40,41,49
[2] = 575337201905 = 5,19,20,37,53,57
[3] = 504236262316 = 16,23,26,36,42,50
[4] = 534730201912 = 12,19,20,30,47,53
[46] = 595648302516 = 16,25,30,48,56,59
[47] = 453635251108 = 8,11,25,35,36,45
[48] = 403221161202 = 2,12,16,21,32,40
[49] = 443736310604 = 4,6,31,36,37,44
[50] = 575248312821 = 21,28,31,48,52,57
First and last 5 rows after sorting:
[0] = 403221161202 = 2,12,16,21,32,40
[1] = 413218141002 = 2,10,14,18,32,41
[2] = 443736310604 = 4,6,31,36,37,44
[3] = 444127211604 = 4,16,21,27,41,44
[4] = 453028070302 = 2,3,7,28,30,45
[46] = 585043260907 = 7,9,26,43,50,58
[47] = 593524170902 = 2,9,17,24,35,59
[48] = 595248392711 = 11,27,39,48,52,59
[49] = 595251272612 = 12,26,27,51,52,59
[50] = 595648302516 = 16,25,30,48,56,59
我猜我以某种方式比较了错误的值(例如指针值而不是实际值),但我不太确定正确的语法是什么。
从好的方面来说,这种方式的速度非常快。 :)
对 33m 64 位整数进行排序大约需要 4-5 秒(至少在当前的错误形式中)。
【问题讨论】:
-
您应该提供数组的声明,因为您提供的
qsort()调用和比较函数对于真正二维数组的排序是完全错误的。不过,您可以使用它们对指针数组进行排序。目前尚不清楚您实际拥有哪个(如果有的话)。 (虽然如果它真的有效,那么我猜你的一定是一个指针数组。) -
你有更多关于值范围的信息吗?例如。对只有值 1..10 的数组进行排序可能与所有值都在整数范围内一致时非常不同。
-
@jhilgeman:不,您从原始数组中预先计算了这些新的宽整数键,并且只对新消息进行排序以代替旧消息。最后,您可以通过连续除以 100 重新创建数据(其中余数产生旧列值)。这个想法是单个 64 位整数比多个更小的整数更快、更容易比较。
-
您以相反的顺序打包。对于每个数字 10 位,我会使用类似: (long long)a[5]|((long long)a[4]
-
正如@AntonínLejsek 指出的那样,您的数据排列得很漂亮,但在最后一个数字上,而不是第一个数字上。您需要重新订购包装。您应该能够使用循环而不是 N 路
if语句来驱动打包。当然,您将对解包进行补充更改。
标签: c arrays performance sorting