comparison mercurial/dirs.c @ 18900:02ee846b246a

scmutil: rewrite dirs in C, use if available This is over twice as fast as the Python dirs code. Upcoming changes will nearly double its speed again. perfdirs results for a working dir with 170,000 files: Python 638 msec C 244
author Bryan O'Sullivan <bryano@fb.com>
date Wed, 10 Apr 2013 15:08:27 -0700
parents
children 66d3aebe2d95
comparison
equal deleted inserted replaced
18899:d8ff607ef721 18900:02ee846b246a
1 /*
2 dirs.c - dynamic directory diddling for dirstates
3
4 Copyright 2013 Facebook
5
6 This software may be used and distributed according to the terms of
7 the GNU General Public License, incorporated herein by reference.
8 */
9
10 #define PY_SSIZE_T_CLEAN
11 #include <Python.h>
12 #include "util.h"
13
14 /*
15 * This is a multiset of directory names, built from the files that
16 * appear in a dirstate or manifest.
17 */
18 typedef struct {
19 PyObject_HEAD
20 PyObject *dict;
21 } dirsObject;
22
23 static inline Py_ssize_t _finddir(PyObject *path, Py_ssize_t pos)
24 {
25 const char *s = PyString_AS_STRING(path);
26
27 while (pos != -1) {
28 if (s[pos] == '/')
29 break;
30 pos -= 1;
31 }
32
33 return pos;
34 }
35
36 static int _addpath(PyObject *dirs, PyObject *path)
37 {
38 Py_ssize_t pos = PyString_GET_SIZE(path);
39 PyObject *newval = NULL, *key = NULL;
40 int ret = -1;
41
42 while ((pos = _finddir(path, pos - 1)) != -1) {
43 PyObject *val;
44 long v = 0;
45
46 key = PyString_FromStringAndSize(PyString_AS_STRING(path), pos);
47
48 if (key == NULL)
49 goto bail;
50
51 val = PyDict_GetItem(dirs, key);
52 if (val != NULL)
53 v = PyInt_AS_LONG(val);
54
55 newval = PyInt_FromLong(v + 1);
56
57 if (newval == NULL)
58 goto bail;
59
60 ret = PyDict_SetItem(dirs, key, newval);
61 if (ret == -1)
62 goto bail;
63 Py_CLEAR(key);
64 Py_CLEAR(newval);
65 }
66 ret = 0;
67
68 bail:
69 Py_XDECREF(key);
70 Py_XDECREF(newval);
71
72 return ret;
73 }
74
75 static int _delpath(PyObject *dirs, PyObject *path)
76 {
77 Py_ssize_t pos = PyString_GET_SIZE(path);
78 PyObject *newval = NULL, *key = NULL;
79 int ret = -1;
80
81 while ((pos = _finddir(path, pos - 1)) != -1) {
82 PyObject *val;
83 long v;
84
85 key = PyString_FromStringAndSize(PyString_AS_STRING(path), pos);
86
87 if (key == NULL)
88 goto bail;
89
90 val = PyDict_GetItem(dirs, key);
91 if (val == NULL) {
92 PyErr_SetString(PyExc_ValueError,
93 "expected a value, found none");
94 goto bail;
95 }
96 v = PyInt_AS_LONG(val);
97
98 if (v <= 1) {
99 if (PyDict_DelItem(dirs, key) == -1)
100 goto bail;
101 continue;
102 }
103 newval = PyInt_FromLong(v - 1);
104
105 if (newval == NULL)
106 goto bail;
107
108 ret = PyDict_SetItem(dirs, key, newval);
109 if (ret == -1)
110 goto bail;
111 Py_CLEAR(key);
112 Py_CLEAR(newval);
113 }
114 ret = 0;
115
116 bail:
117 Py_XDECREF(key);
118 Py_XDECREF(newval);
119
120 return ret;
121 }
122
123 static int dirs_fromdict(PyObject *dirs, PyObject *source, char skipchar)
124 {
125 PyObject *key, *value;
126 Py_ssize_t pos = 0;
127
128 while (PyDict_Next(source, &pos, &key, &value)) {
129 if (!PyString_Check(key)) {
130 PyErr_SetString(PyExc_TypeError, "expected string key");
131 return -1;
132 }
133 if (skipchar) {
134 PyObject *st;
135
136 if (!PyTuple_Check(value) ||
137 PyTuple_GET_SIZE(value) == 0) {
138 PyErr_SetString(PyExc_TypeError,
139 "expected non-empty tuple");
140 return -1;
141 }
142
143 st = PyTuple_GET_ITEM(value, 0);
144
145 if (!PyString_Check(st) || PyString_GET_SIZE(st) == 0) {
146 PyErr_SetString(PyExc_TypeError,
147 "expected non-empty string "
148 "at tuple index 0");
149 return -1;
150 }
151
152 if (PyString_AS_STRING(st)[0] == skipchar)
153 continue;
154 }
155
156 if (_addpath(dirs, key) == -1)
157 return -1;
158 }
159
160 return 0;
161 }
162
163 static int dirs_fromiter(PyObject *dirs, PyObject *source)
164 {
165 PyObject *iter, *item = NULL;
166 int ret;
167
168 iter = PyObject_GetIter(source);
169 if (iter == NULL)
170 return -1;
171
172 while ((item = PyIter_Next(iter)) != NULL) {
173 if (!PyString_Check(item)) {
174 PyErr_SetString(PyExc_TypeError, "expected string");
175 break;
176 }
177
178 if (_addpath(dirs, item) == -1)
179 break;
180 Py_CLEAR(item);
181 }
182
183 ret = PyErr_Occurred() ? -1 : 0;
184 Py_XDECREF(item);
185 return ret;
186 }
187
188 /*
189 * Calculate a refcounted set of directory names for the files in a
190 * dirstate.
191 */
192 static int dirs_init(dirsObject *self, PyObject *args)
193 {
194 PyObject *dirs = NULL, *source = NULL;
195 char skipchar = 0;
196 int ret = -1;
197
198 self->dict = NULL;
199
200 if (!PyArg_ParseTuple(args, "|Oc:__init__", &source, &skipchar))
201 return -1;
202
203 dirs = PyDict_New();
204
205 if (dirs == NULL)
206 return -1;
207
208 if (source == NULL)
209 ret = 0;
210 else if (PyDict_Check(source))
211 ret = dirs_fromdict(dirs, source, skipchar);
212 else if (skipchar)
213 PyErr_SetString(PyExc_ValueError,
214 "skip character is only supported "
215 "with a dict source");
216 else
217 ret = dirs_fromiter(dirs, source);
218
219 if (ret == -1)
220 Py_XDECREF(dirs);
221 else
222 self->dict = dirs;
223
224 return ret;
225 }
226
227 PyObject *dirs_addpath(dirsObject *self, PyObject *args)
228 {
229 PyObject *path;
230
231 if (!PyArg_ParseTuple(args, "O!:addpath", &PyString_Type, &path))
232 return NULL;
233
234 if (_addpath(self->dict, path) == -1)
235 return NULL;
236
237 Py_RETURN_NONE;
238 }
239
240 static PyObject *dirs_delpath(dirsObject *self, PyObject *args)
241 {
242 PyObject *path;
243
244 if (!PyArg_ParseTuple(args, "O!:delpath", &PyString_Type, &path))
245 return NULL;
246
247 if (_delpath(self->dict, path) == -1)
248 return NULL;
249
250 Py_RETURN_NONE;
251 }
252
253 static int dirs_contains(dirsObject *self, PyObject *value)
254 {
255 return PyString_Check(value) ? PyDict_Contains(self->dict, value) : 0;
256 }
257
258 static void dirs_dealloc(dirsObject *self)
259 {
260 Py_XDECREF(self->dict);
261 PyObject_Del(self);
262 }
263
264 static PyObject *dirs_iter(dirsObject *self)
265 {
266 return PyObject_GetIter(self->dict);
267 }
268
269 static PySequenceMethods dirs_sequence_methods;
270
271 static PyMethodDef dirs_methods[] = {
272 {"addpath", (PyCFunction)dirs_addpath, METH_VARARGS, "add a path"},
273 {"delpath", (PyCFunction)dirs_delpath, METH_VARARGS, "remove a path"},
274 {NULL} /* Sentinel */
275 };
276
277 static PyTypeObject dirsType = { PyObject_HEAD_INIT(NULL) };
278
279 void dirs_module_init(PyObject *mod)
280 {
281 dirs_sequence_methods.sq_contains = (objobjproc)dirs_contains;
282 dirsType.tp_name = "parsers.dirs";
283 dirsType.tp_new = PyType_GenericNew;
284 dirsType.tp_basicsize = sizeof(dirsObject);
285 dirsType.tp_dealloc = (destructor)dirs_dealloc;
286 dirsType.tp_as_sequence = &dirs_sequence_methods;
287 dirsType.tp_flags = Py_TPFLAGS_DEFAULT;
288 dirsType.tp_doc = "dirs";
289 dirsType.tp_iter = (getiterfunc)dirs_iter;
290 dirsType.tp_methods = dirs_methods;
291 dirsType.tp_init = (initproc)dirs_init;
292
293 if (PyType_Ready(&dirsType) < 0)
294 return;
295 Py_INCREF(&dirsType);
296
297 PyModule_AddObject(mod, "dirs", (PyObject *)&dirsType);
298 }