<div dir="ltr">Could that actually be provided immutability-safely?  I suppose an append-only, fixed-length builder would be potentially safe.<br><br>Part of the trickiness there is with primitive parameters, where presizing and doing the actual append both require calculating the size of the primitive when converted to a string.  The current approach just uses an upper bound for the primitive type as a whole, which is fine and allocates a small constant excess in most cases, but I'm not sure how we could avoid duplicating that computation in a public API.</div><br><div class="gmail_quote">On Tue, Apr 7, 2015 at 2:07 PM Peter Levart <<a href="mailto:peter.levart@gmail.com">peter.levart@gmail.com</a>> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
  
    
  
  <div style="background-color:rgb(255,255,255);color:rgb(0,0,0)" bgcolor="#FFFFFF" text="#000000">
    Hi Louis,<br>
    <br>
    This is nice. Amd could be even nicer. In case the estimated initial
    StringBuilder capacity is exactly the final String length, then
    constructing the String could skip the char[] copying step as the
    StringBuilder instance does not escape. But in order to be safe,
    this would have to be a special kind of StringBuilder. Like the
    following:<br>
    <br>
<a href="http://cr.openjdk.java.net/~plevart/misc/ThreadLocalStringBuilder/webrev.01/" target="_blank">http://cr.openjdk.java.net/~plevart/misc/ThreadLocalStringBuilder/webrev.01/</a><br>
    <br>
    Such class would be useful for direct API use too.<br>
    <br>
    <br>
    Regards, Peter</div><div style="background-color:rgb(255,255,255);color:rgb(0,0,0)" bgcolor="#FFFFFF" text="#000000"><br>
    <br>
    <div>On 03/13/2015 10:40 PM, Louis Wasserman
      wrote:<br>
    </div>
    <blockquote type="cite">
      <div dir="ltr">Got it.  I think the only cases we have to worry
        about, then, are buffer size overflows resulting in
        NegativeArraySizeException, or possibly an explicitly thrown
        OutOfMemoryError (which is StringBuilder's response when the
        buffer size tries to exceed Integer.MAX_VALUE).  I think we
        might conceivably deal with this by rewriting the bytecode to --
        I think we can improve on this with jump hackery to avoid
        repetition, but essentially --
        <div><br>
        </div>
        <div>int length = 3; // sum of the constant strings; we can
          check that this won't overflow at compile time but I think it
          couldn't no matter what because of method length constraints</div>
        <div>String mStr = m().toString();</div>
        <div>length += mStr.length();</div>
        <div>if (length < 0) {</div>
        <div>  throw new OutOfMemoryError();</div>
        <div>}</div>
        <div>String nStr = n().toString();</div>
        <div>length += nStr.length();</div>
        <div>if (length < 0) {</div>
        <div>  throw new OutOfMemoryError();</div>
        <div>}</div>
        <div><br>
        </div>
        <div>This continues to expand the bytecode, but probably
          manageably -- we don't actually need a local for length; if
          (int < 0) is easy in bytecode, and we can have only one
          OOME site that all the ifs jump to?</div>
      </div>
      <br>
      <div class="gmail_quote">On Fri, Mar 13, 2015 at 12:59 PM Alex
        Buckley <<a href="mailto:alex.buckley@oracle.com" target="_blank">alex.buckley@oracle.com</a>>
        wrote:<br>
        <blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">I do
          recognize that the proposed implementation doesn't reorder the<br>
          evaluation of subexpressions.<br>
          <br>
          When discussing the proposed implementation of '+' -- whose
          key element<br>
          is calling append(String) with a pre-computed value -- I was
          careful to<br>
          set aside asynchronous OOMEs, but I see that even synchronous
          OOMEs are<br>
          sidetracking us. My concern is not heap pressure; my concern
          is<br>
          arbitrary unchecked exceptions arising from the
          <init>(int) and<br>
          append(String) calls.<br>
          <br>
          For sake of argument, I'll simplify "unchecked exceptions" to
          just<br>
          RuntimeExceptions, not Errors. If you can guarantee that no<br>
          RuntimeExceptions are thrown synchronously during the
          execution of those<br>
          method bodies on the JVM, then '+' cannot fail and the timing
          of<br>
          subexpression evaluation is unobservable (ordering is still
          observable,<br>
          as required). I think this guarantee is just a matter of
          reviewing the<br>
          method bodies.<br>
          <br>
          Alex<br>
          <br>
          On 3/12/2015 6:01 PM, Louis Wasserman wrote:<br>
          > I confess I'm not sure how that "quality" goal would be
          achievable in<br>
          > bytecode without deliberately allocating arrays we then
          discard.<br>
          ><br>
          > For what it's worth, delaying or avoiding OOMEs seems a
          desirable goal<br>
          > in general, and up to a constant multiplicative factor,
          this<br>
          > implementation seems to allocate the same amount in the
          same order.<br>
          > That is, we're still computing m().toString() before
          n().toString(), and<br>
          > up to a constant multiplicative factor, m().toString()
          allocates the<br>
          > same number of bytes as the StringBuilder the status quo
          generates.  So<br>
          > if m() does something like allocate a
          char[Integer.MAX_VALUE], we still<br>
          > OOM at the appropriate time.<br>
          ><br>
          > Other notes: this implementation would tend to decrease
          maximum<br>
          > allocation, so it'd reduce OOMEs.  Also, since the
          StringBuilder will<br>
          > never need further expansion and we're only using the
          String and<br>
          > primitive overloads of append, the only way for append to
          OOME would be<br>
          > in append(float) and append(double), which allocate a
          FloatingDecimal<br>
          > (which may, in turn, allocate a new thread-local char[26]
          if one isn't<br>
          > already there).<br>
          ><br>
          > On Thu, Mar 12, 2015 at 4:28 PM Alex Buckley <<a href="mailto:alex.buckley@oracle.com" target="_blank">alex.buckley@oracle.com</a><br>
          > <mailto:<a href="mailto:alex.buckley@oracle.com" target="_blank">alex.buckley@oracle.com</a>>>
          wrote:<br>
          ><br>
          >  Â  Â More abstract presentation. Given the expression:<br>
          ><br>
          >  Â  Â  Â  Â "foo" + m() + n()<br>
          ><br>
          >  Â  Â you must not evaluate n() if evaluation of "foo" +
          m() completes<br>
          >  Â  Â abruptly. The proposed implementation evaluates n()
          regardless.<br>
          ><br>
          >  Â  Â All is not lost. In the proposed implementation, the
          abrupt completion<br>
          >  Â  Â of "foo" + m() could occur because an append call
          fails or (thanks to<br>
          >  Â  Â Jon for pointing this out) the StringBuilder ctor
          fails. The<br>
          >  Â  Â quality-of-implementation issue is thus: if the
          proposed implementation<br>
          >  Â  Â is of sufficiently high quality to guarantee that the
          ctor and the first<br>
          >  Â  Â append both succeed, then the evaluation of "foo" +
          m() will always<br>
          >  Â  Â complete normally, and it would be an unobservable
          (thus acceptable)<br>
          >  Â  Â implementation detail to evaluate n() early.<br>
          ><br>
          >  Â  Â Alex<br>
          ><br>
          >  Â  Â On 3/11/2015 10:26 PM, Jeremy Manson wrote:<br>
          >  Â  Â  > Isn't Louis's proposed behavior equivalent to
          saying "the rightmost<br>
          >  Â  Â  > concatenation threw an OOME" instead of "some
          concatenation in the<br>
          >  Â  Â  > middle threw an OOME"?<br>
          >  Â  Â  ><br>
          >  Â  Â  > It's true that the intermediate String
          concatenations haven't<br>
          >  Â  Â occurred<br>
          >  Â  Â  > at that point, but in the JDK's current
          implementation, that's true,<br>
          >  Â  Â  > too: the concatenations that have occurred at
          that point are<br>
          >  Â  Â  > StringBuilder ones, not String ones.  If any of
          the append operations<br>
          >  Â  Â  > throws an OOME, no Strings have been created at
          all, either in<br>
          >  Â  Â Louis's<br>
          >  Â  Â  > implementation or in the JDK's.<br>
          >  Â  Â  ><br>
          >  Â  Â  > Ultimately, isn't this a quality of
          implementation issue?  And if so,<br>
          >  Â  Â  > isn't it a quality of implementation issue that
          doesn't provide any<br>
          >  Â  Â  > additional quality?  I can't imagine code whose
          semantics relies on<br>
          >  Â  Â  > this, and if they do, they are relying on
          something<br>
          >  Â  Â  > implementation-dependent.<br>
          >  Â  Â  ><br>
          >  Â  Â  > Jeremy<br>
          >  Â  Â  ><br>
          >  Â  Â  > On Wed, Mar 11, 2015 at 6:13 PM, Alex Buckley<br>
          >  Â  Â <<a href="mailto:alex.buckley@oracle.com" target="_blank">alex.buckley@oracle.com</a>
          <mailto:<a href="mailto:alex.buckley@oracle.com" target="_blank">alex.buckley@oracle.com</a>><br>
          >  Â  Â  > <mailto:<a href="mailto:alex.buckley@oracle." target="_blank">alex.buckley@oracle.</a>__com<br>
          >  Â  Â <mailto:<a href="mailto:alex.buckley@oracle.com" target="_blank">alex.buckley@oracle.com</a>>>>
          wrote:<br>
          >  Â  Â  ><br>
          >  Â  Â  >  Â  Â On 3/11/2015 2:01 PM, Louis Wasserman
          wrote:<br>
          >  Â  Â  ><br>
          >  Â  Â  >  Â  Â  Â  Â So for example, "foo" + myInt +
          myString + "bar" + myObj<br>
          >  Â  Â would be<br>
          >  Â  Â  >  Â  Â  Â  Â compiled to the equivalent of<br>
          >  Â  Â  ><br>
          >  Â  Â  >  Â  Â  Â  Â int myIntTmp = myInt;<br>
          >  Â  Â  >  Â  Â  Â  Â String myStringTmp =
          String.valueOf(myString); // defend<br>
          >  Â  Â against<br>
          >  Â  Â  >  Â  Â  Â  Â null<br>
          >  Â  Â  >  Â  Â  Â  Â String myObjTmp =<br>
          >  Â  Â String.valueOf(String.valueOf(____myObj)); // defend<br>
          >  Â  Â  >  Â  Â  Â  Â against evil toString implementations
          returning null<br>
          >  Â  Â  ><br>
          >  Â  Â  >  Â  Â  Â  Â return new StringBuilder(<br>
          >  Â  Â  >  Â  Â  Â  Â  Â  Â  Â  17 // length of "foo" (3) + max
          length of myInt (11) +<br>
          >  Â  Â  >  Â  Â  Â  Â length of<br>
          >  Â  Â  >  Â  Â  Â  Â "bar" (3)<br>
          >  Â  Â  >  Â  Â  Â  Â  Â  Â  Â  + myStringTmp.length()<br>
          >  Â  Â  >  Â  Â  Â  Â  Â  Â  Â  + myObjTmp.length())<br>
          >  Â  Â  >  Â  Â  Â  Â  Â  Â  .append("foo")<br>
          >  Â  Â  >  Â  Â  Â  Â  Â  Â  .append(myIntTmp)<br>
          >  Â  Â  >  Â  Â  Â  Â  Â  Â  .append(myStringTmp)<br>
          >  Â  Â  >  Â  Â  Â  Â  Â  Â  .append("bar")<br>
          >  Â  Â  >  Â  Â  Â  Â  Â  Â  .append(myObjTmp)<br>
          >  Â  Â  >  Â  Â  Â  Â  Â  Â  .toString();<br>
          >  Â  Â  ><br>
          >  Â  Â  >  Â  Â  Â  Â As far as language constraints go, the
          JLS is (apparently<br>
          >  Â  Â  >  Â  Â  Â  Â deliberately)<br>
          >  Â  Â  >  Â  Â  Â  Â vague about how string concatenation is
          implemented.  "An<br>
          >  Â  Â  >  Â  Â  Â  Â implementation<br>
          >  Â  Â  >  Â  Â  Â  Â may choose to perform conversion and
          concatenation in one<br>
          >  Â  Â step<br>
          >  Â  Â  >  Â  Â  Â  Â to avoid<br>
          >  Â  Â  >  Â  Â  Â  Â creating and then discarding an
          intermediate String<br>
          >  Â  Â object. To<br>
          >  Â  Â  >  Â  Â  Â  Â increase<br>
          >  Â  Â  >  Â  Â  Â  Â the performance of repeated string
          concatenation, a Java<br>
          >  Â  Â  >  Â  Â  Â  Â compiler may<br>
          >  Â  Â  >  Â  Â  Â  Â use the StringBuffer class or a similar
          technique to<br>
          >  Â  Â reduce the<br>
          >  Â  Â  >  Â  Â  Â  Â number<br>
          >  Â  Â  >  Â  Â  Â  Â of intermediate String objects that are
          created by<br>
          >  Â  Â evaluation of an<br>
          >  Â  Â  >  Â  Â  Â  Â expression."  We see no reason this
          approach would not<br>
          >  Â  Â qualify as a<br>
          >  Â  Â  >  Â  Â  Â  Â "similar technique."<br>
          >  Â  Â  ><br>
          >  Â  Â  ><br>
          >  Â  Â  >  Â  Â The really key property of the string
          concatenation operator is<br>
          >  Â  Â  >  Â  Â left-associativity. Later subexpressions
          must not be<br>
          >  Â  Â evaluated until<br>
          >  Â  Â  >  Â  Â earlier subexpressions have been
          successfully evaluated AND<br>
          >  Â  Â  >  Â  Â concatenated. Consider this expression:<br>
          >  Â  Â  ><br>
          >  Â  Â  >  Â  Â  Â  "foo" + m() + n()<br>
          >  Â  Â  ><br>
          >  Â  Â  >  Â  Â which JLS8 15.8 specifies to mean:<br>
          >  Â  Â  ><br>
          >  Â  Â  >  Â  Â  Â  ("foo" + m()) + n()<br>
          >  Â  Â  ><br>
          >  Â  Â  >  Â  Â We know from JLS8 15.6 that if m() throws,
          then foo+m()<br>
          >  Â  Â throws, and<br>
          >  Â  Â  >  Â  Â n() will never be evaluated.<br>
          >  Â  Â  ><br>
          >  Â  Â  >  Â  Â Happily, your translation doesn't appear to
          catch and swallow<br>
          >  Â  Â  >  Â  Â exceptions when eagerly evaluating each
          subexpression in<br>
          >  Â  Â turn, so I<br>
          >  Â  Â  >  Â  Â believe you won't evaluate n() if m()
          already threw.<br>
          >  Â  Â  ><br>
          >  Â  Â  >  Â  Â Unhappily, a call to append(..) can in
          general fail with<br>
          >  Â  Â  >  Â  Â OutOfMemoryError. (I'm not talking about
          asynchronous<br>
          >  Â  Â exceptions in<br>
          >  Â  Â  >  Â  Â general, but rather the sense that
          append(..) manipulates the<br>
          >  Â  Â heap<br>
          >  Â  Â  >  Â  Â so an OOME is at least plausible.) In the
          OpenJDK<br>
          >  Â  Â implementation, if<br>
          >  Â  Â  >  Â  Â blah.append(m()) fails with OOME, then n()
          hasn't been<br>
          >  Â  Â evaluated yet<br>
          >  Â  Â  >  Â  Â -- that's "real" left-associativity. In the
          proposed<br>
          >  Â  Â implementation,<br>
          >  Â  Â  >  Â  Â it's possible that more memory is available
          when evaluating<br>
          >  Â  Â m() and<br>
          >  Â  Â  >  Â  Â n() upfront than at the time of an append
          call, so n() is<br>
          >  Â  Â evaluated<br>
          >  Â  Â  >  Â  Â even if append(<<tmp result of
          m()>>) fails -- that's not<br>
          >  Â  Â  >  Â  Â left-associative.<br>
          >  Â  Â  ><br>
          >  Â  Â  >  Â  Â Perhaps you can set my mind at ease that
          append(..) can't<br>
          >  Â  Â fail with<br>
          >  Â  Â  >  Â  Â OOME?<br>
          >  Â  Â  ><br>
          >  Â  Â  >  Â  Â Alex<br>
          >  Â  Â  ><br>
          >  Â  Â  ><br>
          ><br>
        </blockquote>
      </div>
      </blockquote>
    <br>
  </div></blockquote></div>