409953 More efficient implementation of BufferUtil.writeTo(...)
This commit is contained in:
parent
5b60d42d3a
commit
7792c1dad7
|
@ -84,6 +84,7 @@ import java.nio.charset.Charset;
|
||||||
*/
|
*/
|
||||||
public class BufferUtil
|
public class BufferUtil
|
||||||
{
|
{
|
||||||
|
static final int TEMP_BUFFER_SIZE = 4096;
|
||||||
static final byte SPACE = 0x20;
|
static final byte SPACE = 0x20;
|
||||||
static final byte MINUS = '-';
|
static final byte MINUS = '-';
|
||||||
static final byte[] DIGIT =
|
static final byte[] DIGIT =
|
||||||
|
@ -96,7 +97,7 @@ public class BufferUtil
|
||||||
/** Allocate ByteBuffer in flush mode.
|
/** Allocate ByteBuffer in flush mode.
|
||||||
* The position and limit will both be zero, indicating that the buffer is
|
* The position and limit will both be zero, indicating that the buffer is
|
||||||
* empty and must be flipped before any data is put to it.
|
* empty and must be flipped before any data is put to it.
|
||||||
* @param capacity
|
* @param capacity capacity of the allocated ByteBuffer
|
||||||
* @return Buffer
|
* @return Buffer
|
||||||
*/
|
*/
|
||||||
public static ByteBuffer allocate(int capacity)
|
public static ByteBuffer allocate(int capacity)
|
||||||
|
@ -110,7 +111,7 @@ public class BufferUtil
|
||||||
/** Allocate ByteBuffer in flush mode.
|
/** Allocate ByteBuffer in flush mode.
|
||||||
* The position and limit will both be zero, indicating that the buffer is
|
* The position and limit will both be zero, indicating that the buffer is
|
||||||
* empty and in flush mode.
|
* empty and in flush mode.
|
||||||
* @param capacity
|
* @param capacity capacity of the allocated ByteBuffer
|
||||||
* @return Buffer
|
* @return Buffer
|
||||||
*/
|
*/
|
||||||
public static ByteBuffer allocateDirect(int capacity)
|
public static ByteBuffer allocateDirect(int capacity)
|
||||||
|
@ -264,7 +265,7 @@ public class BufferUtil
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/** Get the space from the limit to the capacity
|
/** Get the space from the limit to the capacity
|
||||||
* @param buffer
|
* @param buffer the buffer to get the space from
|
||||||
* @return space
|
* @return space
|
||||||
*/
|
*/
|
||||||
public static int space(ByteBuffer buffer)
|
public static int space(ByteBuffer buffer)
|
||||||
|
@ -276,7 +277,7 @@ public class BufferUtil
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/** Compact the buffer
|
/** Compact the buffer
|
||||||
* @param buffer
|
* @param buffer the buffer to compact
|
||||||
* @return true if the compact made a full buffer have space
|
* @return true if the compact made a full buffer have space
|
||||||
*/
|
*/
|
||||||
public static boolean compact(ByteBuffer buffer)
|
public static boolean compact(ByteBuffer buffer)
|
||||||
|
@ -404,7 +405,7 @@ public class BufferUtil
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
public static void readFrom(File file, ByteBuffer buffer) throws IOException
|
public static void readFrom(File file, ByteBuffer buffer) throws IOException
|
||||||
{
|
{
|
||||||
try(RandomAccessFile raf = new RandomAccessFile(file,"r");)
|
try(RandomAccessFile raf = new RandomAccessFile(file,"r"))
|
||||||
{
|
{
|
||||||
FileChannel channel = raf.getChannel();
|
FileChannel channel = raf.getChannel();
|
||||||
long needed=raf.length();
|
long needed=raf.length();
|
||||||
|
@ -437,9 +438,19 @@ public class BufferUtil
|
||||||
out.write(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());
|
out.write(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// TODO this is horribly inefficient
|
byte[] bytes = new byte[TEMP_BUFFER_SIZE];
|
||||||
|
int j = 0;
|
||||||
for (int i = buffer.position(); i < buffer.limit(); i++)
|
for (int i = buffer.position(); i < buffer.limit(); i++)
|
||||||
out.write(buffer.get(i));
|
{
|
||||||
|
bytes[j] = buffer.get(i);
|
||||||
|
j++;
|
||||||
|
if (j == TEMP_BUFFER_SIZE)
|
||||||
|
{
|
||||||
|
out.write(bytes);
|
||||||
|
j = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.write(bytes, 0, j);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -615,9 +626,9 @@ public class BufferUtil
|
||||||
{
|
{
|
||||||
boolean started = false;
|
boolean started = false;
|
||||||
// This assumes constant time int arithmatic
|
// This assumes constant time int arithmatic
|
||||||
for (int i = 0; i < hexDivisors.length; i++)
|
for (int hexDivisor : hexDivisors)
|
||||||
{
|
{
|
||||||
if (n < hexDivisors[i])
|
if (n < hexDivisor)
|
||||||
{
|
{
|
||||||
if (started)
|
if (started)
|
||||||
buffer.put((byte)'0');
|
buffer.put((byte)'0');
|
||||||
|
@ -625,9 +636,9 @@ public class BufferUtil
|
||||||
}
|
}
|
||||||
|
|
||||||
started = true;
|
started = true;
|
||||||
int d = n / hexDivisors[i];
|
int d = n / hexDivisor;
|
||||||
buffer.put(DIGIT[d]);
|
buffer.put(DIGIT[d]);
|
||||||
n = n - d * hexDivisors[i];
|
n = n - d * hexDivisor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -656,9 +667,9 @@ public class BufferUtil
|
||||||
{
|
{
|
||||||
boolean started = false;
|
boolean started = false;
|
||||||
// This assumes constant time int arithmatic
|
// This assumes constant time int arithmatic
|
||||||
for (int i = 0; i < decDivisors.length; i++)
|
for (int decDivisor : decDivisors)
|
||||||
{
|
{
|
||||||
if (n < decDivisors[i])
|
if (n < decDivisor)
|
||||||
{
|
{
|
||||||
if (started)
|
if (started)
|
||||||
buffer.put((byte)'0');
|
buffer.put((byte)'0');
|
||||||
|
@ -666,9 +677,9 @@ public class BufferUtil
|
||||||
}
|
}
|
||||||
|
|
||||||
started = true;
|
started = true;
|
||||||
int d = n / decDivisors[i];
|
int d = n / decDivisor;
|
||||||
buffer.put(DIGIT[d]);
|
buffer.put(DIGIT[d]);
|
||||||
n = n - d * decDivisors[i];
|
n = n - d * decDivisor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -696,9 +707,9 @@ public class BufferUtil
|
||||||
{
|
{
|
||||||
boolean started = false;
|
boolean started = false;
|
||||||
// This assumes constant time int arithmatic
|
// This assumes constant time int arithmatic
|
||||||
for (int i = 0; i < decDivisorsL.length; i++)
|
for (long aDecDivisorsL : decDivisorsL)
|
||||||
{
|
{
|
||||||
if (n < decDivisorsL[i])
|
if (n < aDecDivisorsL)
|
||||||
{
|
{
|
||||||
if (started)
|
if (started)
|
||||||
buffer.put((byte)'0');
|
buffer.put((byte)'0');
|
||||||
|
@ -706,9 +717,9 @@ public class BufferUtil
|
||||||
}
|
}
|
||||||
|
|
||||||
started = true;
|
started = true;
|
||||||
long d = n / decDivisorsL[i];
|
long d = n / aDecDivisorsL;
|
||||||
buffer.put(DIGIT[(int)d]);
|
buffer.put(DIGIT[(int)d]);
|
||||||
n = n - d * decDivisorsL[i];
|
n = n - d * aDecDivisorsL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -785,7 +796,7 @@ public class BufferUtil
|
||||||
|
|
||||||
public static ByteBuffer toBuffer(File file) throws IOException
|
public static ByteBuffer toBuffer(File file) throws IOException
|
||||||
{
|
{
|
||||||
try (RandomAccessFile raf = new RandomAccessFile(file, "r");)
|
try (RandomAccessFile raf = new RandomAccessFile(file, "r"))
|
||||||
{
|
{
|
||||||
return raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length());
|
return raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length());
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,15 +19,22 @@
|
||||||
package org.eclipse.jetty.util;
|
package org.eclipse.jetty.util;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
import java.nio.BufferOverflowException;
|
import java.nio.BufferOverflowException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
public class BufferUtilTest
|
public class BufferUtilTest
|
||||||
|
@ -226,4 +233,65 @@ public class BufferUtilTest
|
||||||
|
|
||||||
Assert.assertEquals("Count of bytes",length,count);
|
Assert.assertEquals("Count of bytes",length,count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final Logger LOG = Log.getLogger(BufferUtilTest.class);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Ignore("Very simple microbenchmark to compare different writeTo implementations. Only for development thus " +
|
||||||
|
"ignored.")
|
||||||
|
public void testWriteToMicrobenchmark() throws IOException
|
||||||
|
{
|
||||||
|
int capacity = 1024 * 1024;
|
||||||
|
int iterations = 30;
|
||||||
|
byte[] bytes = new byte[capacity];
|
||||||
|
new Random().nextBytes(bytes);
|
||||||
|
ByteBuffer buffer = BufferUtil.allocate(capacity);
|
||||||
|
BufferUtil.append(buffer, bytes, 0, capacity);
|
||||||
|
long start = System.nanoTime();
|
||||||
|
for (int i = 0; i < iterations; i++)
|
||||||
|
{
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
long startRun = System.nanoTime();
|
||||||
|
BufferUtil.writeTo(buffer.asReadOnlyBuffer(), out);
|
||||||
|
long elapsedRun = System.nanoTime() - startRun;
|
||||||
|
LOG.warn("run elapsed={}ms", elapsedRun / 1000);
|
||||||
|
assertThat("Bytes in out equal bytes in buffer", Arrays.equals(bytes, out.toByteArray()), is(true));
|
||||||
|
}
|
||||||
|
long elapsed = System.nanoTime() - start;
|
||||||
|
LOG.warn("elapsed={}ms average={}ms", elapsed / 1000, elapsed/iterations/1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWriteToWithBufferThatDoesNotExposeArrayAndSmallContent() throws IOException
|
||||||
|
{
|
||||||
|
int capacity = BufferUtil.TEMP_BUFFER_SIZE/4;
|
||||||
|
testWriteToWithBufferThatDoesNotExposeArray(capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWriteToWithBufferThatDoesNotExposeArrayAndContentLengthMatchingTempBufferSize() throws IOException
|
||||||
|
{
|
||||||
|
int capacity = BufferUtil.TEMP_BUFFER_SIZE;
|
||||||
|
testWriteToWithBufferThatDoesNotExposeArray(capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWriteToWithBufferThatDoesNotExposeArrayAndContentSlightlyBiggerThanTwoTimesTempBufferSize()
|
||||||
|
throws
|
||||||
|
IOException
|
||||||
|
{
|
||||||
|
int capacity = BufferUtil.TEMP_BUFFER_SIZE*2+1024;
|
||||||
|
testWriteToWithBufferThatDoesNotExposeArray(capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testWriteToWithBufferThatDoesNotExposeArray(int capacity) throws IOException
|
||||||
|
{
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
byte[] bytes = new byte[capacity];
|
||||||
|
new Random().nextBytes(bytes);
|
||||||
|
ByteBuffer buffer = BufferUtil.allocate(capacity);
|
||||||
|
BufferUtil.append(buffer, bytes, 0, capacity);
|
||||||
|
BufferUtil.writeTo(buffer.asReadOnlyBuffer(), out);
|
||||||
|
assertThat("Bytes in out equal bytes in buffer", Arrays.equals(bytes, out.toByteArray()), is(true));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue