c++中内联函数的零成本列表
Posted
技术标签:
【中文标题】c++中内联函数的零成本列表【英文标题】:Zero-cost lists for inline functions in c++ 【发布时间】:2019-06-28 13:50:41 【问题描述】:我喜欢为列表中的函数编写检查。为此,我通常会编写这样的函数:
inline bool good_strings(const std::vector<const char *> & items)
for (i in items)
if (not is_good(i)) return false;
return true;
然后我可以像if (all_good("a", "b", "c", "d", "e")) ...
这样写,它看起来真的很不错。当您对几个项目的检查变得像这样变大时,这是很好的使用:
if (is_good("a") and is_good("b") and /* that's too much, man */ is_good("c")) ...
但我担心我正在使用的容器的开销,而且很难选择一个:std::vector
、std::list
、QList
、QStringList
甚至可能是 std::array
或 @ 987654329@ - 内联函数应该使用哪个?在使用 括号创建时,其中哪一个具有最小甚至零开销?
好的,更新一下:我拿了我朋友的授权 IDA Pro 并检查了一些选项。
std::initializer_list
: 这个函数甚至没有内联,还有
是创建列表和复制指针的开销。
std::vector
:该函数确实是内联的,但是,有一个
创建向量和复制指针的开销。
std::array
:因为模板专业化,没那么好看,
并且该函数没有内联。所以多次调用它会创建
许多类似的代码块。但是,数组没有开销
创建,并且所有指针都作为函数参数传递,这
x86_64
注册调用约定很快。
问题仍然存在,是否有绝对零成本的容器?
【问题讨论】:
为什么不对整个函数进行模板化呢?然后它可以使用你碰巧通过它的任何容器。 参数复杂性不应该影响内联函数。您通过引用传递该参数,因此这只是存储在寄存器中的地址(对于大多数编译器)。编译器通常决定在出现在函数内部的代码上内联一些东西,分支预测等等。您不必担心参数类型。 您的目标是在编译时评估所有结果,还是在运行时尽可能高效地评估? std::all_of(items.begin(), items.end(), is_good); @JeremyFriesner 函数 is_good 有副作用,必须在运行时评估,但它的参数始终相同,可以在编译时传递。 【参考方案1】:没有一个容器将是零开销。 std::array
或 std::initializer_list
会给你最少的成本。 std::array
需要在编译时指定它的类型和大小,因此在这种情况下它比std::initializer_list
对用户不太友好。因此,使用std::initializer_list<const char*>
将是您可以使用的最小且最容易使用的“容器”。它会消耗编译器生成的指针数组的大小,甚至可能更多一些,并且不需要任何动态内存分配。
如果你可以使用 C++17,你甚至不需要容器。使用variadic template 和fold expression 您可以将所有参数作为单独的参数传递给函数,并对所有参数应用相同的操作。
template<typename... Args>
bool good_strings(Args&&... args)
return (is_good(args) && ...);
会转
all_good("a", "b", "c", "d", "e")
进入
return is_good("a") && is_good("b") && ... && is_good("e");
它利用了短路,因此一旦第一次调用 is_good
返回 false,它就会停止评估。
您可以在 C++11 中使用可变参数模板,但您要么需要使用递归,要么构建自己的数组,这实际上并没有为您带来任何额外的复杂性。
【讨论】:
非常好。您还可以将is_good
设为另一个参数,以便轻松更改谓词。
是的,这是一个不错的解决方案,也是我想要使用的,但遗憾的是我的目标平台还没有 c++14。
@MorJ 这实际上需要 C++17(用于折叠表达式)。你受限于哪个版本的 C++?
好吧,我查过了,你对 initializer_list 的看法是错误的。 (-; 如果您有兴趣,请查看我的答案,我可以发送带有测试二进制文件的 .idb 文件。
对了,我发现了折叠表达式的一个小问题:没有办法写一个具体类型的参数包。有趣的是,告诉我这件事的 *** 问题说我应该使用std::vector
来完成这项工作。 (***.com/questions/18017543/…)【参考方案2】:
好的,感谢之前答案中的想法,我想通了。如果人们说“没有更好的容器存在”,那么 std::array 是最好的。当使用优化级别 -O2
编译时,std::array
既没有参数复制,也没有函数调用,而 std::initializer_list 有参数复制。当使用-O0
编译时,这一切都如我在问题本身中所描述的那样。
所以我的解决方案:使用std::array
并处理指定<N>
的参数数量。
【讨论】:
通过引用 (const &
) 传递初始化列表可能有助于提高性能。
@NathanOliver 通过 const 引用传递是隐含的,当然没有它就没有任何优化的希望。【参考方案3】:
如果你真的关心使用容器,你可以写N
重载,例如对于N = 5
:
inline bool good_string(const char* a)
return true;
inline bool good_strings(const char* a, const char* b)
return good_string(a) && good_string(b);
inline bool good_strings(const char* a, const char* b, const char* c)
return good_strings(a, b) && good_string(c);
inline bool good_strings(const char* a, const char* b, const char* c, const char* d)
return good_strings(a, b, c) && good_string(d);
inline bool good_strings(const char* a, const char* b, const char* c, const char* d, const char* e)
return good_strings(a ,b, c, d) && good_string(e);
【讨论】:
对不起,您的解决方案很糟糕:我想避免重复,而重复就是您的建议。你的解决方案从一个小问题变成了一个大问题。另一方面,可以使这项工作更好,并使用参数包和递归来编写。但是,正如我刚刚发现的另一个 *** 问题所说,没有办法编写具有具体类型的参数包。 我认为您误解了重复的概念。使用此代码,您不是在重复自己,而是在创建重载,其中每个重载都有自己的目的!从 abseil.io here 查看StrCat
。他们在那里应用相同的概念。
好吧,我对重复有一些强烈的意见,这里不是发誓的地方。但是谢谢,我检查了abseil源,它看起来像垃圾。让我感到困惑的是,作者知道参数包,但只在 5 个参数之后使用它们。他们也非常喜欢指针魔术、reinterpret_cast 和 void*,这对项目来说也是不好的迹象。
正如我所说,我发布的内容与重复无关。【参考方案4】:
如果你这样模板化函数:
bool is_good(const std::string &) return true; )
template<typename Container>
inline bool good_strings(const Container & items)
for (auto const &i : items)
if (not is_good(i)) return false;
return true;
然后调用good_strings(std::initializer_list<std::string>"a", "b", "c", "d", "e")
会将初始化列表传递给all_good
。不需要容器。
【讨论】:
编译失败,因为...
没有类型:coliru.stacked-crooked.com/a/54814bd7767b79c7
Container
与概念有关吗? OP为c++11添加标签
我试过了,它不能在 gcc 中为 c++11 编译。对于这种多态列表文字,显然无法在编译时推断出模板类型。哦,我多么想念 Haskell。以上是关于c++中内联函数的零成本列表的主要内容,如果未能解决你的问题,请参考以下文章