Mercurial > hg
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 } |