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< boost::shared_ptr< std::string > > >
。这避免了在调整向量大小时复制字符串,但代价是一些复杂性。一个真正的不可变字符串类将消除成本(或者就此而言,在 C++0x 中只是 std::string
,由于移动语义)。干杯&hth.,
另一个想法:如果你还没有打开优化。
【参考方案1】:
一般来说,如果向向量添加元素是一个瓶颈,你应该使用std::vector<T>::reserve
提前预留一些空间。这应该会降低调用push_back
触发内存重新分配的可能性。
也就是说,字符串处理通常会占用大量 CPU,并且重新分配字符串对象的向量需要 大量 次复制。每次向量重新分配内存时,都需要将每个字符串对象复制到内存中的另一个位置。 (幸运的是,一旦 C++0x 移动构造函数到位,这将大大缓解。)
此外,您每次都清除向量这一事实并不会改变这样一个事实,即每次调用 push_back
都会导致将字符串对象复制到向量中,这可能是您进行所有堆分配的原因重看。不要忘记std::string
的每个实例都需要在堆上分配内存来存储字符串。
【讨论】:
【参考方案2】:如果你知道结果字符串的数量而不是你不知道它,那么向量将是最好的。双端队列或列表会做得更好。 但也许你可以检查一下向量开始的容量是多少,最后的大小是多少。
【讨论】:
呃,你为什么这么认为,你有证据吗? 链表是(如果不是)性能较差的容器之一。【参考方案3】:您可以切换到间接保存字符串的向量。然后在每次调整存储大小时都不会复制字符串,只复制“句柄”。所以不是std::vector<std::string> &words
,而是更像std::vector< counted_ptr<std::string> > &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&
。这将带来更简洁、更灵活的设计。
clear()
的定义不保证内存利用率。当您调用 clear 时,实现完全有权释放所有使用的内存。它可以像这样相当合理地实现:
void clear() vector tmp; swap(tmp);
你可能会幸运地调用resize(0)
而不是clear()
,但即使这样也不需要保持向量的容量。
如果你真的想压缩所有这些内存分配:
-
按照我上面的建议,将函数定义为带有输出迭代器的模板函数,同时传入计数限制。
传入一个大到足以容纳您希望看到的最大字数的普通 C 数组。
使用
std::pair<const char*, const char*>
而不是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&)’