向量重声明与循环操作中的插入-C++
Vector re-declaration versus insertions in looping operations - C++
我可以选择在每次调用func()
时创建和销毁向量,并在每次迭代中推送元素,如示例a所示。或者修复初始化,在每次迭代时只覆盖旧值,如示例B所示。
示例A:
void func ()
{
std::vector<double> my_vec(5, 0.0);
for ( int i = 0; i < my_vec.size(); i++) {
my_vec.push_back(i);
// do something
}
}
while (condition) {
func();
}
示例B:
void func (std::vector<double>& my_vec)
{
for ( int i = 0; i < my_vec.size(); i++) {
my_vec[i] = i;
// do something
}
}
while (condition) {
std::vector<double> my_vec(5, 0.0);
func(myVec);
}
这两者中哪一个在计算上比较便宜。数组的大小不会超过10。
我仍然怀疑所问的问题不是预期的问题,但我突然想到,我回答的要点可能不会改变。如果问题更新了,我总是可以编辑这个答案以匹配(或者删除它,如果它不适用的话)。
取消优化的优先级
有多种因素会影响您编写代码的方式。理想的目标包括空间优化、时间优化、数据封装、逻辑封装、可读性、健壮性和正确的功能。理想情况下,所有这些目标都可以在每一段代码中实现,但这并不特别现实。更有可能的情况是,必须牺牲其中一个或多个目标来支持其他目标当这种情况发生时,优化通常应该屈服于其他一切
这并不是说优化应该被忽略。有很多优化很少阻碍更高优先级的目标。这些范围从小到大,比如选择对数算法而不是指数算法,比如通过常量引用而不是通过值。然而,干扰其他目标的优化应该推迟到代码合理完成并正常运行之后。这时,应该使用探查器来确定瓶颈的实际位置。只有当探查器确认优化实现了它们的目标时,这些瓶颈才是其他目标应该屈服于优化的地方。
对于所问的问题,这意味着主要关注的不是计算费用,而是封装。为什么func()
的调用者需要为func()
分配工作空间?它不应该,除非探查器将其识别为性能瓶颈。如果探查器做到了这一点,那么询问探查器更改是否有帮助要比询问Stack Overflow容易得多(也更可靠!)。
为什么
我可以想到两个主要原因来降低优化的优先级。首先,"嗅觉测试"是不可靠的。虽然可能有少数人可以通过查看代码来识别瓶颈,但还有很多人只是认为自己可以。其次,这就是我们优化编译器的原因。有人想出这种超级聪明的优化技巧,却发现编译器已经在做了,这并非闻所未闻。保持代码干净,让编译器处理例程优化。只有当任务明显超出编译器的能力时才介入。
另请参阅:过早优化
选择优化
好吧,假设探查器确实将这个10元素的小数组的构建确定为瓶颈。下一步是测试替代方案,对吧?几乎首先,你需要一个替代方案,我认为审查各种替代方案的理论益处是有用的。请记住,这是理论性的,剖析者拥有最终发言权。因此,我将从这个问题中探讨替代方案的利弊,以及其他一些可能值得考虑的替代方案。让我们从最糟糕的选择开始,朝着更好的选择努力。
示例A
在示例A中,用5个元素创建一个向量,然后将元素推到向量上,直到i
满足或超过向量的大小。看到i
和向量的大小在每次迭代中都增加了一个(并且i
开始时小于大小),这个循环将运行,直到向量变大到足以使程序崩溃。这意味着可能要进行数十亿次迭代(尽管问题声称规模不会超过10次)。
很容易成为计算成本最高的选项。不要这样做。
示例B
在示例B中,为外部while
循环的每次迭代创建一个向量,然后通过引用从func()
中访问该向量。这里的性能缺点包括将参数传递给func()
,以及让func()
通过引用间接访问向量。没有性能优势,因为这可以完成基线(见下文)所要做的一切,再加上一些额外的步骤。
尽管编译器可能能够补偿缺点,但我认为没有理由尝试这种方法。
基线
我使用的基线是对示例a的无限循环的修复。具体而言,将"my_vec.push_back(i);
"替换为示例B的"my_vec[i] = i;
"。这种简单的方法与我对探查器的初始评估的期望一致。如果你不能打败简单,就坚持下去。
示例B*
该问题的文本对示例B进行了不准确的评估。有趣的是,该评估描述了一种有可能在基线上改进的方法。要获得与文本描述匹配的代码,请将示例B的"std::vector<double> my_vec(5, 0.0);
"移到while
语句之前的行。这样做的效果是只构造一次向量,而不是每次迭代都构造它。
这种方法的缺点与最初编码的示例B的缺点相同。然而,我们现在获得了一个好处,即向量的构造函数只被调用一次。如果构造比间接成本更昂贵,那么一旦while
循环足够频繁地迭代,结果应该是净改进。(注意这些条件:这是一个重要的"如果",并且没有关于多少次迭代"足够"的先验猜测。)尝试一下,看看分析器会说什么是合理的。
获取一些静态
示例B*的一个有助于保持封装的变体是使用基线(固定的示例A),但在向量声明之前使用关键字static
。这带来了只构造一次向量的好处,但没有使向量成为参数的相关开销。事实上,由于每次程序执行只进行一次构造,而不是每次启动while
循环,因此这种好处可能比示例B*中的好处更大。while
循环启动的次数越多,这一好处就越大。
这里的主要缺点是矢量将在整个程序执行过程中占用内存。与示例B*不同,当包含while
循环的块结束时,它不会释放内存。在太多地方使用这种方法会导致内存膨胀。因此,尽管介绍这种方法是合理的,但您可能需要考虑其他选项。(当然,如果探查器将其称为瓶颈,使所有其他瓶颈相形见绌,那么成本就足够小了。)
固定大小
我个人的选择是从基线开始,将向量切换到std::array<10,double>
。我的主要动机是所需的尺码不会超过10码。同样相关的是CCD_ 21的构造是琐碎的。数组的构造应该与声明double
类型的10个变量一样,我认为这是可以忽略的。所以不需要花里胡哨的优化技巧。让编译器来做它自己的事情。
这种方法的预期可能好处是vector
在堆上为其存储分配空间,这会带来开销成本。本地array
将不具有此成本。然而,这只是一个可能的好处。矢量实现可能已经利用了小矢量的这种性能考虑。(也许在容量需要超过某个神奇的数字,可能超过10之前,它不会使用堆。)我想让你回到我之前提到的"超级聪明"answers"编译器已经在做了"。
我会通过探查器运行这个。如果没有好处,那么其他方法很可能也没有好处。当然,请尝试一下,因为它们已经足够简单了,但最好利用你的时间从其他方面进行优化。
- 使用C++库在Android项目中修改gradle中的cmake参数,用于插入指令的测试
- 有关插入适配器的错误。[错误]请求从 'back_insert_iterator<vector<>>' 类型转换为非标量类型
- 预处理器:插入结构名称中的前一个行号
- 在未初始化映射的情况下,将值插入到映射的映射中
- 如何在c++中只将键插入到bimap的一侧
- 如何将结构插入到集合中并打印集合的成员
- C++json插入数组
- Visual Studio 2019:插入多个C++风格的单行注释
- nlohmann-json将一个数组插入到另一个数组中
- 有效地使用std::unordered_map来插入或增加键的值
- 为字符串中每 N 个字符插入空格的函数没有按照我认为的方式工作?
- 正在插入动态数组
- 插入或删除时获取usb的dos_name
- 叮叮当当在修复时插入多个"覆盖"说明符
- 链表c++插入,所有情况都已检查,但没有任何工作
- 将重物插入std::map
- C++17 - 使用自定义分配器的节点提取/重新插入 - 适用于 clang++/libc++,但不适用于 libstd
- 在数字之间插入 + 或 - 符号以使其等于整数
- 在字符串中插入空格
- 重载运算符的范围是什么?它是否会影响作为类成员的集合的插入函数?