我使用这两个函数从一大堆数据中搜索查询。它们的速度起初大致相同,但是当尺寸变得非常大时,二进制搜索阵列会稍快一些。那是因为缓存效应吗?数组有顺序。树有吗?
int binary_array_search(int array[], int length, int query){
//the array has been sorted
int left=0, right=length-1;
int mid;
while(left <= right){
mid = (left+right)/2;
if(query == array[mid]){
return 1;
}
else if(query < array[mid]){
right = mid-1;
}
else{
left = mid+1;
}
}
return 0;
}
// Search a binary search tree
int binary_tree_search(bst_t *tree, int ignore, int query){
node_t *node = tree->root;
while(node != NULL){
int data = node->data;
if(query < data){
node = node->left;
}
else if(query > data){
node =node->right;
}
else{
return 1;
}
}
return 0;
}
以下是一些结果:
LENGTH SEARCHES binary search array binary search tree
1024 10240 7.336000e-03 8.230000e-03
2048 20480 1.478000e-02 1.727900e-02
4096 40960 3.001100e-02 3.596800e-02
8192 81920 6.132700e-02 7.663800e-02
16384 163840 1.251240e-01 1.637960e-01
答案 0 :(得分:0)
为什么数组可能并且应该更快有几个原因:
由于left
和right
指针,树中的节点至少比数组中的项目大3倍。
例如,在32位系统上,您将有12个字节而不是4个字符。这些12个字节被填充或对齐16个字节。在64位系统上,我们得到8和24到32字节。
这意味着使用数组可以在L1缓存中加载3到4倍的项目。
树中的节点在堆上分配,并且它们可以在内存中的任何位置,具体取决于它们的分配顺序(也可以是堆碎片) - 并创建这些节点(使用new
或与数组的可能一次性分配相比,alloc
}也将花费更多时间 - 但这可能不是速度测试的一部分。
要访问数组中的单个值,只需要进行一次读取,对于我们需要两个的树:left
或right
指针和值。
当达到较低级别的搜索时,要比较的项目将在数组中靠近(并且可能已经在L1缓存中),而它们可能会在树的内存中传播。
由于locality of reference,大部分时间数组会更快。
答案 1 :(得分:0)
这是因为缓存效应吗?
当然,这是主要原因。在现代CPU上,缓存透明地用于在内存中读/写数据。
缓存比主存(DRAM)快得多。为了给你一个观点,访问1级缓存中的数据大约是4个CPU周期,而访问同一CPU上的DRAM大约是200个CPU周期,即快50倍。
缓存在称为缓存行的小块上运行,缓存行通常为64字节长。
更多信息:https://en.wikipedia.org/wiki/CPU_cache
数组有顺序。树有吗?
Array是一个单独的数据块。数组的每个元素都与其邻居相邻,即:
+-------------------------------+
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
+-------------------------------+
block of 32 bytes (8 times 4)
每个数组访问都获取一个缓存行,即64个字节或16个int值。因此,对于数组,存在很高的概率(特别是在二进制搜索结束时),下一次访问将在同一个缓存行中,因此不需要内存访问。
另一方面,树节点是逐个分配的:
+------------------------------------------------+
+------------------+ | +------------------+ +------------------+ |
| 0 | left | right | -+ | 2 | left | right | <- | 1 | left | right | <-+
+------------------+ +------------------+ +------------------+
block 0 of 24 bytes block 2 of 24 bytes block 1 of 24 bytes
正如我们所看到的,为了存储3个值,我们使用的内存比在上面的数组中存储8个值多2倍。因此,树结构更稀疏,统计上每个64字节高速缓存行的数据更少。
此外,每个内存分配都会在内存中返回一个块,该块可能与先前分配的树节点不相邻。
此外,分配器将每个内存块对齐至少8个字节(在64位CPU上),因此在那里浪费了一些字节。更不用说我们需要在每个节点中存储left
和right
个指针......
因此,即使在排序的最后,每个树访问都需要获取一个缓存行,即数组访问速度较慢。
那么为什么数组在测试中的速度稍微快一点呢?这是由于二进制搜索。在排序的最开始,我们非常随机地访问数据,每次访问都与之前的访问相差甚远。因此,数组结构会在排序结束时获得提升。
为了好玩,尝试比较数组中的线性搜索(即基本搜索循环)与树中的二进制搜索。我打赌你会对结果感到惊讶;)