Presize TemporaryBuffer.Heap to a reasonable size

JGit's TemporaryBuffer.Heap recently gained an estimatedSize argument
used for presizing the backing block pointer list. Without this
argument, it assumes callers are going to use the entire maximum size,
and allocates an array large enough to hold enough pointers to
8192-byte blocks to fill the maximum size.

This is pathologically wasteful in RestApiServlet, where we might
allocate one or more buffers with size Integer.MAX_VALUE just to store
JSON serialization results. This allocates an array with over 250k
elements, taking about 2M of memory which is immediately made garbage.
On *every* request.

Most JSON responses are small. We can even be pretty liberal and guess
that they are under 10 blocks (80 KiB) in size, costing us only 10
object pointers (<100 bytes) worth of memory.

Change-Id: I9e1806737706ab2a24693455d64caa7d1067af35
This commit is contained in:
Dave Borowitz
2015-03-18 14:29:28 -07:00
parent 9d89ae88e2
commit 5bcf15cc1e
2 changed files with 18 additions and 8 deletions

View File

@@ -142,6 +142,8 @@ public class RestApiServlet extends HttpServlet {
private static final String JSON_TYPE = "application/json"; private static final String JSON_TYPE = "application/json";
private static final String FORM_TYPE = "application/x-www-form-urlencoded"; private static final String FORM_TYPE = "application/x-www-form-urlencoded";
private static final int HEAP_EST_SIZE = 10 * 8 * 1024; // Presize 10 blocks.
/** /**
* Garbage prefix inserted before JSON output to prevent XSSI. * Garbage prefix inserted before JSON output to prevent XSSI.
* <p> * <p>
@@ -656,7 +658,7 @@ public class RestApiServlet extends HttpServlet {
Multimap<String, String> config, Multimap<String, String> config,
Object result) Object result)
throws IOException { throws IOException {
TemporaryBuffer.Heap buf = heap(Integer.MAX_VALUE); TemporaryBuffer.Heap buf = heap(HEAP_EST_SIZE, Integer.MAX_VALUE);
buf.write(JSON_MAGIC); buf.write(JSON_MAGIC);
Writer w = new BufferedWriter(new OutputStreamWriter(buf, UTF_8)); Writer w = new BufferedWriter(new OutputStreamWriter(buf, UTF_8));
Gson gson = newGson(config, req); Gson gson = newGson(config, req);
@@ -781,7 +783,7 @@ public class RestApiServlet extends HttpServlet {
private static BinaryResult stackJsonString(HttpServletResponse res, private static BinaryResult stackJsonString(HttpServletResponse res,
final BinaryResult src) throws IOException { final BinaryResult src) throws IOException {
TemporaryBuffer.Heap buf = heap(Integer.MAX_VALUE); TemporaryBuffer.Heap buf = heap(HEAP_EST_SIZE, Integer.MAX_VALUE);
buf.write(JSON_MAGIC); buf.write(JSON_MAGIC);
try(Writer w = new BufferedWriter(new OutputStreamWriter(buf, UTF_8)); try(Writer w = new BufferedWriter(new OutputStreamWriter(buf, UTF_8));
JsonWriter json = new JsonWriter(w)) { JsonWriter json = new JsonWriter(w)) {
@@ -1053,10 +1055,15 @@ public class RestApiServlet extends HttpServlet {
return false; return false;
} }
private static int base64MaxSize(long n) {
return 4 * IntMath.divide((int) n, 3, CEILING);
}
private static BinaryResult base64(BinaryResult bin) private static BinaryResult base64(BinaryResult bin)
throws IOException { throws IOException {
int max = 4 * IntMath.divide((int) bin.getContentLength(), 3, CEILING); int maxSize = base64MaxSize(bin.getContentLength());
TemporaryBuffer.Heap buf = heap(max); int estSize = Math.min(base64MaxSize(HEAP_EST_SIZE), maxSize);
TemporaryBuffer.Heap buf = heap(estSize, maxSize);
OutputStream encoded = BaseEncoding.base64().encodingStream( OutputStream encoded = BaseEncoding.base64().encodingStream(
new OutputStreamWriter(buf, ISO_8859_1)); new OutputStreamWriter(buf, ISO_8859_1));
bin.writeTo(encoded); bin.writeTo(encoded);
@@ -1066,7 +1073,7 @@ public class RestApiServlet extends HttpServlet {
private static BinaryResult compress(BinaryResult bin) private static BinaryResult compress(BinaryResult bin)
throws IOException { throws IOException {
TemporaryBuffer.Heap buf = heap(20 << 20); TemporaryBuffer.Heap buf = heap(HEAP_EST_SIZE, 20 << 20);
GZIPOutputStream gz = new GZIPOutputStream(buf); GZIPOutputStream gz = new GZIPOutputStream(buf);
bin.writeTo(gz); bin.writeTo(gz);
gz.close(); gz.close();
@@ -1083,8 +1090,8 @@ public class RestApiServlet extends HttpServlet {
}.setContentLength(buf.length()); }.setContentLength(buf.length());
} }
private static Heap heap(int max) { private static Heap heap(int est, int max) {
return new TemporaryBuffer.Heap(max); return new TemporaryBuffer.Heap(est, max);
} }
@SuppressWarnings("serial") @SuppressWarnings("serial")

View File

@@ -379,6 +379,8 @@ public abstract class ChangeEmail extends NotificationEmail {
return args.settings.includeDiff; return args.settings.includeDiff;
} }
private static int HEAP_EST_SIZE = 32 * 1024;
/** Show patch set as unified difference. */ /** Show patch set as unified difference. */
public String getUnifiedDiff() { public String getUnifiedDiff() {
PatchList patchList; PatchList patchList;
@@ -394,8 +396,9 @@ public abstract class ChangeEmail extends NotificationEmail {
return ""; return "";
} }
int maxSize = args.settings.maximumDiffSize;
TemporaryBuffer.Heap buf = TemporaryBuffer.Heap buf =
new TemporaryBuffer.Heap(args.settings.maximumDiffSize); new TemporaryBuffer.Heap(Math.min(HEAP_EST_SIZE, maxSize), maxSize);
try (DiffFormatter fmt = new DiffFormatter(buf)) { try (DiffFormatter fmt = new DiffFormatter(buf)) {
Repository git; Repository git;
try { try {