<div dir="ltr">On Wed, Jul 24, 2013 at 10:27 AM, Jeffrey Yasskin <span dir="ltr">&lt;<a href="mailto:jyasskin@google.com" target="_blank">jyasskin@google.com</a>&gt;</span> wrote:<br><div class="gmail_extra"><div class="gmail_quote">
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div class="HOEnZb"><div class="h5">On Wed, Jul 24, 2013 at 8:43 AM, Howard Hinnant<br>
&lt;<a href="mailto:howard.hinnant@gmail.com">howard.hinnant@gmail.com</a>&gt; wrote:<br>
&gt; On Jul 23, 2013, at 10:36 PM, Lawrence Crowl &lt;Lawrence@Crowl.org&gt; wrote:<br>
&gt;<br>
&gt;&gt;&gt; This isn&#39;t an academic argument.  If we can keep the compiler from<br>
&gt;&gt;&gt; optimizing away the following example code (which has UB from what I&#39;ve been<br>
&gt;&gt;&gt; reading here), we make some parts of the std::library components 2 to 3<br>
&gt;&gt;&gt; times faster.  That&#39;s a much better optimization than what the compilers are<br>
&gt;&gt;&gt; getting from actively exploiting UB.<br>
&gt;&gt;&gt;<br>
&gt;&gt;&gt; #include &lt;utility&gt;<br>
&gt;&gt;&gt; #include &lt;string&gt;<br>
&gt;&gt;&gt;<br>
&gt;&gt;&gt; template &lt;class T, class U&gt;<br>
&gt;&gt;&gt; struct pair<br>
&gt;&gt;&gt; {<br>
&gt;&gt;&gt;    T first;<br>
&gt;&gt;&gt;    U second;<br>
&gt;&gt;&gt; };<br>
&gt;&gt;&gt;<br>
&gt;&gt;&gt; template &lt;class T, class U&gt;<br>
&gt;&gt;&gt; union V<br>
&gt;&gt;&gt; {<br>
&gt;&gt;&gt;    typedef pair&lt;      T, U&gt; NPair;<br>
&gt;&gt;&gt;    typedef pair&lt;const T, U&gt; CPair;<br>
&gt;&gt;&gt;    NPair n;<br>
&gt;&gt;&gt;    CPair c;<br>
&gt;&gt;&gt;<br>
&gt;&gt;&gt;    V() : c() {}<br>
&gt;&gt;&gt;    V(const T&amp; t) : c{t} {}<br>
&gt;&gt;&gt;    V(const T&amp; t, const U&amp; u) : c{t, u} {}<br>
&gt;&gt;&gt;    V(const V&amp; v) : c(v.c) {}<br>
&gt;&gt;&gt;    V&amp; operator=(const V&amp; v) {n = v.n; return *this;}<br>
&gt;&gt;&gt;    V(V&amp;&amp; v) : n(std::move(v.n)) {}<br>
&gt;&gt;&gt;    V&amp; operator=(V&amp;&amp; v) {n = std::move(v.n); return *this;}<br>
&gt;&gt;&gt;    ~V() {c.~CPair();}<br>
&gt;&gt;&gt; };<br>
&gt;&gt;&gt;<br>
&gt;&gt;&gt; struct X<br>
&gt;&gt;&gt; {<br>
&gt;&gt;&gt;    std::string s;<br>
&gt;&gt;&gt; };<br>
&gt;&gt;&gt;<br>
&gt;&gt;&gt; struct Y<br>
&gt;&gt;&gt; {<br>
&gt;&gt;&gt;    std::string s;<br>
&gt;&gt;&gt; };<br>
&gt;&gt;&gt;<br>
&gt;&gt;&gt; int<br>
&gt;&gt;&gt; main()<br>
&gt;&gt;&gt; {<br>
&gt;&gt;&gt;    typedef V&lt;X, Y&gt;  Z;<br>
&gt;&gt;&gt;    Z z1(X{&quot;some &quot;}, Y{&quot;text&quot;});<br>
&gt;&gt;&gt;    Z z2 = std::move(z1);<br>
&gt;&gt;&gt; }<br>
&gt;&gt;<br>
&gt;&gt; Can you explain your reasoning here?<br>
&gt;&gt;<br>
&gt;&gt; BTW, I think there is potential optimization in knowing that a value<br>
&gt;&gt; will not change, and so writing into a const variable strikes me as<br>
&gt;&gt; being locally optimistic and globally pessimistic.<br>
&gt;<br>
&gt; This is a crude model of std::map&lt;T, U&gt;::value_type that would allow map&#39;s copy assignment operator to recycle nodes, instead of deleting all nodes in the lhs prior to inserting new ones.  Here is a an example chart of the performance of the copy assignment of a map&lt;int, int&gt; when the rhs has 1000 values, the lhs has between 0 and 2000 values (prior to the assignment), and the implementation is varied between a simple clear()/insert(), a structural copy (avoids rebalancing the lhs at each insert), and recycling nodes using something very similar to what is sketched above with the union V.<br>

&gt;<br>
<br>
</div></div>Thanks for the concrete use case.<br>
<br>
There&#39;s always the hack of destroying and re-constructing a node when<br>
you want to re-use its memory without going through the global<br>
allocator. Then you have to figure out what you need to do with<br>
pointers to keep them valid. e.g. is &quot;p-&gt;~T(); new(p) T(...); use(p);&quot;<br>
valid, or must one write &quot;p-&gt;~T(); p = new(p) T(...); use(p);&quot;? (note<br>
the extra &quot;p=&quot;) [basic.life]p7 addresses this ... and says that for<br>
class types with a const-qualified data member, you do need to<br>
re-assign the pointer. (C++03 through C++14)<br></blockquote><div><br></div><div>Right, under the current standard, that is the way to recycle the memory and avoid undefined behavior.</div><div><br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">

Can you try that approach and see if the no-op assignments cost<br>
measurable performance? If they do, probably a clang or llvm bug would<br>
be in order.<br>
<br>
However, I agree that this is more subtle than I&#39;m happy with. We<br>
already have the notion of layout-compatible types, but T and const T<br>
aren&#39;t layout-compatible (C++14[basic.types]p11). What would go wrong<br>
if we said that cv-qualified versions of T were layout-compatible with<br>
T?</blockquote><div><br></div><div>It&#39;s important to understand the goal of [basic.life]p7 here. It&#39;s easiest to think about this rule from an implementer&#39;s perspective.</div><div><br></div><div>From a programmer&#39;s perspective, this rule says that if you have a pointer or reference to an object, and you destroy the object and create a new one in the same place, that old pointer or reference can only be used to access the new object if the old and new types match and don&#39;t contain any const subobjects or reference subobjects.</div>
<div><br></div><div>From an implementer&#39;s perspective, this rule says that you can fold together a common subexpression that loads from the same position in the same object, if you&#39;re loading a const data member, a reference member, or a vptr. (The vptr case in particular is important for performance, since it allows a lot more reasoning about the dynamic type of an object, and thus more inlining of virtual function calls.)</div>
<div><br></div><div>If we permitted const and non-const types to be layout-compatible, we&#39;d break [basic.life]p7&#39;s guarantees and undermine its intent, and an implementation would no longer be able to perform common subexpression elimination on loads of the same const data member of the same object.</div>
<div><br></div><div>Also, the common initial sequence rules only permit &quot;inspect[ing] the common initial part&quot;. So either the active member is pair&lt;T,U&gt; and users of the map can&#39;t modify the .second of values through a pair&lt;const T,U&gt;, or the active member is pair&lt;const T,U&gt; and Howard&#39;s recycling trick does not work because .first is const. We would require additional changes to the common initial sequence rules to allow this; changing &quot;layout-compatible&quot; is insufficient.<br>
</div></div></div></div>