Skip to content

Commit 05de73b

Browse files
authored
Merge pull request #42 from ngoldbaum/array-storage-refactor
Store contents of static string struct in the array buffer
2 parents 21c5618 + 01f13a9 commit 05de73b

File tree

7 files changed

+179
-99
lines changed

7 files changed

+179
-99
lines changed

stringdtype/stringdtype/src/casts.c

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -40,24 +40,26 @@ string_to_string_resolve_descriptors(PyObject *NPY_UNUSED(self),
4040
}
4141

4242
static int
43-
string_to_string(PyArrayMethod_Context *context, char *const data[],
44-
npy_intp const dimensions[], npy_intp const strides[],
45-
NpyAuxData *NPY_UNUSED(auxdata))
43+
string_to_string(PyArrayMethod_Context *NPY_UNUSED(context),
44+
char *const data[], npy_intp const dimensions[],
45+
npy_intp const strides[], NpyAuxData *NPY_UNUSED(auxdata))
4646
{
4747
npy_intp N = dimensions[0];
48-
ss **in = (ss **)data[0];
49-
ss **out = (ss **)data[1];
50-
// strides are in bytes but pointer offsets are in pointer widths, so
51-
// divide by the element size (one pointer width) to get the pointer offset
52-
npy_intp in_stride = strides[0] / context->descriptors[0]->elsize;
53-
npy_intp out_stride = strides[1] / context->descriptors[1]->elsize;
48+
char *in = data[0];
49+
char *out = data[1];
50+
npy_intp in_stride = strides[0];
51+
npy_intp out_stride = strides[1];
52+
53+
ss *s = NULL, *os = NULL;
5454

5555
while (N--) {
56-
out[0] = ssdup(in[0]);
57-
if (out[0] == NULL) {
56+
load_string(in, &s);
57+
os = (ss *)out;
58+
if (ssdup(s, os) < 0) {
5859
gil_error(PyExc_MemoryError, "ssdup failed");
5960
return -1;
6061
}
62+
6163
in += in_stride;
6264
out += out_stride;
6365
}
@@ -114,7 +116,7 @@ unicode_to_string_resolve_descriptors(PyObject *NPY_UNUSED(self),
114116
// is the number of codepoints that are not trailing null codepoints. Returns
115117
// 0 on success and -1 when an invalid code point is found.
116118
static int
117-
utf8_size(Py_UCS4 *codepoints, long max_length, size_t *num_codepoints,
119+
utf8_size(const Py_UCS4 *codepoints, long max_length, size_t *num_codepoints,
118120
size_t *utf8_bytes)
119121
{
120122
size_t ucs4len = max_length;
@@ -126,7 +128,7 @@ utf8_size(Py_UCS4 *codepoints, long max_length, size_t *num_codepoints,
126128

127129
size_t num_bytes = 0;
128130

129-
for (int i = 0; i < ucs4len; i++) {
131+
for (size_t i = 0; i < ucs4len; i++) {
130132
Py_UCS4 code = codepoints[i];
131133

132134
if (code <= 0x7F) {
@@ -201,13 +203,11 @@ unicode_to_string(PyArrayMethod_Context *context, char *const data[],
201203

202204
npy_intp N = dimensions[0];
203205
Py_UCS4 *in = (Py_UCS4 *)data[0];
204-
ss **out = (ss **)data[1];
206+
char *out = data[1];
205207

206208
// 4 bytes per UCS4 character
207209
npy_intp in_stride = strides[0] / 4;
208-
// strides are in bytes but pointer offsets are in pointer widths, so
209-
// divide by the element size (one pointer width) to get the pointer offset
210-
npy_intp out_stride = strides[1] / context->descriptors[1]->elsize;
210+
npy_intp out_stride = strides[1];
211211

212212
while (N--) {
213213
size_t out_num_bytes = 0;
@@ -217,9 +217,9 @@ unicode_to_string(PyArrayMethod_Context *context, char *const data[],
217217
gil_error(PyExc_TypeError, "Invalid unicode code point found");
218218
return -1;
219219
}
220-
ss *out_ss = ssnewempty(out_num_bytes);
221-
if (out_ss == NULL) {
222-
gil_error(PyExc_MemoryError, "ssnewempty failed");
220+
ss *out_ss = (ss *)out;
221+
if (ssnewemptylen(out_num_bytes, out_ss) < 0) {
222+
gil_error(PyExc_MemoryError, "ssnewemptylen failed");
223223
return -1;
224224
}
225225
char *out_buf = out_ss->buf;
@@ -247,9 +247,6 @@ unicode_to_string(PyArrayMethod_Context *context, char *const data[],
247247
// pad string with null character
248248
out_buf[out_num_bytes] = '\0';
249249

250-
// set out to the address of the beginning of the string
251-
out[0] = out_ss;
252-
253250
in += in_stride;
254251
out += out_stride;
255252
}
@@ -329,19 +326,20 @@ string_to_unicode(PyArrayMethod_Context *context, char *const data[],
329326
NpyAuxData *NPY_UNUSED(auxdata))
330327
{
331328
npy_intp N = dimensions[0];
332-
ss **in = (ss **)data[0];
329+
char *in = data[0];
333330
Py_UCS4 *out = (Py_UCS4 *)data[1];
334-
// strides are in bytes but pointer offsets are in pointer widths, so
335-
// divide by the element size (one pointer width) to get the pointer offset
336-
npy_intp in_stride = strides[0] / context->descriptors[0]->elsize;
331+
npy_intp in_stride = strides[0];
337332
// 4 bytes per UCS4 character
338333
npy_intp out_stride = strides[1] / 4;
339334
// max number of 4 byte UCS4 characters that can fit in the output
340335
long max_out_size = (context->descriptors[1]->elsize) / 4;
341336

337+
ss *s = NULL;
338+
342339
while (N--) {
343-
unsigned char *this_string = (unsigned char *)((*in)->buf);
344-
size_t n_bytes = (*in)->len;
340+
load_string(in, &s);
341+
unsigned char *this_string = (unsigned char *)(s->buf);
342+
size_t n_bytes = s->len;
345343
size_t tot_n_bytes = 0;
346344

347345
for (int i = 0; i < max_out_size; i++) {
@@ -363,6 +361,7 @@ string_to_unicode(PyArrayMethod_Context *context, char *const data[],
363361
break;
364362
}
365363
}
364+
366365
in += in_stride;
367366
out += out_stride;
368367
}

stringdtype/stringdtype/src/dtype.c

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ new_stringdtype_instance(void)
1616
if (new == NULL) {
1717
return NULL;
1818
}
19-
new->base.elsize = sizeof(ss *);
20-
new->base.alignment = _Alignof(ss *);
19+
new->base.elsize = sizeof(ss);
20+
new->base.alignment = _Alignof(ss);
2121
new->base.flags |= NPY_NEEDS_INIT;
2222
new->base.flags |= NPY_LIST_PICKLE;
2323
new->base.flags |= NPY_ITEM_REFCOUNT;
@@ -119,26 +119,42 @@ stringdtype_setitem(StringDTypeObject *NPY_UNUSED(descr), PyObject *obj,
119119
return -1;
120120
}
121121

122-
ss *str_val = ssnewlen(val, length);
123-
if (str_val == NULL) {
124-
PyErr_SetString(PyExc_MemoryError, "ssnewlen failed");
122+
// free if dataptr holds preexisting string data,
123+
// ssfree does a NULL check
124+
ssfree((ss *)dataptr);
125+
126+
// copies contents of val into item_val->buf
127+
int res = ssnewlen(val, length, (ss *)dataptr);
128+
129+
// val_obj must stay alive until here to ensure *val* doesn't get
130+
// deallocated
131+
Py_DECREF(val_obj);
132+
133+
if (res == -1) {
134+
PyErr_NoMemory();
125135
return -1;
126136
}
127-
// the dtype instance has the NPY_NEEDS_INIT flag set,
128-
// so if *dataptr is NULL, that means we're initializing
129-
// the array and don't need to free an existing string
130-
if (*dataptr != NULL) {
131-
free((ss *)*dataptr);
137+
else if (res == -2) {
138+
// this should never happen
139+
assert(0);
132140
}
133-
*dataptr = (char *)str_val;
134-
Py_DECREF(val_obj);
141+
135142
return 0;
136143
}
137144

138145
static PyObject *
139-
stringdtype_getitem(StringDTypeObject *descr, char **dataptr)
146+
stringdtype_getitem(StringDTypeObject *NPY_UNUSED(descr), char **dataptr)
140147
{
141-
PyObject *val_obj = PyUnicode_FromString(((ss *)*dataptr)->buf);
148+
char *data;
149+
150+
if (*dataptr == NULL) {
151+
data = "\0";
152+
}
153+
else {
154+
data = ((ss *)dataptr)->buf;
155+
}
156+
157+
PyObject *val_obj = PyUnicode_FromString(data);
142158

143159
if (val_obj == NULL) {
144160
return NULL;
@@ -147,10 +163,6 @@ stringdtype_getitem(StringDTypeObject *descr, char **dataptr)
147163
PyObject *res = PyObject_CallFunctionObjArgs((PyObject *)StringScalar_Type,
148164
val_obj, NULL);
149165

150-
if (res == NULL) {
151-
return NULL;
152-
}
153-
154166
Py_DECREF(val_obj);
155167

156168
return res;
@@ -161,8 +173,8 @@ stringdtype_getitem(StringDTypeObject *descr, char **dataptr)
161173
int
162174
compare_strings(char **a, char **b, PyArrayObject *NPY_UNUSED(arr))
163175
{
164-
ss *ss_a = (ss *)*a;
165-
ss *ss_b = (ss *)*b;
176+
ss *ss_a = (ss *)a;
177+
ss *ss_b = (ss *)b;
166178
return strcmp(ss_a->buf, ss_b->buf);
167179
}
168180

@@ -181,8 +193,8 @@ stringdtype_clear_loop(void *NPY_UNUSED(traverse_context),
181193
{
182194
while (size--) {
183195
if (data != NULL) {
184-
free(*(ss **)data);
185-
*(ss **)data = NULL;
196+
ssfree((ss *)data);
197+
memset(data, 0, sizeof(ss));
186198
}
187199
data += stride;
188200
}

stringdtype/stringdtype/src/main.c

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,16 +50,15 @@ _memory_usage(PyObject *NPY_UNUSED(self), PyObject *obj)
5050

5151
// initialize with the size of the internal buffer
5252
size_t memory_usage = PyArray_NBYTES(arr);
53-
size_t struct_size = sizeof(ss);
5453

5554
do {
56-
ss **in = (ss **)*dataptr;
57-
npy_intp stride = *strideptr / descr->elsize;
55+
char *in = dataptr[0];
56+
npy_intp stride = *strideptr;
5857
npy_intp count = *innersizeptr;
5958

6059
while (count--) {
6160
// +1 byte for the null terminator
62-
memory_usage += (*in)->len + struct_size + 1;
61+
memory_usage += ((ss *)in)->len + 1;
6362
in += stride;
6463
}
6564

Lines changed: 58 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,77 @@
11
#include "static_string.h"
22

3-
// allocates a new ss string of length len, filling with the contents of init
4-
ss *
5-
ssnewlen(const char *init, size_t len)
3+
int
4+
ssnewlen(const char *init, size_t len, ss *to_init)
65
{
6+
if ((to_init->buf != NULL) || (to_init->len != 0)) {
7+
return -2;
8+
}
9+
710
// one extra byte for null terminator
8-
ss *ret = (ss *)malloc(sizeof(ss) + sizeof(char) * (len + 1));
11+
char *ret_buf = (char *)malloc(sizeof(char) * (len + 1));
912

10-
if (ret == NULL) {
11-
return NULL;
13+
if (ret_buf == NULL) {
14+
return -1;
1215
}
1316

14-
ret->len = len;
17+
to_init->len = len;
1518

1619
if (len > 0) {
17-
memcpy(ret->buf, init, len);
20+
memcpy(ret_buf, init, len);
1821
}
1922

20-
ret->buf[len] = '\0';
23+
ret_buf[len] = '\0';
24+
25+
to_init->buf = ret_buf;
26+
27+
return 0;
28+
}
2129

22-
return ret;
30+
void
31+
ssfree(ss *str)
32+
{
33+
if (str->buf != NULL) {
34+
free(str->buf);
35+
str->buf = NULL;
36+
}
37+
str->len = 0;
2338
}
2439

25-
// returns a new heap-allocated copy of input string *s*
26-
ss *
27-
ssdup(const ss *s)
40+
int
41+
ssdup(ss *in, ss *out)
2842
{
29-
return ssnewlen(s->buf, s->len);
43+
return ssnewlen(in->buf, in->len, out);
3044
}
3145

32-
// returns a new, empty string of length len
33-
// does not do any initialization, the caller must
34-
// initialize and null-terminate the string
35-
ss *
36-
ssnewempty(size_t len)
46+
int
47+
ssnewemptylen(size_t num_bytes, ss *out)
3748
{
38-
ss *ret = (ss *)malloc(sizeof(ss) + sizeof(char) * (len + 1));
39-
ret->len = len;
40-
return ret;
49+
if (out->len != 0 || out->buf != NULL) {
50+
return -2;
51+
}
52+
53+
char *buf = (char *)malloc(sizeof(char) * (num_bytes + 1));
54+
55+
if (buf == NULL) {
56+
return -1;
57+
}
58+
59+
out->buf = buf;
60+
out->len = num_bytes;
61+
62+
return 0;
63+
}
64+
65+
static ss EMPTY = {0, "\0"};
66+
67+
void
68+
load_string(char *data, ss **out)
69+
{
70+
ss *ss_d = (ss *)data;
71+
if (ss_d->len == 0) {
72+
*out = &EMPTY;
73+
}
74+
else {
75+
*out = ss_d;
76+
}
4177
}

0 commit comments

Comments
 (0)