c++ - 用于收集可调用对象的 std::function 的替代方法

标签 c++ c++11 templates c++14

除了求助于 std::function 之外,还有其他方法可以存储可调用对象的同类集合吗?即,替换以下代码中的类型 T

using T = std::function<void(int)>;
std::vector<T> v{some_lambda, some_fn_ptr, some_pmf, some_functor};

还有别的吗?

当将单个可调用对象作为参数传递给高阶函数时,我尽可能使用模板来避免 std::function 的开销。但是对于收藏,我不知道有什么可以做的。

最佳答案

直接类型减少开销的最大来源是内联函数的能力。在重复应用的紧密循环中,有时可以对内联函数进行矢量化或以其他方式进行大量优化。

std::function 的第二个开销来源是它使用虚函数表。这会导致两次“随机访问”内存查找——一次针对 vtable,一次跟随 vtable 上的指针。如果这些不在先前使用的缓存中,那么它是相当昂贵的。但是,如果它们在缓存中,这最终并不昂贵(一些指令)。

std::function 的最后一个开销来源是内存分配。如果std::function中存储的对象很大(以MSVC为例,大于sizeof(std::string)*2,其中std::string 本身使用 SBO,因此大小适中),发生堆分配。因此,无论何时复制或创建 std::function,都会产生相当高的成本。

这些都可以缓解。

自定义 std::function 克隆可以使用无 vtable 调用类型删除来降低 #2 的成本。这有尺寸成本。

可以编写不存储可调用对象的function_ref 类型。这些存储 void*vtable 的等价物(或指向方法的直接指针)。或者,可以编写具有自定义存储大小和拒绝堆分配的 std::function 克隆。以灵 active 和/或缺乏值(value)语义为代价,可以很好地缓解#3。

第一个是最难缓解的。

如果您知道使用您的可调用对象将执行哪些操作,您可以删除到上下文中的调用,而不是删除到通用调用。

例如,假设您有一个逐像素操作。在图像的每个像素上调用 std::function 会产生大量开销。

但是,如果我们不删除每像素调用(或同样如此),而是删除每像素运行调用,我们现在可以一般地存储我们的可调用对象,并且开销从每像素变为每扫描线或每张图片!

可调用对象现在在紧密循环中可见,因此它可以被编译器内联和矢量化,并且每个扫描线仅完成一次 vtable 后续工作。

您可以变得更有趣,甚至可以使用 linestride 删除扫描线。或者有一些删除,一个用于具有零额外线跨度的扫描线,一个用于倒置扫描线,另一个用于非零线跨度,等等。扫描线长度为 2 的幂。

这些都有成本,尤其是在开发阶段。如果您已经测试并确认 std::function 确实导致了问题,那么只能沿着这条路走下去。

关于c++ - 用于收集可调用对象的 std::function 的替代方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41204607/

相关文章:

c++ - 复数类中的静态关键字错误

c++ - 这是 std::bitset::operator^= 和 std::bitset::count 的正常行为吗?如果是这样,为什么?

c++ - 模板代码返回类型重载未编译。怎么了

c++ - yaml-cpp、YAML::Node 和模板运算符 >>

c++ - 如何将函数应用于可变参数列表并将它们分类为元组

java - 将字节数组从java程序发送到c++

java - Swig:将返回类型 std::string 转换为 java byte[]

c++ - 寻找有向图的最短路径 C++

c++ - 使用 GCC 4.7 从初始化程序列表初始化 unique_ptrs 的容器失败

c++ - 如何通过STL容器和算法库计算两个 vector 的内积?