Browse Source

Make type and size lazy for large delta objects

Callers don't necessarily need the getSize() result from a large
delta.  They instead should be always using openStream() or copyTo()
for blobs going to local files, or they should be checking the
result of the constant-time isLarge() method to determine the type
of access they can use on the ObjectLoader.  Avoid inflating the
delta instruction stream twice by delaying the decoding of the size
until after we have created the DeltaStream and decoded the header.

Likewise with the type, callers don't necessarily always need it
to be present in an ObjectLoader.  Delay looking at it as late as
we can, thereby avoiding an ugly O(N^2) loop looking up the type
for every single object in the entire delta chain.

Change-Id: I6487b75b52a5d201d811a8baed2fb4fcd6431320
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
stable-0.9
Shawn O. Pearce 15 years ago
parent
commit
e4a480f658
  1. 94
      org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LargePackedDeltaObject.java
  2. 17
      org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java

94
org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LargePackedDeltaObject.java

@ -46,20 +46,25 @@ package org.eclipse.jgit.storage.file;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.zip.DataFormatException;
import java.util.zip.InflaterInputStream; import java.util.zip.InflaterInputStream;
import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.LargeObjectException; import org.eclipse.jgit.errors.LargeObjectException;
import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectStream; import org.eclipse.jgit.lib.ObjectStream;
import org.eclipse.jgit.storage.pack.BinaryDelta;
import org.eclipse.jgit.storage.pack.DeltaStream; import org.eclipse.jgit.storage.pack.DeltaStream;
class LargePackedDeltaObject extends ObjectLoader { class LargePackedDeltaObject extends ObjectLoader {
private final int type; private static final long SIZE_UNKNOWN = -1;
private final long size; private int type;
private long size;
private final long objectOffset; private final long objectOffset;
@ -71,11 +76,11 @@ class LargePackedDeltaObject extends ObjectLoader {
private final FileObjectDatabase db; private final FileObjectDatabase db;
LargePackedDeltaObject(int type, long size, long objectOffset, LargePackedDeltaObject(long objectOffset,
long baseOffset, int headerLength, PackFile pack, long baseOffset, int headerLength, PackFile pack,
FileObjectDatabase db) { FileObjectDatabase db) {
this.type = type; this.type = Constants.OBJ_BAD;
this.size = size; this.size = SIZE_UNKNOWN;
this.objectOffset = objectOffset; this.objectOffset = objectOffset;
this.baseOffset = baseOffset; this.baseOffset = baseOffset;
this.headerLength = headerLength; this.headerLength = headerLength;
@ -85,11 +90,58 @@ class LargePackedDeltaObject extends ObjectLoader {
@Override @Override
public int getType() { public int getType() {
if (type == Constants.OBJ_BAD) {
WindowCursor wc = new WindowCursor(db);
try {
type = pack.getObjectType(wc, objectOffset);
} catch (IOException packGone) {
// If the pack file cannot be pinned into the cursor, it
// probably was repacked recently. Go find the object
// again and get the type from that location instead.
//
try {
type = wc.open(getObjectId()).getType();
} catch (IOException packGone2) {
// "He's dead, Jim." We just can't discover the type
// and the interface isn't supposed to be lazy here.
// Report an invalid type code instead, callers will
// wind up bailing out with an error at some point.
}
} finally {
wc.release();
}
}
return type; return type;
} }
@Override @Override
public long getSize() { public long getSize() {
if (size == SIZE_UNKNOWN) {
WindowCursor wc = new WindowCursor(db);
try {
byte[] b = pack.getDeltaHeader(wc, objectOffset + headerLength);
size = BinaryDelta.getResultSize(b);
} catch (DataFormatException objectCorrupt) {
// The zlib stream for the delta is corrupt. We probably
// cannot access the object. Keep the size negative and
// report that bogus result to the caller.
} catch (IOException packGone) {
// If the pack file cannot be pinned into the cursor, it
// probably was repacked recently. Go find the object
// again and get the size from that location instead.
//
try {
size = wc.open(getObjectId()).getSize();
} catch (IOException packGone2) {
// "He's dead, Jim." We just can't discover the size
// and the interface isn't supposed to be lazy here.
// Report an invalid type code instead, callers will
// wind up bailing out with an error at some point.
}
} finally {
wc.release();
}
}
return size; return size;
} }
@ -112,7 +164,7 @@ class LargePackedDeltaObject extends ObjectLoader {
final WindowCursor wc = new WindowCursor(db); final WindowCursor wc = new WindowCursor(db);
InputStream in = open(wc); InputStream in = open(wc);
in = new BufferedInputStream(in, 8192); in = new BufferedInputStream(in, 8192);
return new ObjectStream.Filter(type, size, in) { return new ObjectStream.Filter(getType(), size, in) {
@Override @Override
public void close() throws IOException { public void close() throws IOException {
wc.release(); wc.release();
@ -132,24 +184,44 @@ class LargePackedDeltaObject extends ObjectLoader {
// probably was repacked recently. Go find the object // probably was repacked recently. Go find the object
// again and open the stream from that location instead. // again and open the stream from that location instead.
// //
return wc.open(getObjectId(), type).openStream(); return wc.open(getObjectId()).openStream();
} }
delta = new InflaterInputStream(delta); delta = new InflaterInputStream(delta);
final ObjectLoader base = pack.load(wc, baseOffset); final ObjectLoader base = pack.load(wc, baseOffset);
return new DeltaStream(delta) { DeltaStream ds = new DeltaStream(delta) {
private long baseSize = SIZE_UNKNOWN;
@Override @Override
protected InputStream openBase() throws IOException { protected InputStream openBase() throws IOException {
InputStream in;
if (base instanceof LargePackedDeltaObject) if (base instanceof LargePackedDeltaObject)
return ((LargePackedDeltaObject) base).open(wc); in = ((LargePackedDeltaObject) base).open(wc);
return base.openStream(); else
in = base.openStream();
if (baseSize == SIZE_UNKNOWN) {
if (in instanceof DeltaStream)
baseSize = ((DeltaStream) in).getSize();
else if (in instanceof ObjectStream)
baseSize = ((ObjectStream) in).getSize();
}
return in;
} }
@Override @Override
protected long getBaseSize() throws IOException { protected long getBaseSize() throws IOException {
return base.getSize(); if (baseSize == SIZE_UNKNOWN) {
// This code path should never be used as DeltaStream
// is supposed to open the stream first, which would
// initialize the size for us directly from the stream.
baseSize = base.getSize();
}
return baseSize;
} }
}; };
if (size == SIZE_UNKNOWN)
size = ds.getSize();
return ds;
} }
private ObjectId getObjectId() throws IOException { private ObjectId getObjectId() throws IOException {

17
org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java

@ -688,10 +688,8 @@ public class PackFile implements Iterable<PackIndex.MutableEntry> {
// that implies the resulting object is going to be massive. // that implies the resulting object is going to be massive.
// Use only the large delta format here. // Use only the large delta format here.
// //
byte[] hdr = getDeltaHeader(posSelf + hdrLen, curs); return new LargePackedDeltaObject(posSelf, posBase, hdrLen, //
return new LargePackedDeltaObject(getObjectType(curs, posBase), // this, curs.db);
BinaryDelta.getResultSize(hdr), //
posSelf, posBase, hdrLen, this, curs.db);
} }
byte[] data; byte[] data;
@ -707,10 +705,8 @@ public class PackFile implements Iterable<PackIndex.MutableEntry> {
// The base itself is large. We have to produce a large // The base itself is large. We have to produce a large
// delta stream as we don't want to build the whole base. // delta stream as we don't want to build the whole base.
// //
byte[] hdr = getDeltaHeader(posSelf + hdrLen, curs); return new LargePackedDeltaObject(posSelf, posBase, hdrLen,
return new LargePackedDeltaObject(getObjectType(curs, posBase), this, curs.db);
BinaryDelta.getResultSize(hdr), //
posSelf, posBase, hdrLen, this, curs.db);
} }
data = p.getCachedBytes(); data = p.getCachedBytes();
type = p.getType(); type = p.getType();
@ -727,7 +723,7 @@ public class PackFile implements Iterable<PackIndex.MutableEntry> {
return new ObjectLoader.SmallObject(type, data); return new ObjectLoader.SmallObject(type, data);
} }
private byte[] getDeltaHeader(long pos, WindowCursor wc) byte[] getDeltaHeader(WindowCursor wc, long pos)
throws IOException, DataFormatException { throws IOException, DataFormatException {
// The delta stream starts as two variable length integers. If we // The delta stream starts as two variable length integers. If we
// assume they are 64 bits each, we need 16 bytes to encode them, // assume they are 64 bits each, we need 16 bytes to encode them,
@ -739,8 +735,7 @@ public class PackFile implements Iterable<PackIndex.MutableEntry> {
return hdr; return hdr;
} }
private int getObjectType(final WindowCursor curs, long pos) int getObjectType(final WindowCursor curs, long pos) throws IOException {
throws IOException {
final byte[] ib = curs.tempId; final byte[] ib = curs.tempId;
for (;;) { for (;;) {
readFully(pos, ib, 0, 20, curs); readFully(pos, ib, 0, 20, curs);

Loading…
Cancel
Save