3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-01-31 07:12:17 +00:00

Patch 9 from Steves lazy parsing patchset.

Add UnsafeByteArrayOutputStream.  ByteArrayOutputStreams are used extensively and result in a lot of short lived byte arrays.

This patch contains two optimizations

1/ attempt to provide the final length to ByteArrayOutputStream constructor to avoid constantly resizing the backing array.  Default size is 32 which means larger messages may require several array reallocations and almost all will require at least one.

2/ provides the UnsafeByteArrayOutputStream class which eliminates method synchronization.  The toByteArray() will return the backing array rather than a copy if the correct length was provided in the constructor.

In the worst case scenario this cuts array allocations from 3 to 2.
In the most common worst case from 3 to 1.
In most best cases where final array size is greater than 128 bytes from > 4 to 1.
This commit is contained in:
Mike Hearn 2011-10-14 12:37:27 +00:00
parent 27b6b5ab97
commit ee083d6fac
7 changed files with 125 additions and 11 deletions

4
TODO
View File

@ -31,7 +31,7 @@ Cleanup:
Impacts from Steves changes:
- Reformat affected files
- JavaDocs for the c'tors that take parseLazy/parseRetain
- Copyright header/correct author for EmptyMessage/ChildMessage
- Copyright header/correct author for EmptyMessage/ChildMessage/UnsafeByteArrayOutputStream
- Remove superfluous empty lines in ListMessage.java
- Remove VersionMessage check in Message serialization roundtrip checks
- Delete dead code in Message.checkParsed
@ -52,7 +52,7 @@ Impacts from Steves changes:
- Delete the code related to deduping. The new network management code to be checked in later makes it unnecessary.
Also remove singleDigest as a result (it duplicates Sha256Hash).
- More thoroughly review UnsafeByteArrayOutputStream
Block.java:
- Reformat

View File

@ -367,7 +367,7 @@ public class Block extends Message {
// At least one of the two cacheable components is invalid
// so fall back to stream write since we can't be sure of the length.
ByteArrayOutputStream stream = new ByteArrayOutputStream(length == UNKNOWN_LENGTH ? 80 + guessTransactionsLength() : length);
ByteArrayOutputStream stream = new UnsafeByteArrayOutputStream(length == UNKNOWN_LENGTH ? 80 + guessTransactionsLength() : length);
try {
writeHeader(stream);
writeTransactions(stream);
@ -442,7 +442,7 @@ public class Block extends Message {
*/
private Sha256Hash calculateHash() {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(80);
writeHeader(bos);
return new Sha256Hash(Utils.reverseBytes(doubleDigest(bos.toByteArray())));
} catch (IOException e) {

View File

@ -170,7 +170,8 @@ public class ECKey implements Serializable {
// of the type used by BitCoin we have to encode them using DER encoding, which is just a way to pack the two
// components into a structure.
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
//usually 70-72 bytes.
ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(72);
DERSequenceGenerator seq = new DERSequenceGenerator(bos);
seq.addObject(new DERInteger(sigs[0]));
seq.addObject(new DERInteger(sigs[1]));

View File

@ -270,7 +270,7 @@ public abstract class Message implements Serializable {
assert bytes == null : "cached bytes present but failed to use them for serialization";
//no cached array available so serialize parts by stream.
ByteArrayOutputStream stream = new ByteArrayOutputStream(length < 32 ? 32 : length + 32);
ByteArrayOutputStream stream = new UnsafeByteArrayOutputStream(length < 32 ? 32 : length + 32);
try {
bitcoinSerializeToStream(stream);
} catch (IOException e) {

View File

@ -254,7 +254,7 @@ public class Script {
static byte[] createOutputScript(Address to) {
try {
// TODO: Do this by creating a Script *first* then having the script reassemble itself into bytes.
ByteArrayOutputStream bits = new ByteArrayOutputStream();
ByteArrayOutputStream bits = new UnsafeByteArrayOutputStream(24);
bits.write(OP_DUP);
bits.write(OP_HASH160);
writeBytes(bits, to.getHash160());
@ -270,7 +270,7 @@ public class Script {
static byte[] createOutputScript(byte[] pubkey) {
try {
// TODO: Do this by creating a Script *first* then having the script reassemble itself into bytes.
ByteArrayOutputStream bits = new ByteArrayOutputStream();
ByteArrayOutputStream bits = new UnsafeByteArrayOutputStream(pubkey.length + 1);
writeBytes(bits, pubkey);
bits.write(OP_CHECKSIG);
return bits.toByteArray();
@ -282,7 +282,7 @@ public class Script {
static byte[] createInputScript(byte[] signature, byte[] pubkey) {
try {
// TODO: Do this by creating a Script *first* then having the script reassemble itself into bytes.
ByteArrayOutputStream bits = new ByteArrayOutputStream();
ByteArrayOutputStream bits = new UnsafeByteArrayOutputStream(signature.length + pubkey.length);
writeBytes(bits, signature);
writeBytes(bits, pubkey);
return bits.toByteArray();

View File

@ -528,7 +528,8 @@ public class Transaction extends ChildMessage implements Serializable {
// Now sign for the output so we can redeem it. We use the keypair to sign the hash,
// and then put the resulting signature in the script along with the public key (below).
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
//usually 71-73 bytes
ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(73);
bos.write(key.sign(hash));
bos.write((hashType.ordinal() + 1) | (anyoneCanPay ? 0x80 : 0)) ;
signatures[i] = bos.toByteArray();
@ -552,7 +553,7 @@ public class Transaction extends ChildMessage implements Serializable {
private byte[] hashTransactionForSignature(SigHash type, boolean anyoneCanPay) {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream(length == UNKNOWN_LENGTH ? 256 : length + 4);
ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(length == UNKNOWN_LENGTH ? 256 : length + 4);
bitcoinSerialize(bos);
// We also have to write a hash type.
int hashType = type.ordinal() + 1;

View File

@ -0,0 +1,112 @@
package com.google.bitcoin.core;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
/**
* An unsynchronized implementation of ByteArrayOutputStream that will return the backing byte array if its length == size().
* This avoids unneeded array copy where the BOS is simply being used to extract a byte array of known length from a
* 'serialized to stream' method.
*
* Unless the final length can be accurately predicted the only performance this will yield is due to unsynchronized
* methods.
* @author git
*
*/
public class UnsafeByteArrayOutputStream extends ByteArrayOutputStream {
public UnsafeByteArrayOutputStream() {
super(32);
}
public UnsafeByteArrayOutputStream(int size) {
super(size);
}
/**
* Writes the specified byte to this byte array output stream.
*
* @param b the byte to be written.
*/
public void write(int b) {
int newcount = count + 1;
if (newcount > buf.length) {
buf = Arrays.copyOf(buf, Math.max(buf.length << 1, newcount));
}
buf[count] = (byte)b;
count = newcount;
}
/**
* Writes <code>len</code> bytes from the specified byte array
* starting at offset <code>off</code> to this byte array output stream.
*
* @param b the data.
* @param off the start offset in the data.
* @param len the number of bytes to write.
*/
public void write(byte b[], int off, int len) {
if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
int newcount = count + len;
if (newcount > buf.length) {
buf = Arrays.copyOf(buf, Math.max(buf.length << 1, newcount));
}
System.arraycopy(b, off, buf, count, len);
count = newcount;
}
/**
* Writes the complete contents of this byte array output stream to
* the specified output stream argument, as if by calling the output
* stream's write method using <code>out.write(buf, 0, count)</code>.
*
* @param out the output stream to which to write the data.
* @exception IOException if an I/O error occurs.
*/
public void writeTo(OutputStream out) throws IOException {
out.write(buf, 0, count);
}
/**
* Resets the <code>count</code> field of this byte array output
* stream to zero, so that all currently accumulated output in the
* output stream is discarded. The output stream can be used again,
* reusing the already allocated buffer space.
*
* @see java.io.ByteArrayInputStream#count
*/
public void reset() {
count = 0;
}
/**
* Creates a newly allocated byte array. Its size is the current
* size of this output stream and the valid contents of the buffer
* have been copied into it.
*
* @return the current contents of this output stream, as a byte array.
* @see java.io.ByteArrayOutputStream#size()
*/
public byte toByteArray()[] {
return count == buf.length ? buf : Arrays.copyOf(buf, count);
}
/**
* Returns the current size of the buffer.
*
* @return the value of the <code>count</code> field, which is the number
* of valid bytes in this output stream.
* @see java.io.ByteArrayOutputStream#count
*/
public int size() {
return count;
}
}