很难以相信构造p[u+1]发生在代码的最内层循环中的几个地方,我保持这样,在正确运行数天的操作中,对其进行微观优化会产生数小时的差异.
通常*((p+u)+1)是最有效的.有时候*(p+(u+1))效率最高.很少*((p+1)+u)是最好的.(但通常优化器可以转换*((p+1)+u)为*((p+u)+1)后者更好,并且不能*(p+(u+1))与其他任何一个转换).
p是一个指针,u是一个无符号的.在实际代码中,它们中的至少一个(更可能两者)在表达式被评估的点处已经在寄存器中.这些事实对我的问题至关重要.
在32位(在我的项目放弃支持之前)中,所有三个都具有完全相同的语义,并且任何一半不错的编译器只选择三者中最好的并且程序员永远不需要关心.
在这些64位用法中,程序员知道这三者具有相同的语义,但编译器不知道.就编译器所知,何时u从32位扩展到64位的决定会影响结果.
什么是最简洁的方法告诉编译器这三个语义是相同的,编译器应该选择最快的?
在一个Linux 64位编译器中,我几乎在那里p[u+1L]使编译器在通常最好*((p+u)+1)和有时更好之间智能地选择*(p+( (long)(u) + 1) ).在极少数情况下*(p+(u+1))仍然比第二种更好,有点丢失.
显然,这在64位Windows中没有用.既然我们已经放弃了32位支持,那么可能p[u+1LL]足够便携且足够好.但我能做得更好吗?
请注意,使用std::size_t而不是unsignedfor u将消除整个问题,但会在附近产生更大的性能问题.铸造u到std::size_t那里几乎是足够好,也许我能做到的最好.但对于一个不完美的解决方案来说,这是非常冗长的.
简单编码(p+1)[u]使选择更可能是最佳选择p[u+1].如果代码的模板化程度较低且更稳定,我可以将它们全部设置为(p+1)[u]然后进行配置文件,然后再将其切换回p[u+1].但是模板化往往会破坏这种方法(在配置文件的许多地方出现单一的源代码行,导致严重的时间,但不是单独的严重时间).
对此应该有效的编译器是GCC,ICC和MSVC.
我想编写几个递归交互的合并函数,我认为应该有签名: T&& merge_XYZ(T&& a, T&& b);
它们倾向于以递归方式使用,例如:
return merge_XYZ( std::move(x), std::move(y) );
Run Code Online (Sandbox Code Playgroud)
这几个合并函数中的每一个都将窃取其中一个输入的内容并将这些内容注入另一个输入并返回结果.通常,它们将具有x和y哪些是rvalue引用的名称,因此应该转换回rvalue引用std::move(如果我错了,请纠正我).
但很少,它们具有x或者y说是对其内容不得被盗的对象的引用.我绝对不想写这些函数的替代非窃取版本.相反,我希望呼叫者在这些极少数情况下处理这个问题.所以我的主要问题是,正确的方法是显式调用复制构造,例如:
T temp = merge_QRS( T(x), T(y) ); // use x and y without stealing yet
return merge_XYZ( merge_MNO( std::move(x), std::move(y) ), std::move(temp) );
Run Code Online (Sandbox Code Playgroud)
主要问题:是否T(x)有正确的方法强制在此时创建临时副本?
其他问题:
是否T temp =有正确的方法来确保merge_QRS上述代码中的调用在调用之前发生,merge_MNO但以其他方式便宜地将临时代码转发到第一个操作数中merge_XYZ?如果我使用T&& temp它而不是最终持有指针修改T(x)后的生命T(x)?
是否T&&将正确的返回类型(相对于T)链接在一起?
以上如何比较:
T tx = x;
T&& …Run Code Online (Sandbox Code Playgroud)