From: Junio C Hamano <hidden> Date: 2016-06-15 22:51:15
Traditionally, git always read the full contents of an object in memory
before performing various operations on it, e.g. comparing for diff,
writing it to the working tree, etc. A huge blob that you cannot fit
in memory was very cumbersome to handle.
Recently "diff" learned to avoid reading the contents only to say "Binary
files differ" when these large blobs are marked as binary. Also there is a
topic cooking to teach "git add" to stream a large file directly to a
packfile without keeping the whole thing in core.
The "checkout" codepath is to learn the trick next, and this is the series
to attempt to do so. These would apply cleanly on top of three other
topics still in 'next' or 'pu', namely:
- jc/convert that cleans up the conversion;
- jc/replacing that cleans up the object replacement;
- jc/bigfile that teaches "git add" to handle large files.
Patch 1 and 5 are trivial clean-ups and refactoring. These could be
separated out of the series and applied much earlier, but nothing other
than this series directly benefit from these changes, so they are here in
the series.
Patch 2, 3, and 4 enhances the sha1_file layer.
Patch 6 introduces a new API that takes an object name and gives back a
"handle" you can read from (think: FILE *) the contents of the object.
The implementation at this step is deliberately kept simple: it just calls
read_sha1_file() to read everything in memory.
Patch 7 then uses the new API in the "git checkout" codepath, namely, in
entry.c::write_entry() function. At this point, any blob that does not
require smudge filters including crlf conversion would pass through this
new codepath and used the 'incore' case of the streaming API, which means
that (1) "hold everything in memory and process" limitation is not lifted
yet, and that (2) breakage detected in here would have meant either the
simple 'incore' implementation of the streaming API is broken (not likely),
or its caller streaming_write_entry() is broken (more likely).
Patch 8 teaches the new write-out codepath to detect and make holes in the
resulting file. This is primarily meant to help testing---when you add a
large test file that weighs 1GB with "git add" (see how it is done in the
test t/t1050-large.sh on jc/bigfile topic) and check it out, you do not
want to end up with 1GB file fully populated with real blocks in your
working tree.
Patch 9 teaches the streaming API how to read a non-delta object directly
from packfile, without holding the entire result in the memory. This is
the representation jc/bigfile topic creates for a huge file, and the
primary interest of this topic.
Patch 10 and 11 teaches the streaming API how to read a loose object,
without holding the entire result in the memory. This is not strictly
necessary for the purpose of handling the output from jc/bigfile, but not
having to hold everything in core by itself may be a plus.
Interested parties may want to measure the performance impact of the last
three patches. The series deliberately ignores core.bigfileThreashold and
let small and large blobs alike go through the streaming_write_entry()
codepath, but it _might_ turn out that we would want to use the new code
only for large-ish blobs.
Junio C Hamano (11):
packed_object_info_detail(): do not return a string
sha1_object_info_extended(): expose a bit more info
sha1_object_info_extended(): hint about objects in delta-base cache
unpack_object_header(): make it public
write_entry(): separate two helper functions out
streaming: a new API to read from the object store
streaming_write_entry(): use streaming API in write_entry()
streaming_write_entry(): support files with holes
streaming: read non-delta incrementally from a pack
sha1_file.c: expose helpers to read loose objects
streaming: read loose objects incrementally
Makefile | 2 +
builtin/verify-pack.c | 4 +-
cache.h | 36 +++++-
convert.c | 23 +++
entry.c | 111 ++++++++++++---
sha1_file.c | 71 ++++++++--
streaming.c | 376 +++++++++++++++++++++++++++++++++++++++++++++++++
streaming.h | 12 ++
8 files changed, 600 insertions(+), 35 deletions(-)
create mode 100644 streaming.c
create mode 100644 streaming.h
--
1.7.5.1.365.g32b65
From: Junio C Hamano <hidden> Date: 2016-06-15 22:51:15
Instead return enum object_name just like everybody else does.
The caller can turn it into a string with typename() easily.
Signed-off-by: Junio C Hamano <redacted>
---
builtin/verify-pack.c | 4 ++--
cache.h | 2 +-
sha1_file.c | 4 ++--
3 files changed, 5 insertions(+), 5 deletions(-)
From: Junio C Hamano <hidden> Date: 2016-06-15 22:51:15
Given an object name, use open_istream() to get a git_istream handle
that you can read_istream() from as if you are using read(2) to read
the contents of the object, and close it with close_istream() when
you are done.
Currently, we do not do anything fancy--it just calls read_sha1_file()
and keeps the contents in memory as a whole, and carve it out as you
request with read_istream().
Signed-off-by: Junio C Hamano <redacted>
---
Makefile | 2 +
streaming.c | 196 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
streaming.h | 12 ++++
3 files changed, 210 insertions(+), 0 deletions(-)
create mode 100644 streaming.c
create mode 100644 streaming.h
@@ -0,0 +1,196 @@+#include"cache.h"+#include"streaming.h"++enuminput_source{+stream_error=-1,+incore=0,+loose=1,+pack_non_delta=2+};++typedefint(*open_istream_fn)(structgit_istream*,+structobject_info*,constunsignedchar*,+enumobject_type*,unsignedlong*);+typedefint(*close_istream_fn)(structgit_istream*);+typedefssize_t(*read_istream_fn)(structgit_istream*,char*,size_t);++structstream_vtbl{+close_istream_fnclose;+read_istream_fnread;+};++#define open_method_decl(name) \+intopen_istream_##name\+(structgit_istream*st,structobject_info*oi,\+constunsignedchar*sha1,\+enumobject_type*type,unsignedlong*sz)++#define close_method_decl(name) \+intclose_istream_##name\+(structgit_istream*st)++#define read_method_decl(name) \+ssize_tread_istream_##name\+(structgit_istream*st,char*buf,size_tsz)++/* forward declaration */+staticopen_method_decl(incore);+staticopen_method_decl(loose);+staticopen_method_decl(pack_non_delta);++staticopen_istream_fnopen_istream_tbl[]={+open_istream_incore,+open_istream_loose,+open_istream_pack_non_delta,+};++structgit_istream{+enuminput_sourcesource;+conststructstream_vtbl*vtbl;+union{+struct{+char*buf;/* from read_object() */+unsignedlongsz;+unsignedlongread_ptr;+}incore;++struct{+intfd;/* open for reading */+/* NEEDSWORK: what else? */+}loose;++struct{+intfd;/* open for reading */+/* NEEDSWORK: what else? */+}in_pack;+}u;+};++intclose_istream(structgit_istream*st)+{+returnst->vtbl->close(st);+}++ssize_tread_istream(structgit_istream*st,char*buf,size_tsz)+{+returnst->vtbl->read(st,buf,sz);+}++staticenuminput_sourceistream_source(constunsignedchar*sha1,+enumobject_type*type,+structobject_info*oi)+{+unsignedlongsize;+intstatus;++oi->sizep=&size;+oi->want_deltainfo=1;++status=sha1_object_info_extended(sha1,oi);+if(status<0)+returnstream_error;+*type=status;++switch(oi->whence){+caseOI_LOOSE:+returnloose;+caseOI_PACKED:+if(!oi->u.packed.delta&&big_file_threshold<=size)+returnpack_non_delta;+/* fallthru */+default:+returnincore;+}+}++structgit_istream*open_istream(constunsignedchar*sha1,+enumobject_type*type,+unsignedlong*sz)+{+structgit_istream*st;+structobject_infooi;+constunsignedchar*real=lookup_replace_object(sha1);+enuminput_sourcesrc=istream_source(real,type,&oi);++if(src<0)+returnNULL;++st=xmalloc(sizeof(*st));+st->source=src;+if(open_istream_tbl[src](st,&oi,real,type,sz)){+if(open_istream_incore(st,&oi,real,type,sz)){+free(st);+st=NULL;+}+}+returnst;+}++/*****************************************************************+*+*Looseobjectstream+*+*****************************************************************/++staticopen_method_decl(loose)+{+return-1;/* for now */+}+++/*****************************************************************+*+*Non-deltapackedobjectstream+*+*****************************************************************/++staticopen_method_decl(pack_non_delta)+{+return-1;/* for now */+}+++/*****************************************************************+*+*In-corestream+*+*****************************************************************/++staticclose_method_decl(incore)+{+free(st->u.incore.buf);+return0;+}++staticread_method_decl(incore)+{+size_tread_size=sz;+size_tremainder=st->u.incore.sz-st->u.incore.read_ptr;++if(remainder<=read_size)+read_size=remainder;+if(read_size){+memcpy(buf,st->u.incore.buf+st->u.incore.read_ptr,read_size);+st->u.incore.read_ptr+=read_size;+}+returnread_size;+}++staticstructstream_vtblincore_vtbl={+close_istream_incore,+read_istream_incore,+};++staticopen_method_decl(incore)+{+st->u.incore.buf=read_sha1_file_extended(sha1,type,sz,0);+st->u.incore.read_ptr=0;+st->u.incore.sz=*sz;+st->vtbl=&incore_vtbl;++if(!st->u.incore.buf){+free(st->u.incore.buf);+return-1;+}+return0;+}
From: Junio C Hamano <hidden> Date: 2016-06-15 22:51:15
In the write-out codepath, a block of code determines what file in the
working tree to write to, and opens an output file descriptor to it.
After writing the contents out to the file, another block of code runs
fstat() on the file descriptor when appropriate.
Separate these blocks out to open_output_fd() and fstat_output()
helper functions.
Signed-off-by: Junio C Hamano <redacted>
---
entry.c | 43 ++++++++++++++++++++++++++-----------------
1 files changed, 26 insertions(+), 17 deletions(-)
@@ -91,6 +91,29 @@ static void *read_blob_entry(struct cache_entry *ce, unsigned long *size)returnNULL;}+staticintopen_output_fd(char*path,structcache_entry*ce,intto_tempfile)+{+intsymlink=(ce->ce_mode&S_IFMT)!=S_IFREG;+if(to_tempfile){+strcpy(path,symlink+?".merge_link_XXXXXX":".merge_file_XXXXXX");+returnmkstemp(path);+}else{+returncreate_file(path,!symlink?ce->ce_mode:0666);+}+}++staticintfstat_output(intfd,conststructcheckout*state,structstat*st)+{+/* use fstat() only when path == ce->name */+if(fstat_is_reliable()&&+state->refresh_cache&&!state->base_dir_len){+fstat(fd,st);+return1;+}+return0;+}+staticintwrite_entry(structcache_entry*ce,char*path,conststructcheckout*state,intto_tempfile){unsignedintce_mode_s_ifmt=ce->ce_mode&S_IFMT;
@@ -128,17 +151,7 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkoutsize=newsize;}-if(to_tempfile){-if(ce_mode_s_ifmt==S_IFREG)-strcpy(path,".merge_file_XXXXXX");-else-strcpy(path,".merge_link_XXXXXX");-fd=mkstemp(path);-}elseif(ce_mode_s_ifmt==S_IFREG){-fd=create_file(path,ce->ce_mode);-}else{-fd=create_file(path,0666);-}+fd=open_output_fd(path,ce,to_tempfile);if(fd<0){free(new);returnerror("unable to create file %s (%s)",
@@ -146,12 +159,8 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout}wrote=write_in_full(fd,new,size);-/* use fstat() only when path == ce->name */-if(fstat_is_reliable()&&-state->refresh_cache&&!to_tempfile&&!state->base_dir_len){-fstat(fd,&st);-fstat_done=1;-}+if(!to_tempfile)+fstat_done=fstat_output(fd,state,&st);close(fd);free(new);if(wrote!=size)
From: Junio C Hamano <hidden> Date: 2016-06-15 22:51:15
This function is used to read and skip over the per-object header
in a packfile.
Signed-off-by: Junio C Hamano <redacted>
---
cache.h | 1 +
sha1_file.c | 8 ++++----
2 files changed, 5 insertions(+), 4 deletions(-)
From: Junio C Hamano <hidden> Date: 2016-06-15 22:51:15
An object found in the delta-base cache is not guaranteed to
stay there, but we know it came from a pack and it is likely
to give us a quick access if we read_sha1_file() it right now,
which is a piece of useful information.
Signed-off-by: Junio C Hamano <redacted>
---
cache.h | 3 ++-
sha1_file.c | 9 +++++++++
2 files changed, 11 insertions(+), 1 deletions(-)
From: Junio C Hamano <hidden> Date: 2016-06-15 22:51:15
The original interface for sha1_object_info() takes an object name and
gives back a type and its size (the latter is given only when it was
asked). The new interface wraps its implementation and exposes a bit
more pieces of information that the interface used to discard, namely:
- where the object is stored (loose? cached? packed?)
- if packed, where in which packfile?
Signed-off-by: Junio C Hamano <redacted>
---
cache.h | 29 +++++++++++++++++++++++++++++
sha1_file.c | 46 +++++++++++++++++++++++++++++++++++++++-------
2 files changed, 68 insertions(+), 7 deletions(-)
@@ -2093,7 +2093,8 @@ static int sha1_loose_object_info(const unsigned char *sha1, unsigned long *sizereturnstatus;}-intsha1_object_info(constunsignedchar*sha1,unsignedlong*sizep)+/* returns enum object_type or negative */+intsha1_object_info_extended(constunsignedchar*sha1,structobject_info*oi){structcached_object*co;structpack_entrye;
@@ -2101,16 +2102,19 @@ int sha1_object_info(const unsigned char *sha1, unsigned long *sizep)co=find_cached_object(sha1);if(co){-if(sizep)-*sizep=co->size;+if(oi->sizep)+*(oi->sizep)=co->size;+oi->whence=OI_CACHED;returnco->type;}if(!find_pack_entry(sha1,&e)){/* Most likely it's a loose object. */-status=sha1_loose_object_info(sha1,sizep);-if(status>=0)+status=sha1_loose_object_info(sha1,oi->sizep);+if(status>=0){+oi->whence=OI_LOOSE;returnstatus;+}/* Not a loose object; someone else may have just packed it. */reprepare_packed_git();
@@ -2118,15 +2122,43 @@ int sha1_object_info(const unsigned char *sha1, unsigned long *sizep)returnstatus;}-status=packed_object_info(e.p,e.offset,sizep);+if(!oi->want_deltainfo){+status=packed_object_info(e.p,e.offset,oi->sizep);+}else{+unsignedlongsize,store_size;+unsignedintdelta_chain_length;+unsignedcharbase_sha1[20];+status=packed_object_info_detail(e.p,e.offset,+&size,&store_size,+&delta_chain_length,+base_sha1);+if(0<=status){+if(oi->sizep)+*oi->sizep=size;+oi->u.packed.delta=delta_chain_length;+}+}if(status<0){mark_bad_packed_object(e.p,sha1);-status=sha1_object_info(sha1,sizep);+status=sha1_object_info_extended(sha1,oi);+}else{+oi->whence=OI_PACKED;+oi->u.packed.offset=e.offset;+oi->u.packed.pack=e.p;}returnstatus;}+intsha1_object_info(constunsignedchar*sha1,unsignedlong*sizep)+{+structobject_infooi;++oi.sizep=sizep;+oi.want_deltainfo=0;+returnsha1_object_info_extended(sha1,&oi);+}+staticvoid*read_packed_sha1(constunsignedchar*sha1,enumobject_type*type,unsignedlong*size){
From: Junio C Hamano <hidden> Date: 2016-06-15 22:51:15
When the output to a path does not have to be converted, we can read from
the object database from the streaming API and write to the file in the
working tree, without having to hold everything in the memory.
The ident, auto- and safe- crlf conversions inherently require you to read
the whole thing before deciding what to do, so while it is technically
possible to support them by using a buffer of an unbound size or rewinding
and reading the stream twice, it is less practical than the traditional
"read the whole thing in core and convert" approach.
Adding streaming filters for the other conversions on top of this should
be doable by tweaking the can_bypass_conversion() function (it should be
renamed to can_filter_stream() when it happens). Then the streaming API
can be extended to wrap the git_istream streaming_write_entry() opens on
the underlying object in another git_istream that reads from it, filters
what is read, and let the streaming_write_entry() read the filtered
result. But that is outside the scope of this series.
Signed-off-by: Junio C Hamano <redacted>
---
cache.h | 1 +
convert.c | 23 +++++++++++++++++++++++
entry.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 76 insertions(+), 0 deletions(-)
From: Junio C Hamano <hidden> Date: 2016-06-15 22:51:15
One typical use of a large binary file is to hold a sparse on-disk hash
table with a lot of holes. Help preserving the holes with lseek().
Signed-off-by: Junio C Hamano <redacted>
---
entry.c | 18 +++++++++++++++++-
1 files changed, 17 insertions(+), 1 deletions(-)
@@ -55,8 +55,11 @@ struct git_istream {}incore;struct{-intfd;/* open for reading */-/* NEEDSWORK: what else? */+void*mapped;+unsignedlongmapsize;+charhdr[32];+inthdr_avail;+inthdr_used;}loose;struct{
@@ -149,9 +152,85 @@ static void close_deflated_stream(struct git_istream *st)******************************************************************/+staticread_method_decl(loose)+{+size_ttotal_read=0;++switch(st->z_state){+casez_done:+return0;+casez_error:+return-1;+default:+break;+}++if(st->u.loose.hdr_used<st->u.loose.hdr_avail){+size_tto_copy=st->u.loose.hdr_avail-st->u.loose.hdr_used;+if(sz<to_copy)+to_copy=sz;+memcpy(buf,st->u.loose.hdr+st->u.loose.hdr_used,to_copy);+st->u.loose.hdr_used+=to_copy;+total_read+=to_copy;+}++while(total_read<sz){+intstatus;++st->z.next_out=(unsignedchar*)buf+total_read;+st->z.avail_out=sz-total_read;+status=git_inflate(&st->z,Z_FINISH);++total_read=st->z.next_out-(unsignedchar*)buf;++if(status==Z_STREAM_END){+git_inflate_end(&st->z);+st->z_state=z_done;+break;+}+if(status!=Z_OK&&status!=Z_BUF_ERROR){+git_inflate_end(&st->z);+st->z_state=z_error;+return-1;+}+}+returntotal_read;+}++staticclose_method_decl(loose)+{+close_deflated_stream(st);+munmap(st->u.loose.mapped,st->u.loose.mapsize);+return0;+}++staticstructstream_vtblloose_vtbl={+close_istream_loose,+read_istream_loose,+};+staticopen_method_decl(loose){-return-1;/* for now */+st->u.loose.mapped=map_sha1_file(sha1,&st->u.loose.mapsize);+if(!st->u.loose.mapped)+return-1;+if(unpack_sha1_header(&st->z,+st->u.loose.mapped,+st->u.loose.mapsize,+st->u.loose.hdr,+sizeof(st->u.loose.hdr))<0){+git_inflate_end(&st->z);+munmap(st->u.loose.mapped,st->u.loose.mapsize);+return-1;+}++/* bypass parse_sha1_header() as we know it is already valid */+st->u.loose.hdr_used=strlen(st->u.loose.hdr)+1;+st->u.loose.hdr_avail=st->z.total_out;+st->z_state=z_used;++st->vtbl=&loose_vtbl;+return0;}
From: Junio C Hamano <hidden> Date: 2016-06-15 22:51:15
Make map_sha1_file() and unpack_sha1_header() available to the streaming
read API by exporting them via cache.h header file.
Signed-off-by: Junio C Hamano <redacted>
---
cache.h | 2 ++
sha1_file.c | 4 ++--
2 files changed, 4 insertions(+), 2 deletions(-)
@@ -780,6 +780,8 @@ extern int hash_sha1_file(const void *buf, unsigned long len, const char *type,externintwrite_sha1_file(constvoid*buf,unsignedlonglen,constchar*type,unsignedchar*return_sha1);externintpretend_sha1_file(void*,unsignedlong,enumobject_type,unsignedchar*);externintforce_object_loose(constunsignedchar*sha1,time_tmtime);+externvoid*map_sha1_file(constunsignedchar*sha1,unsignedlong*size);+externintunpack_sha1_header(z_stream*stream,unsignedchar*map,unsignedlongmapsize,void*buffer,unsignedlongbufsiz);/* global flag to enable extra checks when accessing packed objects */externintdo_check_packed_object_crc;
@@ -60,10 +60,13 @@ struct git_istream {}loose;struct{-intfd;/* open for reading */-/* NEEDSWORK: what else? */+structpacked_git*pack;+off_tpos;+unsignedlongsz;}in_pack;}u;+z_streamz;+enum{z_unused,z_used,z_done,z_error}z_state;};intclose_istream(structgit_istream*st)
@@ -144,9 +161,93 @@ static open_method_decl(loose)******************************************************************/+staticread_method_decl(pack_non_delta)+{+size_ttotal_read=0;++switch(st->z_state){+casez_unused:+memset(&st->z,0,sizeof(st->z));+git_inflate_init(&st->z);+st->z_state=z_used;+break;+casez_done:+return0;+casez_error:+return-1;+casez_used:+break;+}++while(total_read<sz){+intstatus;+structpack_window*window=NULL;+unsignedchar*mapped;++mapped=use_pack(st->u.in_pack.pack,&window,+st->u.in_pack.pos,&st->z.avail_in);++st->z.next_out=(unsignedchar*)buf+total_read;+st->z.avail_out=sz-total_read;+st->z.next_in=mapped;+status=git_inflate(&st->z,Z_FINISH);++st->u.in_pack.pos+=st->z.next_in-mapped;+total_read=st->z.next_out-(unsignedchar*)buf;+unuse_pack(&window);++if(status==Z_STREAM_END){+git_inflate_end(&st->z);+st->z_state=z_done;+break;+}+if(status!=Z_OK&&status!=Z_BUF_ERROR){+git_inflate_end(&st->z);+st->z_state=z_error;+return-1;+}+}+returntotal_read;+}++staticclose_method_decl(pack_non_delta)+{+close_deflated_stream(st);+return0;+}++staticstructstream_vtblpack_non_delta_vtbl={+close_istream_pack_non_delta,+read_istream_pack_non_delta,+};+staticopen_method_decl(pack_non_delta){-return-1;/* for now */+structpack_window*window;+enumobject_typein_pack_type;++st->u.in_pack.pack=oi->u.packed.pack;+st->u.in_pack.pos=oi->u.packed.offset;+window=NULL;++in_pack_type=unpack_object_header(st->u.in_pack.pack,+&window,+&st->u.in_pack.pos,+&st->u.in_pack.sz);+unuse_pack(&window);+switch(in_pack_type){+default:+return-1;/* we do not do deltas for now */+caseOBJ_COMMIT:+caseOBJ_TREE:+caseOBJ_BLOB:+caseOBJ_TAG:+break;+}++st->z_state=z_unused;+st->vtbl=&pack_non_delta_vtbl;+return0;}
On Sun, May 15, 2011 at 17:30, Junio C Hamano [off-list ref] wrote:
Traditionally, git always read the full contents of an object in memory
before performing various operations on it, e.g. comparing for diff,
writing it to the working tree, etc. A huge blob that you cannot fit
in memory was very cumbersome to handle.
,,,
Interested parties may want to measure the performance impact of the last
three patches. The series deliberately ignores core.bigfileThreashold and
let small and large blobs alike go through the streaming_write_entry()
codepath, but it _might_ turn out that we would want to use the new code
only for large-ish blobs.
FWIW in JGit we control this by looking at the object size and
comparing to the variable core.streamFileThreshold. For any object
below this size we allocate the buffer, unpack into it, and return the
buffer to the caller. Only objects above the size use the streaming
code paths.
There is a performance difference, at least for us in Java. Most of
the overhead seems to be due to running zlib inflate() with a tiny
buffer size rather than the full destination buffer. This probably has
to do with the cost associated with jumping from the Java bytecode
through JNI to the libz library.
--
Shawn.
On Sun, May 15, 2011 at 17:30, Junio C Hamano [off-list ref] wrote:
+static read_method_decl(pack_non_delta)
I am not a huge fan of these decl macros... but I can see how writing
out the same function prototype 3 times is annoying.
+ switch (in_pack_type) {
+ default:
+ return -1; /* we do not do deltas for now */
Haha. Deltas are going to be painful. Very, very painful.
We actually try to stream deltas in JGit. Our implementation isn't
useful. It only works if the base object is only accessed in
sequential order by the delta instruction sequences.
I had plans to add an external delta base cache to
$GIT_DIR/objects/delta-base-cache using a block file format that is
random accessible, but has CRC-32 checksums on each block to still
ensure there isn't silent data corruption on reads. You can read more
about it here http://egit.eclipse.org/r/1724 but the patch is probably
stalled and will get abandoned.
I think the better strategy is to avoid delta compression altogether
for objects that are so big we cannot materialize them as a contiguous
buffer. What a reasonable limit is, I don't know... but its probably
got to be around 25-50 MB. The Android project (as an example) has 6+
MB XML documents in their source code repository that are very delta
compressible.
--
Shawn.
On Sun, May 15, 2011 at 9:30 PM, Junio C Hamano [off-list ref] wrote:
quoted hunk
One typical use of a large binary file is to hold a sparse on-disk hash
table with a lot of holes. Help preserving the holes with lseek().
Signed-off-by: Junio C Hamano <redacted>
---
entry.c | 18 +++++++++++++++++-
1 files changed, 17 insertions(+), 1 deletions(-)
+ || write(fd, "", 1) != 1))
+ goto close_and_exit;
*fstat_done = fstat_output(fd, state, statbuf);
close_and_exit:
--
1.7.5.1.365.g32b65
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
On Sun, May 15, 2011 at 9:30 PM, Junio C Hamano [off-list ref] wrote:
quoted hunk
Instead return enum object_name just like everybody else does.
The caller can turn it into a string with typename() easily.
Signed-off-by: Junio C Hamano <redacted>
---
builtin/verify-pack.c | 4 ++--
cache.h | 2 +-
sha1_file.c | 4 ++--
3 files changed, 5 insertions(+), 5 deletions(-)
extern void *unpack_entry(struct packed_git *, off_t, enum object_type *, unsigned long *);
extern unsigned long unpack_object_header_buffer(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep);
extern unsigned long get_size_from_delta(struct packed_git *, struct pack_window **, off_t);
-extern const char *packed_object_info_detail(struct packed_git *, off_t, unsigned long *, unsigned long *, unsigned int *, unsigned char *);
+extern int packed_object_info_detail(struct packed_git *, off_t, unsigned long *, unsigned long *, unsigned int *, unsigned char *);
In the commit message you say to return "enum object_name". Maybe
change from int to enum object_name here and below?
Also, |type| below is enum object_type not object_name.
quoted hunk
/* Dumb servers support */
extern int update_server_info(int);
case OBJ_BLOB:
case OBJ_TAG:
unuse_pack(&w_curs);
- return typename(type);
+ return type;
case OBJ_OFS_DELTA:
obj_offset = get_delta_base(p, &w_curs, &curpos, type, obj_offset);
if (!obj_offset)
--
1.7.5.1.365.g32b65
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
From: Jeff King <hidden> Date: 2016-06-15 22:51:15
On Sun, May 15, 2011 at 05:30:20PM -0700, Junio C Hamano wrote:
Interested parties may want to measure the performance impact of the last
three patches. The series deliberately ignores core.bigfileThreashold and
let small and large blobs alike go through the streaming_write_entry()
codepath, but it _might_ turn out that we would want to use the new code
only for large-ish blobs.
Hmm.
$ cd compile/linux-2.6
$ rm -rf *
$ time git.v1.7.5 checkout -f
real 0m4.405s
user 0m3.592s
sys 0m0.804s
$ rm -rf *
$ time git.jch.streaming checkout -f
real 0m7.062s
user 0m5.188s
sys 0m1.776s
(Actually those times are best-of-5 in each case). So there is
definitely some slow-down for the non-huge case. Bisection points to
your cd36b7b (streaming_write_entry(): use streaming API in
write_entry()).
According to perf, though, it's not the increased writes; the slowdown
is actually from create_pack_revindex, in this call chain:
create_pack_revindex
find_pack_revindex
packed_object_info_detail
sha1_object_info_extended
istream_source
open_istream
streaming_write_entry
-Peff
From: Jeff King <hidden> Date: 2016-06-15 22:51:15
On Wed, May 18, 2011 at 02:41:58AM -0400, Jeff King wrote:
According to perf, though, it's not the increased writes; the slowdown
is actually from create_pack_revindex, in this call chain:
create_pack_revindex
find_pack_revindex
packed_object_info_detail
sha1_object_info_extended
istream_source
open_istream
streaming_write_entry
Part of the problem is that with the current code, all you care about is
"Is it loose, packed non-delta, or packed delta?". And
packed_object_info_detail will tell you not just whether it's deltafied,
but will go to a lot more work to make the revindex. One solution is to
let the cheap packed_object_info() report back on delta status, since
it's free there, and then we don't have to deal with the revindex at
all.
Of course, it may turn out that the extra information is useful if and
when open_istream_* actually gets implemented for delta-fied objects.
The patch below implements the cheap "is_delta" check. But it only
shaves off a half second (dropping us from 7s to 6.5s). Prior to your
patches, we were at 4.5 seconds. So there's still quite a bit of
slowdown to figure out.
@@ -1626,11 +1628,15 @@ static int packed_object_info(struct packed_git *p, off_t obj_offset,caseOBJ_TAG:if(sizep)*sizep=size;+if(is_delta)+*is_delta=0;break;default:error("unknown object type %i at offset %"PRIuMAX" in %s",type,(uintmax_t)obj_offset,p->pack_name);type=OBJ_BAD;+if(is_delta)+*is_delta=0;}unuse_pack(&w_curs);returntype;
From: Jeff King <hidden> Date: 2016-06-15 22:51:16
On Wed, May 18, 2011 at 03:08:37AM -0400, Jeff King wrote:
Part of the problem is that with the current code, all you care about is
"Is it loose, packed non-delta, or packed delta?".
[...]
The patch below implements the cheap "is_delta" check.
Hmm, sorry, this patch works well on top of cd36b7b, where I first
detected the slowness, but later in the series we actually do look at
the pack information in the object_info. So my patch breaks that code
path horribly (I still think the concept of avoiding the revindex should
still work in principle, though).
-Peff
I assume the "sz" parameter is meant to be an output parameter with the
total size of the object. The open_istream_incore function fills it in
properly. But later, when you add open_istream_loose and
open_istream_pack_non_delta, neither of them actually touches the "sz"
parameter at all. So code like:
struct git_istream *st;
enum object_type type;
unsigned long size;
st = open_istream(sha1, &type, &size);
may or may not have "size" meaningful at this point, which seems like a
bug.
-Peff
From: Jeff King <hidden> Date: 2016-06-15 22:51:16
On Sun, May 15, 2011 at 05:30:20PM -0700, Junio C Hamano wrote:
Recently "diff" learned to avoid reading the contents only to say "Binary
files differ" when these large blobs are marked as binary.
With your series, we should be able to get similar speedups even if the
user didn't explicitly mark a file as binary. We need only peek at the
beginning of a blob to see if it is binary, so we can be conservative
with big files. Something like this (which doesn't work because of the
"size" bug I mentioned elsewhere, but is meant to be illustrative):
@@ -1931,6 +1932,37 @@ static void diff_filespec_load_driver(struct diff_filespec *one)one->driver=userdiff_find_by_name("default");}+staticchar*populate_or_peek(structdiff_filespec*df,+unsignedlongwant,+unsignedlong*got)+{+structgit_istream*st;+enumobject_typetype;+char*buf;++st=open_istream(df->sha1,&type,&df->size);+if(!st){+diff_populate_filespec(df,0);+*got=df->size;+returndf->data;+}++if(df->size<big_file_threshold){+buf=df->data=xmallocz(df->size);+want=df->size;+df->should_free=1;+}+else+buf=xmallocz(want);++/* looks like it will always read_in_full? */+if(read_istream(st,buf,want)!=want)+die("failed to read object");+close_istream(st);+*got=want;+returnbuf;+}+intdiff_filespec_is_binary(structdiff_filespec*one){if(one->is_binary==-1){
@@ -1938,13 +1970,25 @@ int diff_filespec_is_binary(struct diff_filespec *one)if(one->driver->binary!=-1)one->is_binary=one->driver->binary;else{-if(!one->data&&DIFF_FILE_VALID(one))-diff_populate_filespec(one,0);-if(one->data)-one->is_binary=buffer_is_binary(one->data,-one->size);+char*buf;+unsignedlongsize;++if(one->data){+buf=one->data;+size=one->size;+}+elseif(DIFF_FILE_VALID(one))+buf=populate_or_peek(one,8192,&size);+else+buf=NULL;++if(buf)+one->is_binary=buffer_is_binary(buf,size);if(one->is_binary==-1)one->is_binary=0;++if(buf!=one->data)+free(buf);}}returnone->is_binary;
I think a "peek" function like this would be a nice addition to the
streaming API. Something like:
char *peek_sha1(const unsigned char sha1[20], /* which object */
enum object_type *type, /* out: type */
unsigned long want, /* how much do we need */
unsigned long big, /* if less than this, just give us
everything in the name of
efficiency */
unsigned long *got, /* out: how much did we peek */
unsigned long *size, /* out: how big is the whole thing */
);
but maybe diff is the only place where that is useful. I dunno.
-Peff
From: Junio C Hamano <hidden> Date: 2016-06-15 22:51:16
Instead return an integer that can be given to typename() if
the caller wants a string, just like everybody else does.
Signed-off-by: Junio C Hamano <redacted>
---
builtin/verify-pack.c | 4 ++--
cache.h | 2 +-
sha1_file.c | 4 ++--
3 files changed, 5 insertions(+), 5 deletions(-)
From: Junio C Hamano <hidden> Date: 2016-06-15 22:51:16
So this is the second round. Peff noticed that the istream_open() did not
return the size of the object correctly for in-pack (non-delta) and loose
representations, and this round fixes it.
Also sha1_object_info_extended() lost the call to the expensive function
packed_object_info_detail(), as the only thing we are interested in is to
see if the first-level object is a non-delta. As the result, 02/11 would
now be much easier to follow.
Junio C Hamano (11):
packed_object_info_detail(): do not return a string
sha1_object_info_extended(): expose a bit more info
sha1_object_info_extended(): hint about objects in delta-base cache
unpack_object_header(): make it public
write_entry(): separate two helper functions out
streaming: a new API to read from the object store
streaming_write_entry(): use streaming API in write_entry()
streaming_write_entry(): support files with holes
streaming: read non-delta incrementally from a pack
sha1_file.c: expose helpers to read loose objects
streaming: read loose objects incrementally
Makefile | 2 +
builtin/verify-pack.c | 4 +-
cache.h | 36 +++++-
convert.c | 23 +++
entry.c | 112 +++++++++++++---
sha1_file.c | 69 +++++++---
streaming.c | 377 +++++++++++++++++++++++++++++++++++++++++++++++++
streaming.h | 15 ++
8 files changed, 598 insertions(+), 40 deletions(-)
create mode 100644 streaming.c
create mode 100644 streaming.h
--
1.7.5.1.416.gac10c8
From: Junio C Hamano <hidden> Date: 2016-06-15 22:51:16
An object found in the delta-base cache is not guaranteed to
stay there, but we know it came from a pack and it is likely
to give us a quick access if we read_sha1_file() it right now,
which is a piece of useful information.
Signed-off-by: Junio C Hamano <redacted>
---
cache.h | 3 ++-
sha1_file.c | 9 +++++++++
2 files changed, 11 insertions(+), 1 deletions(-)
From: Junio C Hamano <hidden> Date: 2016-06-15 22:51:16
The original interface for sha1_object_info() takes an object name and
gives back a type and its size (the latter is given only when it was
asked). The new interface wraps its implementation and exposes a bit
more pieces of information that the interface used to discard, namely:
- where the object is stored (loose? cached? packed?)
- if packed, where in which packfile?
Signed-off-by: Junio C Hamano <redacted>
---
* In the earlier round, this used u.pack.delta to record the length of
the delta chain, but the caller is not necessarily interested in the
length of the delta chain per-se, but may only want to know if it is a
delta against another object or is stored as a deflated data. Calling
packed_object_info_detail() involves walking the reverse index chain to
compute the store size of the object and is unnecessarily expensive.
We could resurrect the code if a new caller wants to know, but I doubt
it.
---
cache.h | 28 ++++++++++++++++++++++++++++
sha1_file.c | 42 +++++++++++++++++++++++++++++++-----------
2 files changed, 59 insertions(+), 11 deletions(-)
@@ -1481,7 +1481,7 @@ static off_t get_delta_base(struct packed_git *p,/* forward declaration for a mutually recursive function */staticintpacked_object_info(structpacked_git*p,off_toffset,-unsignedlong*sizep);+unsignedlong*sizep,int*rtype);staticintpacked_delta_info(structpacked_git*p,structpack_window**w_curs,
@@ -1495,7 +1495,7 @@ static int packed_delta_info(struct packed_git *p,base_offset=get_delta_base(p,w_curs,&curpos,type,obj_offset);if(!base_offset)returnOBJ_BAD;-type=packed_object_info(p,base_offset,NULL);+type=packed_object_info(p,base_offset,NULL,NULL);if(type<=OBJ_NONE){structrevindex_entry*revidx;constunsignedchar*base_sha1;
@@ -1605,7 +1605,7 @@ int packed_object_info_detail(struct packed_git *p,}staticintpacked_object_info(structpacked_git*p,off_tobj_offset,-unsignedlong*sizep)+unsignedlong*sizep,int*rtype){structpack_window*w_curs=NULL;unsignedlongsize;
@@ -1613,6 +1613,8 @@ static int packed_object_info(struct packed_git *p, off_t obj_offset,enumobject_typetype;type=unpack_object_header(p,&w_curs,&curpos,&size);+if(rtype)+*rtype=type;/* representation type */switch(type){caseOBJ_OFS_DELTA:
@@ -2093,24 +2095,28 @@ static int sha1_loose_object_info(const unsigned char *sha1, unsigned long *sizereturnstatus;}-intsha1_object_info(constunsignedchar*sha1,unsignedlong*sizep)+/* returns enum object_type or negative */+intsha1_object_info_extended(constunsignedchar*sha1,structobject_info*oi){structcached_object*co;structpack_entrye;-intstatus;+intstatus,rtype;co=find_cached_object(sha1);if(co){-if(sizep)-*sizep=co->size;+if(oi->sizep)+*(oi->sizep)=co->size;+oi->whence=OI_CACHED;returnco->type;}if(!find_pack_entry(sha1,&e)){/* Most likely it's a loose object. */-status=sha1_loose_object_info(sha1,sizep);-if(status>=0)+status=sha1_loose_object_info(sha1,oi->sizep);+if(status>=0){+oi->whence=OI_LOOSE;returnstatus;+}/* Not a loose object; someone else may have just packed it. */reprepare_packed_git();
@@ -2118,15 +2124,29 @@ int sha1_object_info(const unsigned char *sha1, unsigned long *sizep)returnstatus;}-status=packed_object_info(e.p,e.offset,sizep);+status=packed_object_info(e.p,e.offset,oi->sizep,&rtype);if(status<0){mark_bad_packed_object(e.p,sha1);-status=sha1_object_info(sha1,sizep);+status=sha1_object_info_extended(sha1,oi);+}else{+oi->whence=OI_PACKED;+oi->u.packed.offset=e.offset;+oi->u.packed.pack=e.p;+oi->u.packed.is_delta=(rtype==OBJ_REF_DELTA||+rtype==OBJ_OFS_DELTA);}returnstatus;}+intsha1_object_info(constunsignedchar*sha1,unsignedlong*sizep)+{+structobject_infooi;++oi.sizep=sizep;+returnsha1_object_info_extended(sha1,&oi);+}+staticvoid*read_packed_sha1(constunsignedchar*sha1,enumobject_type*type,unsignedlong*size){
From: Junio C Hamano <hidden> Date: 2016-06-15 22:51:16
Given an object name, use open_istream() to get a git_istream handle
that you can read_istream() from as if you are using read(2) to read
the contents of the object, and close it with close_istream() when
you are done.
Currently, we do not do anything fancy--it just calls read_sha1_file()
and keeps the contents in memory as a whole, and carve it out as you
request with read_istream().
Signed-off-by: Junio C Hamano <redacted>
---
Makefile | 2 +
streaming.c | 199 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
streaming.h | 15 +++++
3 files changed, 216 insertions(+), 0 deletions(-)
create mode 100644 streaming.c
create mode 100644 streaming.h
@@ -0,0 +1,199 @@+/*+*Copyright(c)2011,GoogleInc.+*/+#include"cache.h"+#include"streaming.h"++enuminput_source{+stream_error=-1,+incore=0,+loose=1,+pack_non_delta=2+};++typedefint(*open_istream_fn)(structgit_istream*,+structobject_info*,+constunsignedchar*,+enumobject_type*);+typedefint(*close_istream_fn)(structgit_istream*);+typedefssize_t(*read_istream_fn)(structgit_istream*,char*,size_t);++structstream_vtbl{+close_istream_fnclose;+read_istream_fnread;+};++#define open_method_decl(name) \+intopen_istream_##name\+(structgit_istream*st,structobject_info*oi,\+constunsignedchar*sha1,\+enumobject_type*type)++#define close_method_decl(name) \+intclose_istream_##name\+(structgit_istream*st)++#define read_method_decl(name) \+ssize_tread_istream_##name\+(structgit_istream*st,char*buf,size_tsz)++/* forward declaration */+staticopen_method_decl(incore);+staticopen_method_decl(loose);+staticopen_method_decl(pack_non_delta);++staticopen_istream_fnopen_istream_tbl[]={+open_istream_incore,+open_istream_loose,+open_istream_pack_non_delta,+};++structgit_istream{+enuminput_sourcesource;+conststructstream_vtbl*vtbl;+unsignedlongsize;/* inflated size of full object */++union{+struct{+char*buf;/* from read_object() */+unsignedlongread_ptr;+}incore;++struct{+intfd;/* open for reading */+/* NEEDSWORK: what else? */+}loose;++struct{+intfd;/* open for reading */+/* NEEDSWORK: what else? */+}in_pack;+}u;+};++intclose_istream(structgit_istream*st)+{+returnst->vtbl->close(st);+}++ssize_tread_istream(structgit_istream*st,char*buf,size_tsz)+{+returnst->vtbl->read(st,buf,sz);+}++staticenuminput_sourceistream_source(constunsignedchar*sha1,+enumobject_type*type,+structobject_info*oi)+{+unsignedlongsize;+intstatus;++oi->sizep=&size;+status=sha1_object_info_extended(sha1,oi);+if(status<0)+returnstream_error;+*type=status;++switch(oi->whence){+caseOI_LOOSE:+returnloose;+caseOI_PACKED:+if(!oi->u.packed.is_delta&&big_file_threshold<=size)+returnpack_non_delta;+/* fallthru */+default:+returnincore;+}+}++structgit_istream*open_istream(constunsignedchar*sha1,+enumobject_type*type,+unsignedlong*size)+{+structgit_istream*st;+structobject_infooi;+constunsignedchar*real=lookup_replace_object(sha1);+enuminput_sourcesrc=istream_source(real,type,&oi);++if(src<0)+returnNULL;++st=xmalloc(sizeof(*st));+st->source=src;+if(open_istream_tbl[src](st,&oi,real,type)){+if(open_istream_incore(st,&oi,real,type)){+free(st);+st=NULL;+}+}+*size=st->size;+returnst;+}++/*****************************************************************+*+*Looseobjectstream+*+*****************************************************************/++staticopen_method_decl(loose)+{+return-1;/* for now */+}+++/*****************************************************************+*+*Non-deltapackedobjectstream+*+*****************************************************************/++staticopen_method_decl(pack_non_delta)+{+return-1;/* for now */+}+++/*****************************************************************+*+*In-corestream+*+*****************************************************************/++staticclose_method_decl(incore)+{+free(st->u.incore.buf);+return0;+}++staticread_method_decl(incore)+{+size_tread_size=sz;+size_tremainder=st->size-st->u.incore.read_ptr;++if(remainder<=read_size)+read_size=remainder;+if(read_size){+memcpy(buf,st->u.incore.buf+st->u.incore.read_ptr,read_size);+st->u.incore.read_ptr+=read_size;+}+returnread_size;+}++staticstructstream_vtblincore_vtbl={+close_istream_incore,+read_istream_incore,+};++staticopen_method_decl(incore)+{+st->u.incore.buf=read_sha1_file_extended(sha1,type,&st->size,0);+st->u.incore.read_ptr=0;+st->vtbl=&incore_vtbl;++if(!st->u.incore.buf){+free(st->u.incore.buf);+return-1;+}+return0;+}
@@ -62,8 +62,11 @@ struct git_istream {}incore;struct{-intfd;/* open for reading */-/* NEEDSWORK: what else? */+void*mapped;+unsignedlongmapsize;+charhdr[32];+inthdr_avail;+inthdr_used;}loose;struct{
@@ -152,9 +155,85 @@ static void close_deflated_stream(struct git_istream *st)******************************************************************/+staticread_method_decl(loose)+{+size_ttotal_read=0;++switch(st->z_state){+casez_done:+return0;+casez_error:+return-1;+default:+break;+}++if(st->u.loose.hdr_used<st->u.loose.hdr_avail){+size_tto_copy=st->u.loose.hdr_avail-st->u.loose.hdr_used;+if(sz<to_copy)+to_copy=sz;+memcpy(buf,st->u.loose.hdr+st->u.loose.hdr_used,to_copy);+st->u.loose.hdr_used+=to_copy;+total_read+=to_copy;+}++while(total_read<sz){+intstatus;++st->z.next_out=(unsignedchar*)buf+total_read;+st->z.avail_out=sz-total_read;+status=git_inflate(&st->z,Z_FINISH);++total_read=st->z.next_out-(unsignedchar*)buf;++if(status==Z_STREAM_END){+git_inflate_end(&st->z);+st->z_state=z_done;+break;+}+if(status!=Z_OK&&status!=Z_BUF_ERROR){+git_inflate_end(&st->z);+st->z_state=z_error;+return-1;+}+}+returntotal_read;+}++staticclose_method_decl(loose)+{+close_deflated_stream(st);+munmap(st->u.loose.mapped,st->u.loose.mapsize);+return0;+}++staticstructstream_vtblloose_vtbl={+close_istream_loose,+read_istream_loose,+};+staticopen_method_decl(loose){-return-1;/* for now */+st->u.loose.mapped=map_sha1_file(sha1,&st->u.loose.mapsize);+if(!st->u.loose.mapped)+return-1;+if(unpack_sha1_header(&st->z,+st->u.loose.mapped,+st->u.loose.mapsize,+st->u.loose.hdr,+sizeof(st->u.loose.hdr))<0){+git_inflate_end(&st->z);+munmap(st->u.loose.mapped,st->u.loose.mapsize);+return-1;+}++parse_sha1_header(st->u.loose.hdr,&st->size);+st->u.loose.hdr_used=strlen(st->u.loose.hdr)+1;+st->u.loose.hdr_avail=st->z.total_out;+st->z_state=z_used;++st->vtbl=&loose_vtbl;+return0;}
From: Junio C Hamano <hidden> Date: 2016-06-15 22:51:16
Make map_sha1_file(), parse_sha1_header() and unpack_sha1_header()
available to the streaming read API by exporting them via cache.h header
file.
Signed-off-by: Junio C Hamano <redacted>
---
cache.h | 3 +++
sha1_file.c | 6 +++---
2 files changed, 6 insertions(+), 3 deletions(-)
@@ -780,6 +780,9 @@ extern int hash_sha1_file(const void *buf, unsigned long len, const char *type,externintwrite_sha1_file(constvoid*buf,unsignedlonglen,constchar*type,unsignedchar*return_sha1);externintpretend_sha1_file(void*,unsignedlong,enumobject_type,unsignedchar*);externintforce_object_loose(constunsignedchar*sha1,time_tmtime);+externvoid*map_sha1_file(constunsignedchar*sha1,unsignedlong*size);+externintunpack_sha1_header(z_stream*stream,unsignedchar*map,unsignedlongmapsize,void*buffer,unsignedlongbufsiz);+externintparse_sha1_header(constchar*hdr,unsignedlong*sizep);/* global flag to enable extra checks when accessing packed objects */externintdo_check_packed_object_crc;
@@ -52,6 +52,8 @@ struct git_istream {enuminput_sourcesource;conststructstream_vtbl*vtbl;unsignedlongsize;/* inflated size of full object */+z_streamz;+enum{z_unused,z_used,z_done,z_error}z_state;union{struct{
@@ -65,8 +67,8 @@ struct git_istream {}loose;struct{-intfd;/* open for reading */-/* NEEDSWORK: what else? */+structpacked_git*pack;+off_tpos;}in_pack;}u;};
@@ -148,9 +164,92 @@ static open_method_decl(loose)******************************************************************/+staticread_method_decl(pack_non_delta)+{+size_ttotal_read=0;++switch(st->z_state){+casez_unused:+memset(&st->z,0,sizeof(st->z));+git_inflate_init(&st->z);+st->z_state=z_used;+break;+casez_done:+return0;+casez_error:+return-1;+casez_used:+break;+}++while(total_read<sz){+intstatus;+structpack_window*window=NULL;+unsignedchar*mapped;++mapped=use_pack(st->u.in_pack.pack,&window,+st->u.in_pack.pos,&st->z.avail_in);++st->z.next_out=(unsignedchar*)buf+total_read;+st->z.avail_out=sz-total_read;+st->z.next_in=mapped;+status=git_inflate(&st->z,Z_FINISH);++st->u.in_pack.pos+=st->z.next_in-mapped;+total_read=st->z.next_out-(unsignedchar*)buf;+unuse_pack(&window);++if(status==Z_STREAM_END){+git_inflate_end(&st->z);+st->z_state=z_done;+break;+}+if(status!=Z_OK&&status!=Z_BUF_ERROR){+git_inflate_end(&st->z);+st->z_state=z_error;+return-1;+}+}+returntotal_read;+}++staticclose_method_decl(pack_non_delta)+{+close_deflated_stream(st);+return0;+}++staticstructstream_vtblpack_non_delta_vtbl={+close_istream_pack_non_delta,+read_istream_pack_non_delta,+};+staticopen_method_decl(pack_non_delta){-return-1;/* for now */+structpack_window*window;+enumobject_typein_pack_type;++st->u.in_pack.pack=oi->u.packed.pack;+st->u.in_pack.pos=oi->u.packed.offset;+window=NULL;++in_pack_type=unpack_object_header(st->u.in_pack.pack,+&window,+&st->u.in_pack.pos,+&st->size);+unuse_pack(&window);+switch(in_pack_type){+default:+return-1;/* we do not do deltas for now */+caseOBJ_COMMIT:+caseOBJ_TREE:+caseOBJ_BLOB:+caseOBJ_TAG:+break;+}+st->z_state=z_unused;+st->vtbl=&pack_non_delta_vtbl;+return0;}
From: Junio C Hamano <hidden> Date: 2016-06-15 22:51:16
One typical use of a large binary file is to hold a sparse on-disk hash
table with a lot of holes. Help preserving the holes with lseek().
Signed-off-by: Junio C Hamano <redacted>
---
entry.c | 21 +++++++++++++++++++--
1 files changed, 19 insertions(+), 2 deletions(-)
From: Junio C Hamano <hidden> Date: 2016-06-15 22:51:16
In the write-out codepath, a block of code determines what file in the
working tree to write to, and opens an output file descriptor to it.
After writing the contents out to the file, another block of code runs
fstat() on the file descriptor when appropriate.
Separate these blocks out to open_output_fd() and fstat_output()
helper functions.
Signed-off-by: Junio C Hamano <redacted>
---
entry.c | 43 ++++++++++++++++++++++++++-----------------
1 files changed, 26 insertions(+), 17 deletions(-)
@@ -91,6 +91,29 @@ static void *read_blob_entry(struct cache_entry *ce, unsigned long *size)returnNULL;}+staticintopen_output_fd(char*path,structcache_entry*ce,intto_tempfile)+{+intsymlink=(ce->ce_mode&S_IFMT)!=S_IFREG;+if(to_tempfile){+strcpy(path,symlink+?".merge_link_XXXXXX":".merge_file_XXXXXX");+returnmkstemp(path);+}else{+returncreate_file(path,!symlink?ce->ce_mode:0666);+}+}++staticintfstat_output(intfd,conststructcheckout*state,structstat*st)+{+/* use fstat() only when path == ce->name */+if(fstat_is_reliable()&&+state->refresh_cache&&!state->base_dir_len){+fstat(fd,st);+return1;+}+return0;+}+staticintwrite_entry(structcache_entry*ce,char*path,conststructcheckout*state,intto_tempfile){unsignedintce_mode_s_ifmt=ce->ce_mode&S_IFMT;
@@ -128,17 +151,7 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkoutsize=newsize;}-if(to_tempfile){-if(ce_mode_s_ifmt==S_IFREG)-strcpy(path,".merge_file_XXXXXX");-else-strcpy(path,".merge_link_XXXXXX");-fd=mkstemp(path);-}elseif(ce_mode_s_ifmt==S_IFREG){-fd=create_file(path,ce->ce_mode);-}else{-fd=create_file(path,0666);-}+fd=open_output_fd(path,ce,to_tempfile);if(fd<0){free(new);returnerror("unable to create file %s (%s)",
@@ -146,12 +159,8 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout}wrote=write_in_full(fd,new,size);-/* use fstat() only when path == ce->name */-if(fstat_is_reliable()&&-state->refresh_cache&&!to_tempfile&&!state->base_dir_len){-fstat(fd,&st);-fstat_done=1;-}+if(!to_tempfile)+fstat_done=fstat_output(fd,state,&st);close(fd);free(new);if(wrote!=size)
From: Junio C Hamano <hidden> Date: 2016-06-15 22:51:16
When the output to a path does not have to be converted, we can read from
the object database from the streaming API and write to the file in the
working tree, without having to hold everything in the memory.
The ident, auto- and safe- crlf conversions inherently require you to read
the whole thing before deciding what to do, so while it is technically
possible to support them by using a buffer of an unbound size or rewinding
and reading the stream twice, it is less practical than the traditional
"read the whole thing in core and convert" approach.
Adding streaming filters for the other conversions on top of this should
be doable by tweaking the can_bypass_conversion() function (it should be
renamed to can_filter_stream() when it happens). Then the streaming API
can be extended to wrap the git_istream streaming_write_entry() opens on
the underlying object in another git_istream that reads from it, filters
what is read, and let the streaming_write_entry() read the filtered
result. But that is outside the scope of this series.
Signed-off-by: Junio C Hamano <redacted>
---
cache.h | 1 +
convert.c | 23 +++++++++++++++++++++++
entry.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 76 insertions(+), 0 deletions(-)
From: Junio C Hamano <hidden> Date: 2016-06-15 22:51:16
This function is used to read and skip over the per-object header
in a packfile.
Signed-off-by: Junio C Hamano <redacted>
---
cache.h | 1 +
sha1_file.c | 8 ++++----
2 files changed, 5 insertions(+), 4 deletions(-)
From: Jeff King <hidden> Date: 2016-06-15 22:51:16
On Thu, May 19, 2011 at 02:33:35PM -0700, Junio C Hamano wrote:
Also sha1_object_info_extended() lost the call to the expensive function
packed_object_info_detail(), as the only thing we are interested in is to
see if the first-level object is a non-delta. As the result, 02/11 would
now be much easier to follow.
Thanks, I can confirm that this clears up the performance issue in my
test. I have no idea why it wasn't totally fixed with the patch I posted
earlier. I must have botched something.
-Peff
From: René Scharfe <hidden> Date: 2016-06-15 22:51:17
Am 19.05.2011 23:33, schrieb Junio C Hamano:
quoted hunk
Given an object name, use open_istream() to get a git_istream handle
that you can read_istream() from as if you are using read(2) to read
the contents of the object, and close it with close_istream() when
you are done.
Currently, we do not do anything fancy--it just calls read_sha1_file()
and keeps the contents in memory as a whole, and carve it out as you
request with read_istream().
Signed-off-by: Junio C Hamano <redacted>
---
Makefile | 2 +
streaming.c | 199 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
streaming.h | 15 +++++
3 files changed, 216 insertions(+), 0 deletions(-)
create mode 100644 streaming.c
create mode 100644 streaming.h
These three uses of the macro can be avoided by moving open_istream_tbl
and open_istream() to the end of the file. It would be just as clear
and clean, albeit not as close to literal programming style.
It would be nice if those macros could be got rid of once the interface
stabilizes.
Probably, but not while it is still in flux in 'pu'. I already had to
tweak something to support my unpublished series I was working on today.
quoted
+struct git_istream {
+ enum input_source source;
source seems to be write-only.
Yes, I had this initially but later ended up with a design that makes
everything go through vtbl, so this is only useful for debugging and can
be removed.
Thanks.