Skip to content

Commit 95d8202

Browse files
committed
apply_delta now has a C implementation, which is only 25 percent faster for small files ( the overhead of all the rest is very high ), but 4 times faster for large files, where the enormous call overhead coming in with python really starts to show
1 parent ff9c83a commit 95d8202

File tree

3 files changed

+81
-9
lines changed

3 files changed

+81
-9
lines changed

_delta_apply.c

+70
Original file line numberDiff line numberDiff line change
@@ -886,3 +886,73 @@ static PyObject* connect_deltas(PyObject *self, PyObject *dstreams)
886886
return (PyObject*)dcl;
887887
}
888888

889+
890+
// Write using a write function, taking remaining bytes from a base buffer
891+
// replaces the corresponding method in python
892+
static
893+
PyObject* apply_delta(PyObject* self, PyObject* args)
894+
{
895+
PyObject* pybbuf = 0;
896+
PyObject* pydbuf = 0;
897+
PyObject* pytbuf = 0;
898+
if (!PyArg_ParseTuple(args, "OOO", &pybbuf, &pydbuf, &pytbuf)){
899+
PyErr_BadArgument();
900+
return NULL;
901+
}
902+
903+
PyObject* objects[] = { pybbuf, pydbuf, pytbuf };
904+
assert(sizeof(objects) / sizeof(PyObject*) == 3);
905+
906+
uint i;
907+
for(i = 0; i < 3; i++){
908+
if (!PyObject_CheckReadBuffer(objects[i])){
909+
PyErr_SetString(PyExc_ValueError, "Argument must be a buffer-compatible object, like a string, or a memory map");
910+
return NULL;
911+
}
912+
}
913+
914+
Py_ssize_t lbbuf; Py_ssize_t ldbuf; Py_ssize_t ltbuf;
915+
const uchar* bbuf; const uchar* dbuf;
916+
uchar* tbuf;
917+
PyObject_AsReadBuffer(pybbuf, (const void**)(&bbuf), &lbbuf);
918+
PyObject_AsReadBuffer(pydbuf, (const void**)(&dbuf), &ldbuf);
919+
920+
if (PyObject_AsWriteBuffer(pytbuf, (void**)(&tbuf), &ltbuf)){
921+
PyErr_SetString(PyExc_ValueError, "Argument 3 must be a writable buffer");
922+
return NULL;
923+
}
924+
925+
const uchar* data = dbuf;
926+
const uchar* dend = dbuf + ldbuf;
927+
928+
while (data < dend)
929+
{
930+
const char cmd = *data++;
931+
932+
if (cmd & 0x80)
933+
{
934+
unsigned long cp_off = 0, cp_size = 0;
935+
if (cmd & 0x01) cp_off = *data++;
936+
if (cmd & 0x02) cp_off |= (*data++ << 8);
937+
if (cmd & 0x04) cp_off |= (*data++ << 16);
938+
if (cmd & 0x08) cp_off |= ((unsigned) *data++ << 24);
939+
if (cmd & 0x10) cp_size = *data++;
940+
if (cmd & 0x20) cp_size |= (*data++ << 8);
941+
if (cmd & 0x40) cp_size |= (*data++ << 16);
942+
if (cp_size == 0) cp_size = 0x10000;
943+
944+
memcpy(tbuf, bbuf + cp_off, cp_size);
945+
tbuf += cp_size;
946+
947+
} else if (cmd) {
948+
memcpy(tbuf, data, cmd);
949+
tbuf += cmd;
950+
data += cmd;
951+
} else {
952+
PyErr_SetString(PyExc_RuntimeError, "Encountered an unsupported delta cmd: 0");
953+
return NULL;
954+
}
955+
}// END handle command opcodes
956+
957+
Py_RETURN_NONE;
958+
}

_fun.c

+2-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ static PyObject *PackIndexFile_sha_to_index(PyObject *self, PyObject *args)
8383

8484
static PyMethodDef py_fun[] = {
8585
{ "PackIndexFile_sha_to_index", (PyCFunction)PackIndexFile_sha_to_index, METH_VARARGS, "TODO" },
86-
{ "connect_deltas", (PyCFunction)connect_deltas, METH_O, "TODO" },
86+
{ "connect_deltas", (PyCFunction)connect_deltas, METH_O, "See python implementation" },
87+
{ "apply_delta", (PyCFunction)apply_delta, METH_VARARGS, "See python implementation" },
8788
{ NULL, NULL, 0, NULL }
8889
};
8990

stream.py

+9-8
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@
2222
zlib
2323
)
2424

25+
try:
26+
from _perf import apply_delta as c_apply_delta
27+
except ImportError:
28+
pass
29+
2530
__all__ = ('DecompressMemMapReader', 'FDCompressedSha1Writer', 'DeltaApplyReader')
2631

2732

@@ -413,7 +418,10 @@ def _set_cache_(self, attr):
413418
stream_copy(dstream.read, ddata.write, dstream.size, 256*mmap.PAGESIZE)
414419

415420
#######################################################################
416-
apply_delta_data(bbuf, src_size, ddata, len(ddata), tbuf.write)
421+
if 'c_apply_delta' in globals():
422+
c_apply_delta(bbuf, ddata, tbuf);
423+
else:
424+
apply_delta_data(bbuf, src_size, ddata, len(ddata), tbuf.write)
417425
#######################################################################
418426

419427
# finally, swap out source and target buffers. The target is now the
@@ -430,13 +438,6 @@ def _set_cache_(self, attr):
430438
self._mm_target = bbuf
431439
self._size = final_target_size
432440

433-
# TODO: Once that works, figure out the ordering of the opcodes. If they
434-
# are always in-order/sequential, an alternate implementation could
435-
# use stream access only. Of course this would mean we would read
436-
# all deltas in advance, analyse the opcode ranges to determine a final
437-
# concatenated opcode list which indicates what to copy from which delta
438-
# to which position. This preprocessing would allow true streaming
439-
440441
def read(self, count=0):
441442
bl = self._size - self._br # bytes left
442443
if count < 1 or count > bl:

0 commit comments

Comments
 (0)