我正在使用标准c库的qsort函数对数组中数百万个结构进行排序。我尝试通过创建具有相同长度的struct的指针数组来优化性能。与我的期望相反,第二个变体的执行时间较慢:
qsort一系列结构:199s qsort结构指针数组:204
我预计在内存中交换指针块的时间会比移动结构更快(大小为576)。我可能有任何性能泄漏或这是一种已知行为吗?
答案 0 :(得分:5)
这里还有其他问题。
通过创建指针数组,您正在分割内存。标准库中的算法旨在优化连续数组的排序,因此,通过这样做,您可能比使用更大的数组更频繁地丢失缓存。
特别是Quicksort对于引用的局部性来说非常好,因为你将样本大小减半,所以最终你可以用完全适合你的缓存的块来对原始数组的子集进行排序。
作为一般规则,缓存未命中比命中慢一个数量级。因此,这个时间延迟可能非常重要,可以通过不复制所有字节来弥补你所获得的速度。
答案 1 :(得分:2)
快速排序的工作方式,它通过将相邻元素放在一起逐渐重新组织阵列。这使得数据缓存能够在算法越接近最终结果时更有效地工作。
如果转换为指针数组,那么数据访问可能会减慢,因为结构保持其“未排序”排序,而它们的指针正在排序。但是,比较结构需要遵循指向其“未排序”实例的指针,这可能会导致数据缓存未命中。
为了达到您想要的效果,您可以为数据创建索引结构。索引结构将保存排序键(或其副本)。
struct index_type {
key_type key;
data_type *data;
};
现在,您要对index_type
数组进行排序,而不是指向data_type
的指针数组。由于密钥存储在数组本身中,因此可以避免跟随指向“未排序”结构的指针。
答案 2 :(得分:0)
我使用这种结构进行了快速的健全性检查(当int
为32位时,其大小为576)
struct test
{
int value;
char data[572];
};
我使用此代码初始化了一个包含100万个结构的动态分配数组
for ( int i = 0; i < count; i++ )
{
array[i].value = rand();
for ( int j = 0; j < 572; j++ )
array[i].data[j] = rand();
}
我用这段代码对数组进行了排序
int compare( const void *ptr1, const void *ptr2 )
{
struct test *tptr1 = (struct test *)ptr1;
struct test *tptr2 = (struct test *)ptr2;
return tptr1->value - tptr2->value;
}
int main( void )
{
int count = 1000000;
...
qsort( array, count, sizeof(struct test), compare );
...
}
初始化阵列的时间是4.3秒,对阵列进行排序的时间是0.9秒。
然后,我修改了代码,创建了一个指向结构的指针数组,并对指针数组进行了排序。初始化时间仍为4.3秒(大部分初始化时间是由于调用rand()
5亿次)。对指针数组进行排序需要0.4秒。对指针数组进行排序的速度是直接对结构数组进行排序的两倍多。
所以我的结论是,您的代码存在一些与qsort
无关的大量低效率。
答案 3 :(得分:0)
更快将取决于结构的大小。对于与指针大小相同的结构,显然对结构进行排序比排序指向结构的指针更快。随着结构大小的增加,将达到反转为真的点(想象排序1 MB结构的数组:您将大部分时间花在memcopy()上)。确切地说,这一点取决于代码控制之外的事物(缓存结构,缓存大小等)。如果这对您很重要,那么您最好进行实验和测量。