glassfish
  1. glassfish
  2. GLASSFISH-18725

[PERF] Servlet Performance Regression when writing string data

    Details

    • Type: Bug Bug
    • Status: Closed
    • Priority: Major Major
    • Resolution: Fixed
    • Affects Version/s: 4.0_b36
    • Fix Version/s: 4.0_b43
    • Component/s: grizzly-kernel
    • Labels:
      None

      Description

      We have recently extended our performance benchmark suite by adding web container atomics benchmarks. This benchmark shows around 12% to 18% regression across atomics. Will add more details to it.

        Issue Links

          Activity

          Hide
          Scott Oaks added a comment -

          This appears to be (at least partly) because of a huge increase in the time spend in the character encoder. In grizzly 1.9 with glassfish 3.1.2, encoding was done to an array-backed byte buffer; now we are doing encoding to a direct bytebuffer. Because there is no backing array, looping through the character encoding takes a long time.

          Show
          Scott Oaks added a comment - This appears to be (at least partly) because of a huge increase in the time spend in the character encoder. In grizzly 1.9 with glassfish 3.1.2, encoding was done to an array-backed byte buffer; now we are doing encoding to a direct bytebuffer. Because there is no backing array, looping through the character encoding takes a long time.
          Hide
          Shing Wai Chan added a comment -

          Assign to Grizzly team for further investigation.

          Show
          Shing Wai Chan added a comment - Assign to Grizzly team for further investigation.
          Hide
          Scott Oaks added a comment -

          There are multiple regressions in the web container atomics benchmarks. In this bug, we will focus on the issue that affects simple servlet writing.

          For servlets, there is a regression in the way in which strings are written to the servlet output stream. Previously, a call to servletOutputStream.println() ended up in com.sun.grizzly.util.buf.C2BConverter, which got the character array from the string and passed it to the encoder. Because the buffer has a backing array, encoding it is simply a matter of iteration through the array.

          Now in the org.glassfish.grizzly.http.server.io.OutputBuffer.flushCharsToBuf() method, we wrap the string in a CharBuffer and pass that to the character encoder. Because that CharBuffer does not have a backing array, the encoder uses its encodeBufferLoop() method, which results in lots of calls to buffer.get() rather than simply iterating through the array. This causes a significant performance penalty.

          However, this code path is not used by JSP tests (JSP writers use char[] by the time the data gets to the OutputBuffer class). The JSP paths make up the bulk of the web container atomics benchmarks – hence changing the subject of this bug (and will file a separate bug on whatever is regressing in the JSP path).

          Show
          Scott Oaks added a comment - There are multiple regressions in the web container atomics benchmarks. In this bug, we will focus on the issue that affects simple servlet writing. For servlets, there is a regression in the way in which strings are written to the servlet output stream. Previously, a call to servletOutputStream.println() ended up in com.sun.grizzly.util.buf.C2BConverter, which got the character array from the string and passed it to the encoder. Because the buffer has a backing array, encoding it is simply a matter of iteration through the array. Now in the org.glassfish.grizzly.http.server.io.OutputBuffer.flushCharsToBuf() method, we wrap the string in a CharBuffer and pass that to the character encoder. Because that CharBuffer does not have a backing array, the encoder uses its encodeBufferLoop() method, which results in lots of calls to buffer.get() rather than simply iterating through the array. This causes a significant performance penalty. However, this code path is not used by JSP tests (JSP writers use char[] by the time the data gets to the OutputBuffer class). The JSP paths make up the bulk of the web container atomics benchmarks – hence changing the subject of this bug (and will file a separate bug on whatever is regressing in the JSP path).
          Hide
          oleksiys added a comment - - edited

          Hi Scott,

          in Grizzly 1.9.x' C2BConverter I see this code

              public void convert(char c[], int off, int len) throws IOException {
                  CharBuffer cb = CharBuffer.wrap(c, off, len);
                  byte[] barr = bb.getBuffer();
                  int boff = bb.getEnd();
                  ByteBuffer tmp = ByteBuffer.wrap(barr, boff, barr.length - boff);
                  CoderResult cr = encoder.encode(cb, tmp, true);
                  bb.setEnd(tmp.position());
                  while (cr == CoderResult.OVERFLOW) {
          	    if (!bb.canGrow())
                          bb.flushBuffer();
          	    boff = bb.getEnd();
          	    barr = bb.getBuffer();
                      tmp = ByteBuffer.wrap(barr, boff, barr.length - boff);
                      cr = encoder.encode(cb, tmp, true);
                      bb.setEnd(tmp.position());
                  }
                  if (cr != CoderResult.UNDERFLOW) {
                      throw new IOException("Encoding error");
          	}
              }
          

          which is similar to what we have in the 2.0's OutputBuffer.

          May be I'm missing something?

          Thaanks.

          Show
          oleksiys added a comment - - edited Hi Scott, in Grizzly 1.9.x' C2BConverter I see this code public void convert( char c[], int off, int len) throws IOException { CharBuffer cb = CharBuffer.wrap(c, off, len); byte [] barr = bb.getBuffer(); int boff = bb.getEnd(); ByteBuffer tmp = ByteBuffer.wrap(barr, boff, barr.length - boff); CoderResult cr = encoder.encode(cb, tmp, true ); bb.setEnd(tmp.position()); while (cr == CoderResult.OVERFLOW) { if (!bb.canGrow()) bb.flushBuffer(); boff = bb.getEnd(); barr = bb.getBuffer(); tmp = ByteBuffer.wrap(barr, boff, barr.length - boff); cr = encoder.encode(cb, tmp, true ); bb.setEnd(tmp.position()); } if (cr != CoderResult.UNDERFLOW) { throw new IOException( "Encoding error" ); } } which is similar to what we have in the 2.0's OutputBuffer. May be I'm missing something? Thaanks.
          Hide
          Ryan Lubke added a comment -

          Integrated Grizzly 2.2.10 (r54548).

          Should be available in this week's nightly build. Please let us know of any changes in performance.

          Show
          Ryan Lubke added a comment - Integrated Grizzly 2.2.10 (r54548). Should be available in this week's nightly build. Please let us know of any changes in performance.
          Hide
          Scott Oaks added a comment -

          The call path is now through encodeArrayLoop as expected, and we've made up a little bit of ground on the regression.

          Show
          Scott Oaks added a comment - The call path is now through encodeArrayLoop as expected, and we've made up a little bit of ground on the regression.
          Hide
          Ryan Lubke added a comment -

          Ok, closing this particular regression out.

          Show
          Ryan Lubke added a comment - Ok, closing this particular regression out.

            People

            • Assignee:
              Ryan Lubke
              Reporter:
              amitagarwal
            • Votes:
              0 Vote for this issue
              Watchers:
              0 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved: