std::vector push_back 是瓶颈

Posted

技术标签:

【中文标题】std::vector push_back 是瓶颈【英文标题】:std::vector push_back is bottleneck 【发布时间】:2010-10-23 23:55:44 【问题描述】:

这是我的算法的作用: 它需要一个长的 std::string 并根据它是否大于宽度将其分为单词和子单词:

inline void extractWords(std::vector<std::string> &words, std::string &text,const AguiFont &font, int maxWidth)



    words.clear();

    int searchStart = 0;
    int curSearchPos = 0;
    char right;
    for(size_t i = 0; i < text.length(); ++i)
    
        curSearchPos = i;

        //check if a space is to the right
        if( i == text.length() - 1)
            right = 'a';
        else
            right = text[i + 1];

        //sub divide the string if it;s too big
        int subStrWidth = 0;
        int subStrLen = 0;
        for(int x = searchStart; x < (curSearchPos - searchStart) + 1; ++x)
        
            subStrWidth += font.getTextWidth(&text[x]);
            subStrLen ++;
        
        if(subStrLen > maxWidth && subStrLen > 1)
        
            for(int k = 2; k <= subStrLen; ++k)
            
                subStrWidth = 0;
                for(int p = 0; p < k; ++p)
                
                    subStrWidth += font.getTextWidth(&text[searchStart + p]);
                
                if(subStrWidth > maxWidth)
                
                    searchStart += k - 1;

                    words.push_back(text.substr(searchStart,k - 1));
                    break;

                
            
        

        //add the word
        if((text[i] == ' ' && right != ' ' ) || i == text.length() - 1)
        

                if(searchStart > 0)
                
                    words.push_back(text.substr(searchStart ,(curSearchPos - searchStart) + 1));

                
                else
                
                    words.push_back(text.substr(0 ,(curSearchPos - searchStart) ));
                    words.back() += text[curSearchPos];

                

            searchStart = i + 1 ;
        
    



如您所见,我使用 std::vectors 来推动我的话。向量由参考给出。那个 std::vector 是静态的,它在调用 extractWord 的过程中。奇怪的是,将其设为静态会导致更多的 CPU 消耗。在分析之后,我看到我进行了大量的堆分配,但我不知道为什么,因为即使在向量被清除后,std::vector 也应该保留它的项目。是否有一种不那么密集的方式来做到这一点?字符串长度是未知的,结果字符串的数量也是未知的,这就是我选择 std::vector 的原因,但是可能有更好的方法吗?

谢谢

*实际上我认为我的子字符串生成速度很慢

【问题讨论】:

分配是否可能是在副本中创建新的string 对象和substr() 操作而不是直接负责向量push 操作的结果?使用非静态向量真的会产生可衡量的差异吗? 如果不能reserve,试试穷人的不可变字符串:std::vector&lt; boost::shared_ptr&lt; std::string &gt; &gt; &gt;。这避免了在调整向量大小时复制字符串,但代价是一些复杂性。一个真正的不可变字符串类将消除成本(或者就此而言,在 C++0x 中只是 std::string,由于移动语义)。干杯&hth., 另一个想法:如果你还没有打开优化。 【参考方案1】:

一般来说,如果向向量添加元素是一个瓶颈,你应该使用std::vector&lt;T&gt;::reserve提前预留一些空间。这应该会降低调用push_back 触发内存重新分配的可能性。

也就是说,字符串处理通常会占用大量 CPU,并且重新分配字符串对象的向量需要 大量 次复制。每次向量重新分配内存时,都需要将每个字符串对象复制到内存中的另一个位置。 (幸运的是,一旦 C++0x 移动构造函数到位,这将大大缓解。)

此外,您每次都清除向量这一事实并不会改变这样一个事实,即每次调用 push_back 都会导致将字符串对象复制到向量中,这可能是您进行所有堆分配的原因重看。不要忘记std::string 的每个实例都需要在堆上分配内存来存储字符串。

【讨论】:

【参考方案2】:

如果你知道结果字符串的数量而不是你不知道它,那么向量将是最好的。双端队列或列表会做得更好。 但也许你可以检查一下向量开始的容量是多少,最后的大小是多少。

【讨论】:

呃,你为什么这么认为,你有证据吗? 链表是(如果不是)性能较差的容器之一。【参考方案3】:

您可以切换到间接保存字符串的向量。然后在每次调整存储大小时都不会复制字符串,只复制“句柄”。所以不是std::vector&lt;std::string&gt; &amp;words,而是更像std::vector&lt; counted_ptr&lt;std::string&gt; &gt; &amp;words。然后查看thisDobb 博士的文章,了解有关 counted_ptr 的更多信息。

另外,为了避免潜在的 Heisenbug 追逐,auto_ptr 不是您希望在 STL 容器中用于此类事情的东西。

【讨论】:

【参考方案4】:

代码看起来运行良好,但在性能方面,魔鬼总是在细节中。以下是一些想法:

    考虑将向量声明从:

    来自:std::vector<:string> &words to : std::vector &words

    这将创建一个指针并为其分配字符串的地址,而不是将每个字符串的内容复制到向量中。

    尝试使用 vector::reserve 预分配处理字符串所需的内存。粗略估计可能是 text.length() / maxWidth。

    密切注意正在使用的字符串操作。很可能有很多临时字符串正在生成并立即被丢弃。找出是否发生这种情况的最佳方法是逐步检查字符串操作行,看看是否出现了额外的字符串构造函数和复制构造函数。

【讨论】:

大多数字符串实现都是引用计数的,因此使用string* 实际上会减慢速度,因为它不会减少复制,但与string 相比,它需要一个额外的堆对象。此外,还有处理管理指向堆对象的原始指针的额外工作。 @Marcelo - 感谢您的跟进。再次查看后,我认为字符串指针不适用于现有代码。 string::substr() 返回一个新的字符串对象,该对象在被丢弃之前必须被复制。 char 指针向量可能是一种有效的方法,但这不会是对现有代码的改进,它更像是重写。谢谢你让我诚实:)【参考方案5】:

首先,您应该考虑传递一个输出迭代器而不是 vector&amp;。这将带来更简洁、更灵活的设计。

clear() 的定义不保证内存利用率。当您调用 clear 时,实现完全有权释放所有使用的内存。它可以像这样相当合理地实现:

void clear()  vector tmp; swap(tmp); 

你可能会幸运地调用resize(0) 而不是clear(),但即使这样也不需要保持向量的容量。

如果你真的想压缩所有这些内存分配:

    按照我上面的建议,将函数定义为带有输出迭代器的模板函数,同时传入计数限制。 传入一个大到足以容纳您希望看到的最大字数的普通 C 数组。 使用std::pair&lt;const char*, const char*&gt; 而不是std::string 来保存找到的字词。

【讨论】:

以上是关于std::vector push_back 是瓶颈的主要内容,如果未能解决你的问题,请参考以下文章

正确使用用户定义类型的 std::vector.push_back()

std::vector of OpenCV 点,没有 push_back 方法

std::vector.push_back() 的奇怪(记忆?)问题

没有匹配函数调用‘std::vector::push_back(std::string&)’

想简化我的 std::vector push_back 使用

std::vector 的 push_back 是不是会创建参数的深层副本?