lambda 是语言层面的 feature,功能强大,性能高,而 bind 在很多时候使用起来更简洁。
在现实中,至少有一种情况,一定不要使用 bind:把 lambda 当作 std::function 使用时!
在 MySQL 源码中,有这样一段代码:
void handler::ha_statistic_increment(ulonglong System_status_var::*offset) const {
if (table && table->in_use) {
(table->in_use->status_var.*offset)++;
table->in_use->check_limit_rows_examined();
table->in_use->update_sql_stats_periodic();
table->in_use->check_yield(std::bind(yield_condition, table));
}
}
注意其中使用的 std::bind
:
table->in_use->check_yield(std::bind(yield_condition, table));
这个 bind 以最简洁的方式,非常准确直观地表达了程序意图,我们看 yield_condition 的原型:
static bool yield_condition(TABLE*);
它只有一个参数,而 check_yield 函数是这样的:
void THD::check_yield(std::function<bool()> cond) {...}
上面的 std::bind 会生成一个 class,其实现了 operator(),仅针对这个例子,实例化之后大致会是这样:
struct BindType {
bool (*m_yield_condition)(TABLE*); // = &yield_condition
TABLE* m_table; // = table;
bool operator() const { return m_yield_condition(m_table); }
};
可以看到,BindType 至少需要占用两个指针的空间,记住这一点!
std::function 的实现,一般(g++ 的 libstdc++)占用四个指针的空间,如果用来初始化 std::function 的 Functor 对象(包括函数指针、lambda……)尺寸过大,就需要通过 new 在堆上申请内存,用来保存 Functor。
MySQL 的这个 std::bind,就生成了一个让 std::function 必须 new 内存的 Functor!这个额外的消耗,在火焰图

虽然这个总耗时不多,但是看看 new/delete 占了多大比例!当然,这个问题,只要意识到了,改进修复还是非常简单的,使用 lambda 即可:

这个 lambda 仅捕获了 handler::table,当然也可以改成这样:
[this]() { return yield_condition(this->table); }
但是捕获 table 性能要更高,因为此刻编译器早已把 table 成员加载到某个寄存器中了,从而需要的后续操作更少。
这个 lambda 的尺寸只有一个指针大小,用它来构造 std::function,不需要 new/delete!
如果 lambda 的尺寸无论如何都超过一个指针大小呢?
使用 std::ref,这样写:
auto lambda = [...](...) {...}; // 超过一个指针大小的 lambda
std::function<...> func(std::ref(lambda));
// ...
但这个前提是必须保证 lambda 的生命周期覆盖 func。