除了求助于 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/