Nicolai Josuttis在其著作《C标准库(第二版)》中指出,与普通函数相比,编译器可以更好地优化lambdas。
此外,C 编译器优化 lambda 的效果比普通函数更好。(第213页)
这是为什么呢?
我想当涉及到内联时,应该不会再有任何区别了。我能想到的唯一原因是编译器可能有更好的lambdas本地上下文,这样可以做出更多假设并执行更多优化。
Lambdas不比通常的函数快或慢。如果错了,请纠正我。
首先,λ和通常函数的区别是什么:
让我们来谈谈捕获。它不会给函数带来任何性能,因为编译器必须传递带有处理捕获所需数据的附加对象。无论如何,如果你只是在适当的地方使用lambda函数,它将很容易被优化。此外,如果lambda不使用捕获,您可以将lambda转换为函数指针。为什么?因为如果没有捕获,它只是一个普通的函数。
void (*a1)() = []() {
// ...
};
void _tmp() {
// ...
}
void (*a2)() = _tmp;
以上两个例子都是有效的。
谈到从对象文件中删除函数。您可以简单地将函数放入匿名命名空间,它就会进行交易。函数会更乐意被内联,因为它除了你的文件之外没有在任何地方使用。
auto a1 = []() {
// ...
};
namespace {
auto a2() {
// ...
}
}
上述功能在性能上是相同的。
此外,我注意到函数指针和lambda进行了比较。这不是一件好事,因为它们是不同的。当你有一个指向函数的指针时,它可以指向各种不同的函数,并且可以在运行时进行更改,因为它只是一个指向内存的指针。Lambda不能这样做。它总是只使用一个函数进行操作,因为要调用哪个函数的信息存储在类型本身中。
您可以使用函数指针编写代码,如下所示:
void f1() {
// ...
}
void f2() {
// ...
}
int main() {
void (*a)();
a = f1;
a = f2;
}
这是绝对好的。您不能用lambdas这样编写代码:
int main() {
auto f1 = []() {
// ...
};
auto f2 = []() {
// ...
};
f2 = f1; // error: no viable overloaded '='
}
如果某些库接受函数指针,这并不意味着编译器可以比普通函数更好地优化lambdas,因为问题不在于公共库和函数指针。
因为当你把一个“函数”传递给一个算法时,你实际上是在传递一个指向函数的指针,所以它必须通过指向该函数的指针进行间接调用。当您使用 lambda 时,您将对象传递给专门为该类型实例化的模板实例,并且对 lambda 函数的调用是直接调用,而不是通过函数指针进行的调用,因此更有可能内联。
原因是lambdas是函数对象,因此将它们传递给函数模板将为该对象实例化一个新函数。因此,编译器可以简单地内联lambda调用。
另一方面,对于函数来说,旧的警告仍然适用:函数指针被传递给函数模板,编译器传统上在通过函数指针内联调用时有很多问题。理论上它们可以被内联,但前提是周围的函数也被内联。
例如,请考虑以下函数模板:
template <typename Iter, typename F>
void map(Iter begin, Iter end, F f) {
for (; begin != end; ++begin)
*begin = f(*begin);
}
像这样用一个lambda来称呼它:
int a[] = { 1, 2, 3, 4 };
map(begin(a), end(a), [](int n) { return n * 2; });
此实例化的结果(由编译器创建):
template <>
void map<int*, _some_lambda_type>(int* begin, int* end, _some_lambda_type f) {
for (; begin != end; ++begin)
*begin = f.operator()(*begin);
}
…编译器知道_some_lambda_type::operator()
并可以内联对它的调用。(使用任何其他lambda调用函数map
将创建map
的新实例化,因为每个lambda都有不同的类型。)
但当使用函数指针调用时,实例化如下所示:
template <>
void map<int*, int (*)(int)>(int* begin, int* end, int (*f)(int)) {
for (; begin != end; ++begin)
*begin = f(*begin);
}
…这里f
指向每个调用map
的不同地址,因此编译器无法内联调用f
,除非周围对map
的调用也已内联,以便编译器可以将f
解析为一个特定的函数。
我正在阅读每个程序员都应该知道的内存https://people.freebsd.org/~lstewart/articles/cpumemory.pdf,它说内联函数使你的代码更可优化 例如 :特别是函数的内联允许编译器一次优化更大的代码块,这反过来又可以生成机器代码,从而更好地利用处理器的管道架构。 and: 当程序的较大部分可以被视为单个单元时,代码和数据的处理(通过死代码消除或值范围传播等
考虑下面的代码片段 通过-O3优化,最新的gcc和clang版本没有优化指向包装器的指针、指向底层函数的指针。参见第22行的组装: 在后面的+中,编译器将指针放置到,而不只是。 编辑2。同样模式的更简单的例子: gcc 8.2通过抛出指向包装器的指针并将直接存储在其位置(https://gcc.godbolt.org/z/nmibeo)成功地优化了这段代码。然而,按照注释更改代码行(即手动执行相同
(这个问题与此密切相关,但它是一个更具体的问题,我希望能就此得到答案)
我经常注意到gcc在可执行文件中将乘法转换为移位。当将与相乘时,可能会发生类似的情况。例如,可能只是将的指数增加1,从而节省一些周期。如果有人要求编译器这样做(例如,通过),编译器通常会这样做吗? 编译器通常是否足够聪明来执行此操作,还是我需要自己使用或函数系列来执行此操作?
问题内容: 我试图理解 为什么Java的ArrayDeque比Java的LinkedList更好, 因为它们都实现了Deque接口。 我几乎看不到有人在他们的代码中使用ArrayDeque。如果有人对ArrayDeque的实现方式有了更多的了解,那将是有帮助的。 如果我理解它,我会更自信地使用它。对于JDK实现管理头和尾引用的方式,我不清楚。 问题答案: 链接结构可能是最糟糕的结构,要在每个元素上
问题内容: 我目前正在开始新的应用开发。应用程序设计师坚持认为我们使用 JBoss5 是因为它“更好”。是否有人对“更好”有更广泛的定义(如果如此)? 我有在具有大量用户负载的大规模应用程序中使用 Tomcat5 和6的经验,并且它处理得很好(IMHO)。两者都将在相同的硬件条件下(如果实现很重要)在 RedHat6 上运行。 提前致谢 问题答案: 说任何工具或框架都只是“更好”是可笑的。它总是取