C ++扫描unsigned char数组和unsigned char向量中某些元素的最快方法是什么?

时间:2013-03-21 07:50:39

标签: c++

我有一个小问题,在LARGE unsigned char数组中扫描某些元素的最快方法是什么,以及只包含unsigned char元素的向量?直接答案会很棒,但深入详细的答案会很棒。快速是什么意思?基本上,要在至少一秒内搜索某些字符。我知道这不是一个受过良好教育的定义...

注意:数组未排序。

共同声明:

unsigned char* Array = new unsigned char[ 50000 ];
std::vector< unsigned char > Vec( 50000 );
/*
 * Fill Array & Vec with random bytes
 */

让我们说,我想在Array中搜索字母'a',我只想写这个循环来搜索它:

注意:搜索过程将搜索多个元素。主要是256.因此,你可以利用这个神奇的数字。

For loop方法:

unsigned int Count = 0;
for ( unsigned int Index = 0; Index != 50000; ++ Index )
   if( Array[ Index ] == 'a' ) Count ++;

std :: count方法:

unsigned int Count = std::count ( Array, Array + 50000, 'a' );

有没有更快的方法来搜索Array中的某些元素?

一些想法 - 请不要对此表示赞同!它只是一个想法。我想要一些意见。

分拣

如果我们制作一个Array副本并对其进行排序,速度会更好吗?为什么要复制?好吧,因为我们需要保留原始内容。目标是基本扫描和计算角色的出现次数。记住,速度很重要。这意味着,复制过程必须很快。

Answer: No and its not worth it!

为什么呢?好吧,让我们读一下:

@Kiril Kirov:

  

取决于。如果您打算搜索单个字符 -   绝对不。复制阵列是一项昂贵的操作。排序 - 更贵。

     

好吧,如果你只有一个阵列并且你打算搜索,比方说100个不同的字符,那么这个方法可以给你一个更好的表现。现在,这实际上取决于您的使用情况。没有人能够为你提供绝对正确的答案。您需要运行它并进行配置。

*向下滚动到@Kiril Krov的内容更多信息。

答案: 到目前为止,没有一个可靠的或答案,因为没有一个真正的“快速”方法来实现这一目标,特别是当它没有SORTED时。但是,线程可能是一种可能的解决方案。但是,要注意我们的CPU!这是基于@Andrea提交的答案(向下滚动一点以获取更多信息) - 我希望我读得对。

6 个答案:

答案 0 :(得分:5)

正如其他人写的那样,最佳算法的复杂性为O(n),特别是因为你的数组没有排序。

为了加快搜索速度,您可以细分数组并在单独的线程中单独扫描每个部分。这将与您计算机上可用的CPU核心数量成线性比例。

例如,如果您有四个可用核心,则生成四个线程并让每个线程扫描该阵列的四分之一。

这个讨论可能会有所帮助:Using threads to reduce array search time


在任何情况下(对于任何与性能相关的问题都是如此),您应该对代码进行概要分析。为您拥有的方法创建一个测试用例,测量所需的时间并将其作为基准。然后,对于您执行的每个修改,重做测量以检查它是否确实改善了执行时间。还要确保每次测量不止一次(在同一测试用例内)并计算平均值,以减少缓存和其他预热效果(理想情况下,在开始第一次测量之前至少执行一次代码)。

这与Java有关,但提供了一些很好的反馈,它并不是在所有情况下并行化都有意义:A Beginner´s Guide to Hardcore Concurrency

答案 1 :(得分:4)

最佳算法为O(n),其中n为元素数。

当您需要检查每个元素时,您必须遍历整个数组。

我能想到的简单方法已经写在你自己的答案中了。

并没有更快的方法 - 内存是连续的,数组没有排序,你需要“触摸”每个元素。这是最快的解决方案。


关于你的编辑:使用std::count和“手动”循环遍历数组会给你相同的性能。


  

有没有更快的方法来搜索Array

中的某些元素

是的,如果数组已排序。然后,您最多可以达到O( log(n) )。然后你需要一些现有的搜索算法,例如二进制搜索。


  

如果我们制作一个Array副本并将其排序

,速度会更好吗?

取决于。如果你打算搜索一个字符 - 绝对不是。复制阵列是一项昂贵的操作。排序 - 更贵。

好吧,如果你只有一个阵列并且你打算搜索,比方说100个不同的字符,那么这个方法可以给你一个更好的表现。现在,这实际上取决于您的使用情况。没有人能够为你提供绝对正确的答案。您需要运行它并进行配置。

答案 2 :(得分:4)

你说“快”是什么意思?

复杂性快,还是常数的改善?使用未排序的数组无法实现更好的复杂性。但是,如果您很少更改阵列并经常搜索它,您可以考虑在每次更改后对其进行排序,或者更好的是,使用不同的数据结构(如multimapset)。

如果你打算在O(n)中有一个更好的常量,那么有一些巧妙的技巧可以使用/滥用你的CPU缓存。如果你搜索多个元素,那么搜索每个字符的前几百个数组元素,接下来几百个,依此类推,然后扫描每个搜索项的整个数组,通常会更快。改进并不复杂,因此效果通常不会那么好。除非这个搜索发生在你的瓶颈重复深入其他算法,我不推荐它。因此,除非它在渲染算法,设备驱动程序或某个特定架构等内,否则很可能不值得。然而,在极少数情况下它可能是合适的,我已经看到通过使用内联汇编和滥用CPU chache,速度提高了3x - 4x或更多。

修改

您的评论意味着包含有关数据结构的简短介绍可能是一个好主意。

  • 数组,向量:访问速度最快,搜索速度慢,如果没有追加到最后,则缓慢添加/删除。
  • 列表:访问速度慢,搜索速度慢,添加/删除速度最快
  • 树,哈希表等:最佳搜索(有些允许O(0)搜索!),变化缓慢(取决于类型)

我建议您在C ++中学习不同的数据结构(矢量,列表,地图,多图,集合,多集等),这样您就可以使用最符合您需求的数据结构。

关于CPU缓存:似乎选择更合适的数据结构和代码组织更为重要。但是,为了完整起见,我将其包括在内。 如果您在较短的块中搜索数组而不是一次搜索整个数组,那么数组的这一部分将被添加到CPU的缓存中,访问缓存比访问RAM要快得多。因此,您可以处理较小的数据块(例如,搜索多个元素),然后切换到下一个数据块,依此类推。这意味着,例如,

search "a" in elements 1..100
search "b" in elements 1..100
search "c" in elements 1..100
search "a" in elements 101..200
search "b" in elements 101..200
search "c" in elements 101..200
...
search "c" in elements 999901 .. 1000000

可能比

更快
search "a" in elements 1..1000000
search "b" in elements 1..1000000
search "c" in elements 1..1000000

如果搜索到的元素(a,b,c,..)的数量足够大。为什么?因为在高速缓存大小为100的情况下,在第一个示例中,数据从RAM读取10000次,在第二个示例中读取30000次。

但是,这种效率(以及您选择的数据块大小)在很大程度上取决于您的体系结构,只有在您确定这是您真正的瓶颈时才建议使用。通常情况并非如此。

答案 3 :(得分:3)

取决于它是一次扫描还是多次扫描。 排序对扫描速度有很大帮助,您可以随时通过bisearch缩小扫描范围。复杂性可能是O(log(n))。

或者如果你可以从插入和构建将要扫描的数组开始,你可以使用插入速度很慢但总是排序的红黑树。

最后但同样重要的是,对于您正在扫描“unsigned char array”的问题,其中元素的数量是有限的。您可以进行一次扫描,但需要更多内存:使用unsigned char数组中每个元素的值作为另一个用于存储扫描结果的数组的索引。

如果你想要每个元素的位置,另一个数组可以是:int scanresult [256] [n],其中n是某个char数的最大数字。

如果你只需要计算数组中有多少'a',另一个数组可能是:int scanresult [256],以此为例,复杂度为O(n),但只需要运行一次:

unsigned char* Array = new unsigned char[ 50000 ];
/* Fill Array */
int scanresult[256];
for ( int i=0;i<256;++i) { scanresult[i]=0; }
for ( unsigned int Index = 0; Index != 50000; ++ Index )
   scanresult[Array[Index]]++;

答案 4 :(得分:2)

对于单个字符搜索,std::count可能同样快 因为你会得到。对于小型数据集(和50000) 小,你不太可能注意到时间。的 当然,对于单个角色,几乎任何合理的算法 将花费比读取数据所花费的时间更少的时间。 (std::count在向量或C样式数组中的50000个元素上 将在现代机器上瞬间接近。订单 无论如何,你的等级至少是一秒钟,无论如何。)

如果你想加快速度,解决办法就是不要创建 数组开始,但同时进行处理 你正在读取数据(或通过直接获取阵列 mmap)。如果您需要多个数据 character ...就像你一样建立一个角色频率表 读取数据。并找到最快的数据读取方式 (几乎可以肯定在Linux下mmap,至少根据一些人的说法 我最近做的措施)。在那之后,只需索引到此 表,当你想要计数。读取数据将是O(n) (并且没有办法绕过那个),但在那之后,得到了 count是O(1),具有非常非常小的连续因子 (在很多机器上都不到纳秒)。

答案 5 :(得分:0)

不要忘记,unsigned char&gt; 0&amp;&amp; unsigned char&lt; = 256 ...

#define MAX 50000 

unsigned char* Array = new unsigned char[ MAX ];
unsigned int Logs[ 256 ];

// Fill Array

::memset( &Logs, 0, sizeof( Logs ) * 256 );
for( unsigned int Index = 0; Index != MAX; ++ Index )
   Logs[ Array[ Index ] ] ++;

delete [] Logs;