简体   繁体   English

函数参数按值传递比按引用传递更快?

[英]Function argument pass-by-value faster than pass-by-reference?

I had an interview, where I get the following function declaration:我进行了一次采访,在那里我得到了以下函数声明:

int f1(const std::vector<int> vec)

I suggested that instead of copying the vector, we should use a const-reference(not to mention that const copy does not makes much sense), but the interviewer claimed that the compiler copy-elision will handle it.我建议不要复制向量,而应该使用 const-reference(更不用说 const copy 没有多大意义),但面试官声称编译器复制省略会处理它。 I couldn't come up with any strong argument at the spot, but today I did some research.我当场无法提出任何有力的论据,但今天我做了一些研究。

I implemented the following two simple example functions:我实现了以下两个简单的示例函数:

int f1(const std::vector<int> vec) {
    const auto num = vec.size();
    return num * num;
}


int f2(const std::vector<int>& vec) {
    const auto num = vec.size();
    return num * num;
}

From the godbolt assembly, it is clear that the f2 function has 2 additional instructions so it should be slower.godbolt汇编中,很明显f2函数有2 条附加指令,所以它应该更慢。 (I think 2 mov is technically almost free in modern CPUs) (我认为 2 mov在现代 CPU 中在技术上几乎是免费的)

I also used quick-bench to measure the two solutions, but it is confirmed my suspicion, that passing const-ref is faster.我还使用 quick-bench 来测量这两种解决方案,但证实了我的怀疑,传递 const-ref 更快。 (even for 1 element) (即使是 1 个元素)

在此处输入图像描述

I suspected that maybe copy-elision is not allowed because of benchmark::DoNotOptimize(result);我怀疑由于benchmark::DoNotOptimize(result); , but after removing it, I received a similar result. ,但删除它后,我收到了类似的结果。

Now I have these results, but I think it is still not convincing enough.现在我有了这些结果,但我觉得还是不够有说服力。

What do you think?你怎么看?

Do you have any good argument for using one over the other?你有什么好的论据来使用一个而不是另一个?

Looking at the assembly of the functions f1 and f2 won't work.查看函数f1f2的组装是行不通的。 With optimizations enabled they are likely to look completely identical.启用优化后,它们可能看起来完全相同。 (Except that in some ABIs it is the callee's responsibility to destroy function parameters, in which case f1 will look much longer. But that doesn't mean anything by itself. There will be destruction of any created object somewhere anyway, whether in the caller or the callee.) (除了在某些 ABI 中,销毁函数参数是被调用者的责任,在这种情况下f1看起来会更长。但这本身并不意味着任何事情。无论如何,无论在调用者中,任何创建的对象都会被销毁或被调用者。)

The performance difference here doesn't lie inside the function, but in the potential caller.这里的性能差异不在于函数内部,而在于潜在的调用者。

Function parameters are constructed in the context of the caller, not in the context of the callee.函数参数是在调用者的上下文中构造的,而不是在被调用者的上下文中。 So, here if f1 is called like所以,如果f1被称为

std::vector<int> vec{/*...*/};
auto res = f1(vec);

will cause vec to be copied into the function parameter in the caller.将导致vec被复制到调用者的函数参数中。 It is not possible to elide the copy.无法省略副本。 (Of course the compiler is free to optimize the copy away if it can see that doing so won't change the observable behavior of the program, but that generally can't happen if the function isn't inlined, for example because it's definition is in another translation unit and there is no link-time optimization.) (当然,如果编译器可以看到这样做不会改变程序的可观察行为,那么编译器可以自由地优化副本,但如果函数没有内联,通常不会发生这种情况,例如因为它是定义在另一个翻译单元中,并且没有链接时优化。)

With

std::vector<int> vec{/*...*/};
auto res = f2(vec);

no copy is done, even conceptually.没有复制,即使在概念上也是如此。 Copy elision is not relevant.复制省略不相关。

It is true that eg确实,例如

std::vector<int> vec{/*...*/};
auto res = f1(std::move(vec));

will use the move constructor and will generally be cheap enough that it might be faster than using f2 under some circumstances where the function can't be inlined.将使用移动构造函数,并且通常足够便宜,在某些无法内联函数的情况下,它可能比使用f2更快。 (However that will depend on how the ABI specifies that a std::vector is passed to functions as well.) (但是,这将取决于 ABI 如何指定std::vector也传递给函数。)

It is also true that eg这也是正确的,例如

auto res = f1(std::vector<int>{/*...*/});

will construct only one std::vector<int> if copy elision is applied (guaranteed since C++17 and optionally before).如果应用了复制省略,则将仅构造一个std::vector<int> (从 C++17 开始保证并且可选地在之前)。 However, the same is true if f2 was used.但是,如果使用f2 ,也是如此。

In some situations f1 might be a better choice, because the compiler can be sure that it doesn't modify vec from the caller.在某些情况下f1可能是更好的选择,因为编译器可以确保它不会从调用者修改vec (A const reference does not strictly mean that the referenced object can't be modified.) This may allow the compiler to make some optimizations in the caller without having to analyze the body of f1 that it might not be able to do with f2 . (一个const引用并不严格意味着引用的对象不能被修改。)这可能允许编译器在调用者中进行一些优化,而不必分析它可能无法用f2做的f1的主体。

So all in all I would say, in some circumstances they are equally good, in some circumstances f2 is definitively the better choice and in some more rare situations it might be that f1 is the better choice.所以总而言之,我想说的是,在某些情况下它们同样好,在某些情况下f2绝对是更好的选择,而在一些更罕见的情况下, f1可能是更好的选择。 I would go with f2 by default.我会默认使用f2

However, looking at the bigger picture, I would try to avoid having a function which iterates over a vector take the vector itself as parameter.但是,从更大的角度来看,我会尽量避免让一个迭代向量的函数将向量本身作为参数。 Functions like this can typically be written more generally to apply to arbitrary ranges and hence it would probably be easy to make them templates and use either the iterator interface or the C++20 range interface used by standard library algorithms.像这样的函数通常可以更普遍地编写以应用于任意范围,因此将它们制作为模板并使用标准库算法使用的迭代器接口或 C++20 范围接口可能会很容易。 This way, if in the future someone decides to use some other container than std::vector , the function will still just work.这样,如果将来有人决定使用std::vector以外的其他容器,该函数仍然可以正常工作。

Just assuming copy-elision is not a good idea: copying a vector is very expensive involving memory management and potentially copying a lot.假设复制省略不是一个好主意:复制向量非常昂贵,涉及内存管理并且可能复制很多。 Risking this copy over some marginal gain that you could have is not a good bet unless you know the behavior of your current and future compilers (copy-elision may be done, but is not mandated).除非您知道当前和未来编译器的行为(可以进行复制省略,但不是强制的),否则冒着这个副本风险可能获得的一些边际收益并不是一个好选择。 One way to check whether copy elision was actually done is to check the pointer to the vector data on the caller side and in the function.检查复制省略是否实际完成的一种方法是检查调用方和函数中指向向量数据的指针。 Even if speed is not the greatest concern, more programmers expect the pass-by-const-ref over pass-by-value and thus don't have to question the reason for the decision.即使速度不是最关心的问题,更多的程序员希望通过 const-ref 而不是按值传递,因此不必质疑决定的原因。

Mind you that copy-elision is mandatory for initialization (from the link: when the initializer expression is a prvalue of the same class type (ignoring cv-qualification) as the variable type)请注意,初始化必须使用复制省略(来自链接:当初始化表达式是与变量类型相同的类类型(忽略 cv-qualification)的纯右值时)

See also on cppreference另请参阅cppreference

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 区分函数模板中的值传递和引用传递 - Distinguish between pass-by-value and pass-by-reference in a function template 区分传递参考和传递价值 - Distinguishing Pass-by-Reference and Pass-by-Value 如何获得模板参数包来推断按引用而不是按值? - How do I get a template parameter pack to infer pass-by-reference rather than pass-by-value? C ++中按值传递和按引用传递之间的区别 - Differences between pass-by-value and pass-by-reference in c++ 我应该在哪里更喜欢按引用传递或按值传递? - Where should I prefer pass-by-reference or pass-by-value? 通过查看装配将值传递与引用传递性能进行比较 - Comparing pass-by-value with pass-by-reference performance by looking at assembly 值传递和 std::move 优于传递引用的优点 - Advantages of pass-by-value and std::move over pass-by-reference 将按引用传递和按值传递混合到可变参数模板函数是否有效? - Mixing pass-by-reference and pass-by-value to variadic template function valid? 我可以让C ++编译器决定是按值传递还是按引用传递? - Can I let the C++ compiler decide whether to pass-by-value or pass-by-reference? C++:赋值运算符:按值传递(复制和交换)与按引用传递 - C++: assignment operator: pass-by-value (copy-and-swap) vs pass-by-reference
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM