我正在使用标准c库的qsort-function排序以数组形式组织的数百万个结构。 我试图通过创建具有相同长度的结构的指针数组来优化性能。 与我的预期相反,第二个变体的执行时间较慢:
qsort结构体数组:199s
qsort结构体的指针数组:204
我预计在内存中交换指针块的时间将比移动结构(大小576)更快。 我可能会有性能泄漏,还是这是已知的行为?
您必须在调用sort方法之前和之后通过调用time(3)进行测量
用qsort对结构数组排序是否有可能已经交换了指针而不是结构?
同样5秒钟是2.5%的差异,这可能在您的误差范围之内。
不,qsort将移动结构(如果那是您告诉它执行的操作)。 您需要显示代码。 特别是,如果比较函数所花费的时间比移动结构所花费的时间大,那么指针数组将无济于事。
发布代码。 没有代码(并向我们展示您到底测量了什么),这毫无意义。
这里还有其他问题。
通过创建指针数组,您正在碎片化内存。标准库中的算法旨在优化连续数组的排序,因此,与仅拥有更大数组的情况相比,这样做可能会导致丢失缓存的频率更高。
特别是Quicksort对于参考位置来说是相当不错的,因为您将样本大小减半,因此最终您将原始数组的子集按块进行排序,这些块可以完全适合您的缓存。
通常,缓存未命中比命中要慢一个数量级。结果,此时间延迟可能足以弥补不复制所有字节所带来的速度提高。
快速排序的工作方式是通过将相邻元素放在一起而逐渐重新组织数组。这使得数据缓存越有效,算法越接近最终结果。
如果转换为指针数组,则数据访问可能会减慢速度,因为结构会保持其"未排序"顺序,同时对它们的指针进行排序。但是,比较结构需要遵循指向其"未排序"实例的指针,这可能会导致数据高速缓存未命中。
为了实现所需的功能,可以为数据创建索引结构。索引结构将保留排序键(或其副本)。
struct index_type {
key_type key;
data_type *data;
};
现在,您将对index_type的数组进行排序,而不是对data_type的指针的数组。由于键存储在数组本身中,因此可以避免出现指向"未排序"结构的指针的问题。
通常,哪个更快取决于结构的大小。对于与指针大小相同的结构,应该显而易见的是,对结构进行排序将比对结构的指针进行排序更快。随着结构大小的增加,将达到相反的程度(想象一下对1 MB结构的数组进行排序:您将大部分时间都花在memcopy()上)。确切地讲,这取决于代码控制范围之外的内容(缓存结构,缓存大小等)。如果这对您很重要,那么您最好进行试验和衡量。
我使用此结构进行了快速完整性检查(当int为32位时,其大小为576)
struct test
{
int value;
char data[572];
};
我使用此代码初始化了动态分配的一百万个结构的数组
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无关。