Optimize the output production by reusing 16k buffers over and over.
By reusing over and over a buffer small enough to fit into the processor's L2 cache, we realize a huge savings on data transfer. This replaces the previous method of reading data entirely into huge byte[] and then writing it back in one shot. Experimentally, this is shown to be much faster.
Review by: spoon, fabbott
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5404 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/EmittedArtifact.java b/dev/core/src/com/google/gwt/core/ext/linker/EmittedArtifact.java
index a0a71c4..4bb578a 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/EmittedArtifact.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/EmittedArtifact.java
@@ -19,10 +19,11 @@
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.dev.util.Util;
+import com.google.gwt.util.tools.Utility;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
/**
* An artifact that will be emitted into the output. All EmittedArtifacts
@@ -50,20 +51,6 @@
}
/**
- * Provides access to the contents of the EmittedResource as a byte buffer.
- */
- public byte[] getBytes(TreeLogger logger) throws UnableToCompleteException {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- try {
- Util.copy(getContents(logger), baos);
- } catch (IOException e) {
- logger.log(TreeLogger.ERROR, "Unable to read stream", e);
- throw new UnableToCompleteException();
- }
- return baos.toByteArray();
- }
-
- /**
* Provides access to the contents of the EmittedResource.
*/
public abstract InputStream getContents(TreeLogger logger)
@@ -126,6 +113,21 @@
return getPartialPath();
}
+ /**
+ * Provides access to the contents of the EmittedResource.
+ */
+ public void writeTo(TreeLogger logger, OutputStream out)
+ throws UnableToCompleteException {
+ try {
+ InputStream in = getContents(logger);
+ Util.copyNoClose(in, out);
+ Utility.close(in);
+ } catch (IOException e) {
+ logger.log(TreeLogger.ERROR, "Unable to read or write stream", e);
+ throw new UnableToCompleteException();
+ }
+ }
+
@Override
protected final int compareToComparableArtifact(EmittedArtifact o) {
return getPartialPath().compareTo(o.getPartialPath());
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/SyntheticArtifact.java b/dev/core/src/com/google/gwt/core/ext/linker/SyntheticArtifact.java
index 7158bf5..9faa554 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/SyntheticArtifact.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/SyntheticArtifact.java
@@ -22,6 +22,7 @@
import java.io.ByteArrayInputStream;
import java.io.InputStream;
+import java.io.OutputStream;
/**
* Artifacts created by {@link AbstractLinker}.
@@ -46,11 +47,6 @@
}
@Override
- public byte[] getBytes(TreeLogger logger) throws UnableToCompleteException {
- return diskCache.readByteArray(token);
- }
-
- @Override
public InputStream getContents(TreeLogger logger)
throws UnableToCompleteException {
return new ByteArrayInputStream(diskCache.readByteArray(token));
@@ -60,4 +56,10 @@
public long getLastModified() {
return lastModified;
}
+
+ @Override
+ public void writeTo(TreeLogger logger, OutputStream out)
+ throws UnableToCompleteException {
+ diskCache.writeTo(token, out);
+ }
}
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardGeneratedResource.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardGeneratedResource.java
index f2aae42..c27a7af 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardGeneratedResource.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardGeneratedResource.java
@@ -24,7 +24,6 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.RandomAccessFile;
/**
* The standard implementation of {@link GeneratedResource}.
@@ -39,19 +38,6 @@
}
@Override
- public byte[] getBytes(TreeLogger logger) throws UnableToCompleteException {
- try {
- RandomAccessFile raf = new RandomAccessFile(file, "r");
- byte[] buf = new byte[(int) raf.length()];
- raf.readFully(buf);
- return buf;
- } catch (IOException e) {
- logger.log(TreeLogger.ERROR, "Unable to read file", e);
- throw new UnableToCompleteException();
- }
- }
-
- @Override
public InputStream getContents(TreeLogger logger)
throws UnableToCompleteException {
try {
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
index 2100ce0..fdebdbe 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
@@ -55,8 +55,8 @@
import com.google.gwt.dev.util.Util;
import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.RandomAccessFile;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
@@ -491,13 +491,11 @@
|| (outFile.lastModified() <= artifact.getLastModified())) {
mkdirs(outFile.getParentFile(), createdDirs);
try {
- RandomAccessFile raf = new RandomAccessFile(outFile, "rw");
- byte[] bytes = artifact.getBytes(artifactLogger);
- raf.setLength(bytes.length);
- raf.write(bytes);
- raf.close();
+ FileOutputStream out = new FileOutputStream(outFile);
+ artifact.writeTo(artifactLogger, out);
+ out.close();
} catch (IOException e) {
- logger.log(TreeLogger.ERROR, "Unable to create file '"
+ artifactLogger.log(TreeLogger.ERROR, "Unable to create file '"
+ outFile.getAbsolutePath() + "'", e);
throw new UnableToCompleteException();
}
@@ -529,10 +527,6 @@
Class<? extends Linker> linkerType) {
assert linkerShortNames.containsKey(linkerType) : linkerType.getName()
+ " unknown";
- File toReturn = new File(extraPath, linkerShortNames.get(linkerType));
- if (!toReturn.exists()) {
- toReturn.mkdirs();
- }
- return toReturn;
+ return new File(extraPath, linkerShortNames.get(linkerType));
}
}
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardPublicResource.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardPublicResource.java
index 09e79a7..eecad1e 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardPublicResource.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardPublicResource.java
@@ -25,6 +25,7 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.io.Serializable;
/**
@@ -47,11 +48,6 @@
}
@Override
- public byte[] getBytes(TreeLogger logger) throws UnableToCompleteException {
- return data;
- }
-
- @Override
public InputStream getContents(TreeLogger logger)
throws UnableToCompleteException {
return new ByteArrayInputStream(data);
@@ -61,6 +57,17 @@
public long getLastModified() {
return lastModified;
}
+
+ @Override
+ public void writeTo(TreeLogger logger, OutputStream out)
+ throws UnableToCompleteException {
+ try {
+ out.write(data);
+ } catch (IOException e) {
+ logger.log(TreeLogger.ERROR, "Unable to write to stream", e);
+ throw new UnableToCompleteException();
+ }
+ }
}
private final Resource resource;
diff --git a/dev/core/src/com/google/gwt/dev/util/DiskCache.java b/dev/core/src/com/google/gwt/dev/util/DiskCache.java
index 34568ea..0b5f991 100644
--- a/dev/core/src/com/google/gwt/dev/util/DiskCache.java
+++ b/dev/core/src/com/google/gwt/dev/util/DiskCache.java
@@ -19,6 +19,7 @@
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
+import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -86,7 +87,7 @@
* {@link #writeByteArray(byte[])}
* @return the bytes that were written
*/
- public byte[] readByteArray(long token) {
+ public synchronized byte[] readByteArray(long token) {
try {
atEnd = false;
file.seek(token);
@@ -128,7 +129,7 @@
*
* @return a handle to retrieve it later
*/
- public long writeByteArray(byte[] bytes) {
+ public synchronized long writeByteArray(byte[] bytes) {
try {
if (!atEnd) {
file.seek(file.length());
@@ -162,8 +163,36 @@
return writeByteArray(Util.getBytes(str));
}
+ /**
+ * Reads bytes of data back from disk and writes them into the specified
+ * output stream.
+ */
+ public synchronized void writeTo(long token, OutputStream out) {
+ byte[] buf = Util.takeThreadLocalBuf();
+ try {
+ atEnd = false;
+ file.seek(token);
+ int length = file.readInt();
+ int bufLen = buf.length;
+ while (length > bufLen) {
+ int read = file.read(buf, 0, bufLen);
+ length -= read;
+ out.write(buf, 0, read);
+ }
+ while (length > 0) {
+ int read = file.read(buf, 0, length);
+ length -= read;
+ out.write(buf, 0, read);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Unable to read from byte cache", e);
+ } finally {
+ Util.releaseThreadLocalBuf(buf);
+ }
+ }
+
@Override
- protected void finalize() throws Throwable {
+ protected synchronized void finalize() throws Throwable {
if (file != null) {
file.setLength(0);
file.close();
diff --git a/dev/core/src/com/google/gwt/dev/util/Util.java b/dev/core/src/com/google/gwt/dev/util/Util.java
index 1ce3b0a..8631513 100644
--- a/dev/core/src/com/google/gwt/dev/util/Util.java
+++ b/dev/core/src/com/google/gwt/dev/util/Util.java
@@ -83,6 +83,18 @@
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
'E', 'F'};
+ /**
+ * The size of a {@link #threadLocalBuf}, which should be large enough for
+ * efficient data transfer but small enough to fit easily into the L2 cache of
+ * most modern processors.
+ */
+ private static final int THREAD_LOCAL_BUF_SIZE = 16 * 1024;
+
+ /**
+ * Stores reusable thread local buffers for efficient data transfer.
+ */
+ private static final ThreadLocal<byte[]> threadLocalBuf = new ThreadLocal<byte[]>();
+
public static byte[] append(byte[] xs, byte x) {
int n = xs.length;
byte[] t = new byte[n + 1];
@@ -165,11 +177,7 @@
public static void copy(InputStream is, OutputStream os) throws IOException {
try {
- byte[] buf = new byte[32 * 1024];
- int i;
- while ((i = is.read(buf)) != -1) {
- os.write(buf, 0, i);
- }
+ copyNoClose(is, os);
} finally {
Utility.close(is);
Utility.close(os);
@@ -238,6 +246,23 @@
}
}
+ /**
+ * Copies all of the bytes from the input stream to the output stream until
+ * the input stream is EOF. Does not close either stream.
+ */
+ public static void copyNoClose(InputStream is, OutputStream os)
+ throws IOException {
+ byte[] buf = takeThreadLocalBuf();
+ try {
+ int i;
+ while ((i = is.read(buf)) != -1) {
+ os.write(buf, 0, i);
+ }
+ } finally {
+ releaseThreadLocalBuf(buf);
+ }
+ }
+
public static Reader createReader(TreeLogger logger, URL url)
throws UnableToCompleteException {
try {
@@ -277,7 +302,7 @@
}
}
-/**
+ /**
* Escapes '&', '<', '>', '"', and '\'' to their XML entity equivalents.
*/
public static String escapeXml(String unescaped) {
@@ -817,6 +842,15 @@
return toReturn;
}
+ /**
+ * Release a buffer previously returned from {@link #takeThreadLocalBuf()}.
+ * The released buffer may then be reused.
+ */
+ public static void releaseThreadLocalBuf(byte[] buf) {
+ assert buf.length == THREAD_LOCAL_BUF_SIZE;
+ threadLocalBuf.set(buf);
+ }
+
public static File removeExtension(File file) {
String name = file.getName();
int lastDot = name.lastIndexOf('.');
@@ -859,6 +893,24 @@
}
/**
+ * Get a large byte buffer local to this thread. Currently this is set to a
+ * 16k buffer, which is small enough to fit into the L2 cache on modern
+ * processors. The contents of the returned buffer are undefined. Calling
+ * {@link #releaseThreadLocalBuf(byte[])} on the returned buffer allows
+ * subsequent callers to reuse the buffer later, avoiding unncessary
+ * allocations and GC.
+ */
+ public static byte[] takeThreadLocalBuf() {
+ byte[] buf = threadLocalBuf.get();
+ if (buf == null) {
+ buf = new byte[THREAD_LOCAL_BUF_SIZE];
+ } else {
+ threadLocalBuf.set(null);
+ }
+ return buf;
+ }
+
+ /**
* Creates an array from a collection of the specified component type and
* size. You can definitely downcast the result to T[] if T is the specified
* component type.