2013-04-11 02:08:27 +04:00
|
|
|
/*
|
|
|
|
dirs.c - dynamic directory diddling for dirstates
|
|
|
|
|
|
|
|
Copyright 2013 Facebook
|
|
|
|
|
|
|
|
This software may be used and distributed according to the terms of
|
|
|
|
the GNU General Public License, incorporated herein by reference.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define PY_SSIZE_T_CLEAN
|
|
|
|
#include <Python.h>
|
|
|
|
#include "util.h"
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This is a multiset of directory names, built from the files that
|
|
|
|
* appear in a dirstate or manifest.
|
2013-04-11 02:08:27 +04:00
|
|
|
*
|
|
|
|
* A few implementation notes:
|
|
|
|
*
|
|
|
|
* We modify Python integers for refcounting, but those integers are
|
|
|
|
* never visible to Python code.
|
2013-04-11 02:08:28 +04:00
|
|
|
*
|
|
|
|
* We mutate strings in-place, but leave them immutable once they can
|
|
|
|
* be seen by Python code.
|
2013-04-11 02:08:27 +04:00
|
|
|
*/
|
|
|
|
typedef struct {
|
|
|
|
PyObject_HEAD
|
|
|
|
PyObject *dict;
|
|
|
|
} dirsObject;
|
|
|
|
|
2015-05-09 00:13:12 +03:00
|
|
|
static inline Py_ssize_t _finddir(const char *path, Py_ssize_t pos)
|
2013-04-11 02:08:27 +04:00
|
|
|
{
|
2015-05-09 01:09:28 +03:00
|
|
|
while (pos != -1) {
|
2015-05-09 00:13:12 +03:00
|
|
|
if (path[pos] == '/')
|
2015-05-09 01:09:28 +03:00
|
|
|
break;
|
|
|
|
pos -= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return pos;
|
2013-04-11 02:08:27 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static int _addpath(PyObject *dirs, PyObject *path)
|
|
|
|
{
|
2015-05-09 01:09:28 +03:00
|
|
|
const char *cpath = PyString_AS_STRING(path);
|
|
|
|
Py_ssize_t pos = PyString_GET_SIZE(path);
|
2013-04-11 02:08:27 +04:00
|
|
|
PyObject *key = NULL;
|
2013-04-11 02:08:27 +04:00
|
|
|
int ret = -1;
|
|
|
|
|
2015-05-09 00:13:12 +03:00
|
|
|
while ((pos = _finddir(cpath, pos - 1)) != -1) {
|
2013-04-11 02:08:27 +04:00
|
|
|
PyObject *val;
|
|
|
|
|
2013-04-11 02:08:28 +04:00
|
|
|
/* It's likely that every prefix already has an entry
|
|
|
|
in our dict. Try to avoid allocating and
|
|
|
|
deallocating a string for each prefix we check. */
|
|
|
|
if (key != NULL)
|
|
|
|
((PyStringObject *)key)->ob_shash = -1;
|
2015-05-09 01:09:28 +03:00
|
|
|
else {
|
|
|
|
/* Force Python to not reuse a small shared string. */
|
|
|
|
key = PyString_FromStringAndSize(cpath,
|
|
|
|
pos < 2 ? 2 : pos);
|
2013-04-11 02:08:28 +04:00
|
|
|
if (key == NULL)
|
|
|
|
goto bail;
|
|
|
|
}
|
|
|
|
PyString_GET_SIZE(key) = pos;
|
|
|
|
PyString_AS_STRING(key)[pos] = '\0';
|
2013-04-11 02:08:27 +04:00
|
|
|
|
|
|
|
val = PyDict_GetItem(dirs, key);
|
2013-04-11 02:08:27 +04:00
|
|
|
if (val != NULL) {
|
|
|
|
PyInt_AS_LONG(val) += 1;
|
dirs: speed up by storing number of direct children per dir
The Python version of the dirs type stores only the number of direct
children associated with each directory. That means that while adding
a directory, it only has to walk backwards until it runs into a
directory that is already in its map. The C version walks all the way
to the top-most directory. By copying the Python version's clever
trick to the C code, we can speed it up quite a bit.
On the Firefox repo, perfdirs now runs in 0.031390, from 0.056518
before the undoing Sid's optimization in the previous change, and
0.061835 before previous his optimization. More practically, it speeds
up 'hg status nonexistent' on the Firefox repo from 0.176s to 0.155s.
It's unclear why the C version did not have the same cleverness
implemented from the start, especially given that they were both
written by the same person (Bryan O'Sullivan) very close in time:
36b515ad5d16 (scmutil: add a dirs class, 2013-04-10)
1b2603494a5d (scmutil: rewrite dirs in C, use if available, 2013-04-10)
2015-05-09 01:04:14 +03:00
|
|
|
break;
|
2013-04-11 02:08:27 +04:00
|
|
|
}
|
2013-04-11 02:08:27 +04:00
|
|
|
|
2013-04-11 02:08:27 +04:00
|
|
|
/* Force Python to not reuse a small shared int. */
|
|
|
|
val = PyInt_FromLong(0x1eadbeef);
|
2013-04-11 02:08:27 +04:00
|
|
|
|
2013-04-11 02:08:27 +04:00
|
|
|
if (val == NULL)
|
2013-04-11 02:08:27 +04:00
|
|
|
goto bail;
|
|
|
|
|
2013-04-11 02:08:27 +04:00
|
|
|
PyInt_AS_LONG(val) = 1;
|
|
|
|
ret = PyDict_SetItem(dirs, key, val);
|
|
|
|
Py_DECREF(val);
|
2013-04-11 02:08:27 +04:00
|
|
|
if (ret == -1)
|
|
|
|
goto bail;
|
2015-04-08 06:43:04 +03:00
|
|
|
Py_CLEAR(key);
|
2013-04-11 02:08:27 +04:00
|
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
bail:
|
|
|
|
Py_XDECREF(key);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int _delpath(PyObject *dirs, PyObject *path)
|
|
|
|
{
|
2015-05-09 00:11:00 +03:00
|
|
|
char *cpath = PyString_AS_STRING(path);
|
2015-05-09 01:09:28 +03:00
|
|
|
Py_ssize_t pos = PyString_GET_SIZE(path);
|
2013-04-11 02:08:27 +04:00
|
|
|
PyObject *key = NULL;
|
2013-04-11 02:08:27 +04:00
|
|
|
int ret = -1;
|
|
|
|
|
2015-05-09 00:13:12 +03:00
|
|
|
while ((pos = _finddir(cpath, pos - 1)) != -1) {
|
2013-04-11 02:08:27 +04:00
|
|
|
PyObject *val;
|
|
|
|
|
2015-05-09 00:11:00 +03:00
|
|
|
key = PyString_FromStringAndSize(cpath, pos);
|
2013-04-11 02:08:27 +04:00
|
|
|
|
|
|
|
if (key == NULL)
|
|
|
|
goto bail;
|
|
|
|
|
|
|
|
val = PyDict_GetItem(dirs, key);
|
|
|
|
if (val == NULL) {
|
|
|
|
PyErr_SetString(PyExc_ValueError,
|
|
|
|
"expected a value, found none");
|
|
|
|
goto bail;
|
|
|
|
}
|
|
|
|
|
dirs: speed up by storing number of direct children per dir
The Python version of the dirs type stores only the number of direct
children associated with each directory. That means that while adding
a directory, it only has to walk backwards until it runs into a
directory that is already in its map. The C version walks all the way
to the top-most directory. By copying the Python version's clever
trick to the C code, we can speed it up quite a bit.
On the Firefox repo, perfdirs now runs in 0.031390, from 0.056518
before the undoing Sid's optimization in the previous change, and
0.061835 before previous his optimization. More practically, it speeds
up 'hg status nonexistent' on the Firefox repo from 0.176s to 0.155s.
It's unclear why the C version did not have the same cleverness
implemented from the start, especially given that they were both
written by the same person (Bryan O'Sullivan) very close in time:
36b515ad5d16 (scmutil: add a dirs class, 2013-04-10)
1b2603494a5d (scmutil: rewrite dirs in C, use if available, 2013-04-10)
2015-05-09 01:04:14 +03:00
|
|
|
if (--PyInt_AS_LONG(val) <= 0) {
|
|
|
|
if (PyDict_DelItem(dirs, key) == -1)
|
|
|
|
goto bail;
|
|
|
|
} else
|
|
|
|
break;
|
2013-04-11 02:08:27 +04:00
|
|
|
Py_CLEAR(key);
|
|
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
bail:
|
|
|
|
Py_XDECREF(key);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dirs_fromdict(PyObject *dirs, PyObject *source, char skipchar)
|
|
|
|
{
|
|
|
|
PyObject *key, *value;
|
|
|
|
Py_ssize_t pos = 0;
|
|
|
|
|
|
|
|
while (PyDict_Next(source, &pos, &key, &value)) {
|
|
|
|
if (!PyString_Check(key)) {
|
|
|
|
PyErr_SetString(PyExc_TypeError, "expected string key");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (skipchar) {
|
parsers: inline fields of dirstate values in C version
Previously, while unpacking the dirstate we'd create 3-4 new CPython objects
for most dirstate values:
- the state is a single character string, which is pooled by CPython
- the mode is a new object if it isn't 0 due to being in the lookup set
- the size is a new object if it is greater than 255
- the mtime is a new object if it isn't -1 due to being in the lookup set
- the tuple to contain them all
In some cases such as regular hg status, we actually look at all the objects.
In other cases like hg add, hg status for a subdirectory, or hg status with the
third-party hgwatchman enabled, we look at almost none of the objects.
This patch eliminates most object creation in these cases by defining a custom
C struct that is exposed to Python with an interface similar to a tuple. Only
when tuple elements are actually requested are the respective objects created.
The gains, where they're expected, are significant. The following tests are run
against a working copy with over 270,000 files.
parse_dirstate becomes significantly faster:
$ hg perfdirstate
before: wall 0.186437 comb 0.180000 user 0.160000 sys 0.020000 (best of 35)
after: wall 0.093158 comb 0.100000 user 0.090000 sys 0.010000 (best of 95)
and as a result, several commands benefit:
$ time hg status # with hgwatchman enabled
before: 0.42s user 0.14s system 99% cpu 0.563 total
after: 0.34s user 0.12s system 99% cpu 0.471 total
$ time hg add new-file
before: 0.85s user 0.18s system 99% cpu 1.033 total
after: 0.76s user 0.17s system 99% cpu 0.931 total
There is a slight regression in regular status performance, but this is fixed
in an upcoming patch.
2014-05-28 01:27:41 +04:00
|
|
|
if (!dirstate_tuple_check(value)) {
|
2013-04-11 02:08:27 +04:00
|
|
|
PyErr_SetString(PyExc_TypeError,
|
parsers: inline fields of dirstate values in C version
Previously, while unpacking the dirstate we'd create 3-4 new CPython objects
for most dirstate values:
- the state is a single character string, which is pooled by CPython
- the mode is a new object if it isn't 0 due to being in the lookup set
- the size is a new object if it is greater than 255
- the mtime is a new object if it isn't -1 due to being in the lookup set
- the tuple to contain them all
In some cases such as regular hg status, we actually look at all the objects.
In other cases like hg add, hg status for a subdirectory, or hg status with the
third-party hgwatchman enabled, we look at almost none of the objects.
This patch eliminates most object creation in these cases by defining a custom
C struct that is exposed to Python with an interface similar to a tuple. Only
when tuple elements are actually requested are the respective objects created.
The gains, where they're expected, are significant. The following tests are run
against a working copy with over 270,000 files.
parse_dirstate becomes significantly faster:
$ hg perfdirstate
before: wall 0.186437 comb 0.180000 user 0.160000 sys 0.020000 (best of 35)
after: wall 0.093158 comb 0.100000 user 0.090000 sys 0.010000 (best of 95)
and as a result, several commands benefit:
$ time hg status # with hgwatchman enabled
before: 0.42s user 0.14s system 99% cpu 0.563 total
after: 0.34s user 0.12s system 99% cpu 0.471 total
$ time hg add new-file
before: 0.85s user 0.18s system 99% cpu 1.033 total
after: 0.76s user 0.17s system 99% cpu 0.931 total
There is a slight regression in regular status performance, but this is fixed
in an upcoming patch.
2014-05-28 01:27:41 +04:00
|
|
|
"expected a dirstate tuple");
|
2013-04-11 02:08:27 +04:00
|
|
|
return -1;
|
|
|
|
}
|
parsers: inline fields of dirstate values in C version
Previously, while unpacking the dirstate we'd create 3-4 new CPython objects
for most dirstate values:
- the state is a single character string, which is pooled by CPython
- the mode is a new object if it isn't 0 due to being in the lookup set
- the size is a new object if it is greater than 255
- the mtime is a new object if it isn't -1 due to being in the lookup set
- the tuple to contain them all
In some cases such as regular hg status, we actually look at all the objects.
In other cases like hg add, hg status for a subdirectory, or hg status with the
third-party hgwatchman enabled, we look at almost none of the objects.
This patch eliminates most object creation in these cases by defining a custom
C struct that is exposed to Python with an interface similar to a tuple. Only
when tuple elements are actually requested are the respective objects created.
The gains, where they're expected, are significant. The following tests are run
against a working copy with over 270,000 files.
parse_dirstate becomes significantly faster:
$ hg perfdirstate
before: wall 0.186437 comb 0.180000 user 0.160000 sys 0.020000 (best of 35)
after: wall 0.093158 comb 0.100000 user 0.090000 sys 0.010000 (best of 95)
and as a result, several commands benefit:
$ time hg status # with hgwatchman enabled
before: 0.42s user 0.14s system 99% cpu 0.563 total
after: 0.34s user 0.12s system 99% cpu 0.471 total
$ time hg add new-file
before: 0.85s user 0.18s system 99% cpu 1.033 total
after: 0.76s user 0.17s system 99% cpu 0.931 total
There is a slight regression in regular status performance, but this is fixed
in an upcoming patch.
2014-05-28 01:27:41 +04:00
|
|
|
if (((dirstateTupleObject *)value)->state == skipchar)
|
2013-04-11 02:08:27 +04:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_addpath(dirs, key) == -1)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dirs_fromiter(PyObject *dirs, PyObject *source)
|
|
|
|
{
|
|
|
|
PyObject *iter, *item = NULL;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
iter = PyObject_GetIter(source);
|
|
|
|
if (iter == NULL)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
while ((item = PyIter_Next(iter)) != NULL) {
|
|
|
|
if (!PyString_Check(item)) {
|
|
|
|
PyErr_SetString(PyExc_TypeError, "expected string");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_addpath(dirs, item) == -1)
|
|
|
|
break;
|
|
|
|
Py_CLEAR(item);
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = PyErr_Occurred() ? -1 : 0;
|
2015-01-27 18:10:04 +03:00
|
|
|
Py_DECREF(iter);
|
2013-04-11 02:08:27 +04:00
|
|
|
Py_XDECREF(item);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Calculate a refcounted set of directory names for the files in a
|
|
|
|
* dirstate.
|
|
|
|
*/
|
|
|
|
static int dirs_init(dirsObject *self, PyObject *args)
|
|
|
|
{
|
|
|
|
PyObject *dirs = NULL, *source = NULL;
|
|
|
|
char skipchar = 0;
|
|
|
|
int ret = -1;
|
|
|
|
|
|
|
|
self->dict = NULL;
|
|
|
|
|
|
|
|
if (!PyArg_ParseTuple(args, "|Oc:__init__", &source, &skipchar))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
dirs = PyDict_New();
|
|
|
|
|
|
|
|
if (dirs == NULL)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (source == NULL)
|
|
|
|
ret = 0;
|
|
|
|
else if (PyDict_Check(source))
|
|
|
|
ret = dirs_fromdict(dirs, source, skipchar);
|
|
|
|
else if (skipchar)
|
|
|
|
PyErr_SetString(PyExc_ValueError,
|
|
|
|
"skip character is only supported "
|
|
|
|
"with a dict source");
|
|
|
|
else
|
|
|
|
ret = dirs_fromiter(dirs, source);
|
|
|
|
|
|
|
|
if (ret == -1)
|
|
|
|
Py_XDECREF(dirs);
|
|
|
|
else
|
|
|
|
self->dict = dirs;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
PyObject *dirs_addpath(dirsObject *self, PyObject *args)
|
|
|
|
{
|
|
|
|
PyObject *path;
|
|
|
|
|
|
|
|
if (!PyArg_ParseTuple(args, "O!:addpath", &PyString_Type, &path))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (_addpath(self->dict, path) == -1)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
Py_RETURN_NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static PyObject *dirs_delpath(dirsObject *self, PyObject *args)
|
|
|
|
{
|
|
|
|
PyObject *path;
|
|
|
|
|
|
|
|
if (!PyArg_ParseTuple(args, "O!:delpath", &PyString_Type, &path))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (_delpath(self->dict, path) == -1)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
Py_RETURN_NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dirs_contains(dirsObject *self, PyObject *value)
|
|
|
|
{
|
|
|
|
return PyString_Check(value) ? PyDict_Contains(self->dict, value) : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dirs_dealloc(dirsObject *self)
|
|
|
|
{
|
|
|
|
Py_XDECREF(self->dict);
|
|
|
|
PyObject_Del(self);
|
|
|
|
}
|
|
|
|
|
|
|
|
static PyObject *dirs_iter(dirsObject *self)
|
|
|
|
{
|
|
|
|
return PyObject_GetIter(self->dict);
|
|
|
|
}
|
|
|
|
|
|
|
|
static PySequenceMethods dirs_sequence_methods;
|
|
|
|
|
|
|
|
static PyMethodDef dirs_methods[] = {
|
|
|
|
{"addpath", (PyCFunction)dirs_addpath, METH_VARARGS, "add a path"},
|
|
|
|
{"delpath", (PyCFunction)dirs_delpath, METH_VARARGS, "remove a path"},
|
|
|
|
{NULL} /* Sentinel */
|
|
|
|
};
|
|
|
|
|
|
|
|
static PyTypeObject dirsType = { PyObject_HEAD_INIT(NULL) };
|
|
|
|
|
|
|
|
void dirs_module_init(PyObject *mod)
|
|
|
|
{
|
|
|
|
dirs_sequence_methods.sq_contains = (objobjproc)dirs_contains;
|
|
|
|
dirsType.tp_name = "parsers.dirs";
|
|
|
|
dirsType.tp_new = PyType_GenericNew;
|
|
|
|
dirsType.tp_basicsize = sizeof(dirsObject);
|
|
|
|
dirsType.tp_dealloc = (destructor)dirs_dealloc;
|
|
|
|
dirsType.tp_as_sequence = &dirs_sequence_methods;
|
|
|
|
dirsType.tp_flags = Py_TPFLAGS_DEFAULT;
|
|
|
|
dirsType.tp_doc = "dirs";
|
|
|
|
dirsType.tp_iter = (getiterfunc)dirs_iter;
|
|
|
|
dirsType.tp_methods = dirs_methods;
|
|
|
|
dirsType.tp_init = (initproc)dirs_init;
|
|
|
|
|
|
|
|
if (PyType_Ready(&dirsType) < 0)
|
|
|
|
return;
|
|
|
|
Py_INCREF(&dirsType);
|
|
|
|
|
|
|
|
PyModule_AddObject(mod, "dirs", (PyObject *)&dirsType);
|
|
|
|
}
|