Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-bitarray for openSUSE:Factory
checked in at 2023-02-16 16:55:44
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-bitarray (Old)
and /work/SRC/openSUSE:Factory/.python-bitarray.new.22824 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-bitarray"
Thu Feb 16 16:55:44 2023 rev:16 rq:1065971 version:2.7.2
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-bitarray/python-bitarray.changes
2023-02-11 21:57:50.223814417 +0100
+++
/work/SRC/openSUSE:Factory/.python-bitarray.new.22824/python-bitarray.changes
2023-02-16 16:55:58.754731167 +0100
@@ -1,0 +2,10 @@
+Wed Feb 15 13:59:36 UTC 2023 - Dirk Müller <[email protected]>
+
+- update to 2.7.2:
+ * speedup all count functionality by using
+ ``__builtin_popcountll`` when available
+ * add ``popcount64()`` to ``bitarray.h`` - we assume now that
+ ``uint64_t`` is always available
+ * improve testing
+
+-------------------------------------------------------------------
Old:
----
bitarray-2.7.1.tar.gz
New:
----
bitarray-2.7.2.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-bitarray.spec ++++++
--- /var/tmp/diff_new_pack.LO5IPt/_old 2023-02-16 16:55:59.306733373 +0100
+++ /var/tmp/diff_new_pack.LO5IPt/_new 2023-02-16 16:55:59.314733406 +0100
@@ -18,7 +18,7 @@
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
Name: python-bitarray
-Version: 2.7.1
+Version: 2.7.2
Release: 0
Summary: Efficient Arrays of Booleans
License: Python-2.0
++++++ bitarray-2.7.1.tar.gz -> bitarray-2.7.2.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/bitarray-2.7.1/CHANGE_LOG
new/bitarray-2.7.2/CHANGE_LOG
--- old/bitarray-2.7.1/CHANGE_LOG 2023-02-10 16:30:12.000000000 +0100
+++ new/bitarray-2.7.2/CHANGE_LOG 2023-02-13 00:51:35.000000000 +0100
@@ -1,3 +1,12 @@
+2023-02-12 2.7.2:
+-------------------
+ * speedup all count functionality by using `__builtin_popcountll` when
+ available, see #187
+ * add `popcount64()` to `bitarray.h` - we assume now that `uint64_t` is
+ always available
+ * improve testing
+
+
2023-02-10 2.7.1:
-------------------
* optimize `util.sc_encode()`
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/bitarray-2.7.1/README.rst
new/bitarray-2.7.2/README.rst
--- old/bitarray-2.7.1/README.rst 2023-02-10 16:30:12.000000000 +0100
+++ new/bitarray-2.7.2/README.rst 2023-02-13 00:51:35.000000000 +0100
@@ -63,20 +63,20 @@
$ python -c 'import bitarray; bitarray.test()'
bitarray is installed in: /Users/ilan/bitarray/bitarray
- bitarray version: 2.7.1
+ bitarray version: 2.7.2
sys.version: 3.11.0 (main, Oct 25 2022) [Clang 14.0.4]
sys.prefix: /Users/ilan/Mini3/envs/py311
pointer size: 64 bit
sizeof(size_t): 8
sizeof(bitarrayobject): 80
- PY_UINT64_T defined: 1
- USE_WORD_SHIFT: 1
+ __clang__ or __GNUC__ defined: 1
+ PY_LITTLE_ENDIAN (use word shift): 1
DEBUG: 0
.........................................................................
.........................................................................
................................................................
----------------------------------------------------------------------
- Ran 464 tests in 0.472s
+ Ran 466 tests in 0.460s
OK
@@ -401,7 +401,7 @@
Reference
=========
-bitarray version: 2.7.1 -- `change log
<https://github.com/ilanschnell/bitarray/blob/master/doc/changelog.rst>`__
+bitarray version: 2.7.2 -- `change log
<https://github.com/ilanschnell/bitarray/blob/master/doc/changelog.rst>`__
In the following, ``item`` and ``value`` are usually a single bit -
an integer 0 or 1.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/bitarray-2.7.1/bitarray/_bitarray.c
new/bitarray-2.7.2/bitarray/_bitarray.c
--- old/bitarray-2.7.1/bitarray/_bitarray.c 2023-02-10 16:30:12.000000000
+0100
+++ new/bitarray-2.7.2/bitarray/_bitarray.c 2023-02-13 00:51:35.000000000
+0100
@@ -193,24 +193,6 @@
}
}
-#ifdef PY_UINT64_T
-#define UINT64_BUFFER(self) ((PY_UINT64_T *) (self)->ob_item)
-#define UINT64_WORDS(bytes) ((bytes) >> 3)
-/* use 64-bit word shift only when the machine has little endian byteorder */
-#ifdef PY_LITTLE_ENDIAN
-#define USE_WORD_SHIFT PY_LITTLE_ENDIAN
-#else
-#define USE_WORD_SHIFT (*((PY_UINT64_T *) "\xff\0\0\0\0\0\0\0") == 0xff)
-#endif
-#else /* !PY_UINT64_T */
-/* The UINT64_BUFFER macro only exists here in order to write code which
- complies with and without PY_UINT64_T defined (in order to avoid
- #ifdef'ing the code below). */
-#define UINT64_BUFFER(self) ((self)->ob_item)
-#define UINT64_WORDS(bytes) 0
-#define USE_WORD_SHIFT 0
-#endif
-
/* Shift bits in byte-range(a, b) by n bits to right (using uint64 shifts
when possible).
The parameter (bebr = big endian byte reverse) is used to allow this
@@ -235,7 +217,7 @@
if (bebr && IS_BE(self))
bytereverse(self, a, b);
- if (USE_WORD_SHIFT && b >= a + 8) {
+ if (PY_LITTLE_ENDIAN && b >= a + 8) {
const Py_ssize_t wa = (a + 7) / 8; /* word range(wa, wb) */
const Py_ssize_t wb = b / 8;
const Py_ssize_t va = 8 * wa, vb = 8 * wb;
@@ -251,7 +233,7 @@
for (i = wb - 1; i >= wa; i--) {
assert_byte_in_range(self, 8 * i + 7);
/* shift word - assumes machine has little endian byteorder */
- UINT64_BUFFER(self)[i] <<= n;
+ ((uint64_t *) ucbuff)[i] <<= n;
if (i != wa) /* add shifted byte from next lower word */
ucbuff[8 * i] |= ucbuff[8 * i - 1] >> m;
}
@@ -381,15 +363,17 @@
invert(bitarrayobject *self)
{
const Py_ssize_t nbytes = Py_SIZE(self);
- const Py_ssize_t nwords = UINT64_WORDS(nbytes);
+ const Py_ssize_t cwords = nbytes / 8; /* complete 64-bit words */
+ char *buff = self->ob_item;
+ uint64_t *wbuff = (uint64_t *) buff;
Py_ssize_t i;
assert_nbits(self);
assert(self->readonly == 0);
- for (i = 0; i < nwords; i++)
- UINT64_BUFFER(self)[i] = ~UINT64_BUFFER(self)[i];
- for (i = 8 * nwords; i < nbytes; i++)
- self->ob_item[i] = ~self->ob_item[i];
+ for (i = 0; i < cwords; i++)
+ wbuff[i] = ~wbuff[i];
+ for (i = 8 * cwords; i < nbytes; i++)
+ buff[i] = ~buff[i];
}
/* repeat self m times (negative m is treated as 0) */
@@ -457,17 +441,30 @@
static Py_ssize_t
count(bitarrayobject *self, Py_ssize_t a, Py_ssize_t b)
{
+ const Py_ssize_t n = b - a;
Py_ssize_t res = 0, i;
assert(0 <= a && a <= self->nbits);
assert(0 <= b && b <= self->nbits);
- if (a >= b)
+ if (n <= 0)
return 0;
- if (b >= a + 8) {
+ if (n >= 64) {
+ const Py_ssize_t word_a = (a + 63) / 64;
+ const Py_ssize_t word_b = b / 64;
+ const uint64_t *wbuff = (uint64_t *) self->ob_item;
+
+ assert(a + 64 > 64 * word_a && 64 * word_b + 64 > b);
+
+ res += count(self, a, 64 * word_a);
+ for (i = word_a; i < word_b; i++)
+ res += popcount64(wbuff[i]);
+ res += count(self, 64 * word_b, b);
+ }
+ else if (n >= 8) {
const Py_ssize_t byte_a = BYTES(a);
const Py_ssize_t byte_b = b / 8;
- unsigned char *ucbuff = (unsigned char *) self->ob_item;
+ const unsigned char *ucbuff = (unsigned char *) self->ob_item;
assert(a + 8 > 8 * byte_a && 8 * byte_b + 8 > b);
@@ -496,14 +493,14 @@
if (n <= 0)
return -1;
-#ifdef PY_UINT64_T
/* When the search range is greater than 64 bits, we skip uint64 words.
Note that we cannot check for n >= 64 here as the function could then
go into an infinite recursive loop when a word is found. */
if (n > 64) {
- Py_ssize_t word_a = (a + 63) / 64;
- Py_ssize_t word_b = b / 64;
- PY_UINT64_T *wbuff = (PY_UINT64_T *) self->ob_item, w = vi ? 0 : ~0;
+ const Py_ssize_t word_a = (a + 63) / 64;
+ const Py_ssize_t word_b = b / 64;
+ const uint64_t *wbuff = (uint64_t *) self->ob_item;
+ const uint64_t w = vi ? 0 : ~0;
if ((res = find_bit(self, vi, a, 64 * word_a)) >= 0)
return res;
@@ -515,14 +512,14 @@
}
return find_bit(self, vi, 64 * word_b, b);
}
-#endif
+
/* For the same reason as above, we cannot check for n >= 8 here. */
if (n > 8) {
- Py_ssize_t byte_a = BYTES(a);
- Py_ssize_t byte_b = b / 8;
- char *buff = self->ob_item, c = vi ? 0 : ~0;
+ const Py_ssize_t byte_a = BYTES(a);
+ const Py_ssize_t byte_b = b / 8;
+ const char *buff = self->ob_item;
+ const char c = vi ? 0 : ~0;
- assert(UINT64_WORDS(8) == 0 || n <= 64);
if ((res = find_bit(self, vi, a, 8 * byte_a)) >= 0)
return res;
@@ -533,7 +530,7 @@
}
return find_bit(self, vi, 8 * byte_b, b);
}
- assert(n <= 8);
+
for (i = a; i < b; i++) {
if (getbit(self, i) == vi)
return i;
@@ -754,7 +751,7 @@
return extend_bitarray(self, (bitarrayobject *) obj);
if (PyBytes_Check(obj)) { /* bytes 01 */
-#ifdef IS_PY3K
+#if IS_PY3K
PyErr_SetString(PyExc_TypeError,
"cannot extend bitarray with 'bytes', "
"use .pack() or .frombytes() instead");
@@ -1089,7 +1086,7 @@
assert(PyLong_Check(result));
if (PyLong_AsSsize_t(result) < 0) {
Py_DECREF(result);
-#ifdef IS_PY3K
+#if IS_PY3K
PyErr_Format(PyExc_ValueError, "%A not in bitarray",
PyTuple_GET_ITEM(args, 0));
#else
@@ -1415,7 +1412,7 @@
static PyObject *
bitarray_tolist(bitarrayobject *self)
{
- PyObject *list, *item;
+ PyObject *list;
Py_ssize_t i;
list = PyList_New(self->nbits);
@@ -1423,7 +1420,7 @@
return NULL;
for (i = 0; i < self->nbits; i++) {
- item = PyLong_FromLong(getbit(self, i));
+ PyObject *item = PyLong_FromLong(getbit(self, i));
if (item == NULL) {
Py_DECREF(list);
return NULL;
@@ -2177,32 +2174,36 @@
bitwise(bitarrayobject *self, bitarrayobject *other, const char oper)
{
const Py_ssize_t nbytes = Py_SIZE(self);
- const Py_ssize_t nwords = UINT64_WORDS(nbytes);
+ const Py_ssize_t cwords = nbytes / 8; /* complete 64-bit words */
Py_ssize_t i;
+ char *buff_s = self->ob_item;
+ char *buff_o = other->ob_item;
+ uint64_t *wbuff_s = (uint64_t *) buff_s;
+ uint64_t *wbuff_o = (uint64_t *) buff_o;
assert(self->nbits == other->nbits);
assert(self->endian == other->endian);
assert_nbits(self);
switch (oper) {
case '&':
- for (i = 0; i < nwords; i++)
- UINT64_BUFFER(self)[i] &= UINT64_BUFFER(other)[i];
- for (i = 8 * nwords; i < nbytes; i++)
- self->ob_item[i] &= other->ob_item[i];
+ for (i = 0; i < cwords; i++)
+ wbuff_s[i] &= wbuff_o[i];
+ for (i = 8 * cwords; i < nbytes; i++)
+ buff_s[i] &= buff_o[i];
break;
case '|':
- for (i = 0; i < nwords; i++)
- UINT64_BUFFER(self)[i] |= UINT64_BUFFER(other)[i];
- for (i = 8 * nwords; i < nbytes; i++)
- self->ob_item[i] |= other->ob_item[i];
+ for (i = 0; i < cwords; i++)
+ wbuff_s[i] |= wbuff_o[i];
+ for (i = 8 * cwords; i < nbytes; i++)
+ buff_s[i] |= buff_o[i];
break;
case '^':
- for (i = 0; i < nwords; i++)
- UINT64_BUFFER(self)[i] ^= UINT64_BUFFER(other)[i];
- for (i = 8 * nwords; i < nbytes; i++)
- self->ob_item[i] ^= other->ob_item[i];
+ for (i = 0; i < cwords; i++)
+ wbuff_s[i] ^= wbuff_o[i];
+ for (i = 8 * cwords; i < nbytes; i++)
+ buff_s[i] ^= buff_o[i];
break;
default:
@@ -2451,7 +2452,7 @@
value = PyDict_GetItem(codedict, symbol);
Py_DECREF(symbol);
if (value == NULL) {
-#ifdef IS_PY3K
+#if IS_PY3K
PyErr_Format(PyExc_ValueError,
"symbol not defined in prefix code: %A", symbol);
#else
@@ -2552,7 +2553,7 @@
return 0;
ambiguity:
-#ifdef IS_PY3K
+#if IS_PY3K
PyErr_Format(PyExc_ValueError, "prefix code ambiguous: %A", symbol);
#else
PyErr_SetString(PyExc_ValueError, "prefix code ambiguous");
@@ -2818,7 +2819,7 @@
create a binary tree object to be passed to `.decode()` or `.iterdecode()`.");
static PyTypeObject DecodeTree_Type = {
-#ifdef IS_PY3K
+#if IS_PY3K
PyVarObject_HEAD_INIT(NULL, 0)
#else
PyObject_HEAD_INIT(NULL)
@@ -3007,7 +3008,7 @@
}
static PyTypeObject DecodeIter_Type = {
-#ifdef IS_PY3K
+#if IS_PY3K
PyVarObject_HEAD_INIT(NULL, 0)
#else
PyObject_HEAD_INIT(NULL)
@@ -3112,7 +3113,7 @@
}
static PyTypeObject SearchIter_Type = {
-#ifdef IS_PY3K
+#if IS_PY3K
PyVarObject_HEAD_INIT(NULL, 0)
#else
PyObject_HEAD_INIT(NULL)
@@ -3556,7 +3557,7 @@
}
static PyTypeObject BitarrayIter_Type = {
-#ifdef IS_PY3K
+#if IS_PY3K
PyVarObject_HEAD_INIT(NULL, 0)
#else
PyObject_HEAD_INIT(NULL)
@@ -3711,7 +3712,7 @@
static PyTypeObject Bitarray_Type = {
-#ifdef IS_PY3K
+#if IS_PY3K
PyVarObject_HEAD_INIT(NULL, 0)
#else
PyObject_HEAD_INIT(NULL)
@@ -3813,7 +3814,7 @@
(int) sizeof(bitarrayobject),
(int) sizeof(decodetreeobject),
(int) sizeof(binode),
-#ifdef PY_UINT64_T
+#if (defined(__clang__) || defined(__GNUC__))
1,
#else
0,
@@ -3823,7 +3824,7 @@
#else
0,
#endif
- (int) USE_WORD_SHIFT
+ (int) PY_LITTLE_ENDIAN
);
}
@@ -3837,9 +3838,9 @@
2. sizeof(bitarrayobject)\n\
3. sizeof(decodetreeobject)\n\
4. sizeof(binode)\n\
-5. PY_UINT64_T defined\n\
+5. __clang__ or __GNUC__ defined\n\
6. NDEBUG not defined\n\
-7. USE_WORD_SHIFT");
+7. PY_LITTLE_ENDIAN");
static PyMethodDef module_functions[] = {
@@ -3854,14 +3855,14 @@
/******************************* Install Module ***************************/
-#ifdef IS_PY3K
+#if IS_PY3K
static PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT, "_bitarray", 0, -1, module_functions,
};
#endif
PyMODINIT_FUNC
-#ifdef IS_PY3K
+#if IS_PY3K
PyInit__bitarray(void)
#else
init_bitarray(void)
@@ -3871,7 +3872,7 @@
setup_reverse_trans();
-#ifdef IS_PY3K
+#if IS_PY3K
m = PyModule_Create(&moduledef);
#else
m = Py_InitModule3("_bitarray", module_functions, 0);
@@ -3905,7 +3906,7 @@
PyModule_AddObject(m, "__version__",
Py_BuildValue("s", BITARRAY_VERSION));
-#ifdef IS_PY3K
+#if IS_PY3K
return m;
error:
return NULL;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/bitarray-2.7.1/bitarray/_util.c
new/bitarray-2.7.2/bitarray/_util.c
--- old/bitarray-2.7.1/bitarray/_util.c 2023-02-10 16:30:12.000000000 +0100
+++ new/bitarray-2.7.2/bitarray/_util.c 2023-02-13 00:51:35.000000000 +0100
@@ -91,10 +91,10 @@
/* Return the smallest index i for which a.count(vi, 0, i) == n.
When n exceeds the total count, return -1. */
static Py_ssize_t
-count_to_n(bitarrayobject *a, Py_ssize_t n, int vi)
+count_n_core(bitarrayobject *a, Py_ssize_t n, int vi)
{
const Py_ssize_t nbits = a->nbits;
- unsigned char *ucbuff = (unsigned char *) a->ob_item;
+ uint64_t *wbuff = (uint64_t *) a->ob_item;
Py_ssize_t i = 0; /* index */
Py_ssize_t j = 0; /* total count up to index */
Py_ssize_t block_start, block_stop, k, m;
@@ -108,11 +108,11 @@
while (i + BLOCK_BITS < nbits) {
m = 0;
assert(i % 8 == 0);
- block_start = i >> 3;
- block_stop = block_start + (BLOCK_BITS >> 3);
- assert(block_stop <= Py_SIZE(a));
+ block_start = i / 64;
+ block_stop = block_start + (BLOCK_BITS / 64);
+ assert(8 * block_stop <= Py_SIZE(a));
for (k = block_start; k < block_stop; k++)
- m += bitcount_lookup[ucbuff[k]];
+ m += popcount64(wbuff[k]);
if (!vi)
m = BLOCK_BITS - m;
if (j + m >= n)
@@ -122,16 +122,16 @@
}
#undef BLOCK_BITS
- while (i + 8 < nbits) {
- k = i >> 3;
- assert(k < Py_SIZE(a));
- m = bitcount_lookup[ucbuff[k]];
+ while (i + 64 < nbits) {
+ k = i / 64;
+ assert(8 * k + 8 <= Py_SIZE(a));
+ m = popcount64(wbuff[k]);
if (!vi)
- m = 8 - m;
+ m = 64 - m;
if (j + m >= n)
break;
j += m;
- i += 8;
+ i += 64;
}
while (j < n && i < nbits) {
@@ -162,7 +162,7 @@
PyErr_SetString(PyExc_ValueError, "n larger than bitarray size");
return NULL;
}
- i = count_to_n(a, n, vi); /* do actual work here */
+ i = count_n_core(a, n, vi); /* do actual work here */
if (i < 0) {
PyErr_SetString(PyExc_ValueError, "n exceeds total count");
@@ -193,11 +193,11 @@
return -1;
/* the logic here is the same as in find_bit() in _bitarray.c */
-#ifdef PY_UINT64_T
if (n > 64) {
- Py_ssize_t word_a = (a + 63) / 64;
- Py_ssize_t word_b = b / 64;
- PY_UINT64_T *wbuff = (PY_UINT64_T *) self->ob_item, w = vi ? 0 : ~0;
+ const Py_ssize_t word_a = (a + 63) / 64;
+ const Py_ssize_t word_b = b / 64;
+ const uint64_t *wbuff = (uint64_t *) self->ob_item;
+ const uint64_t w = vi ? 0 : ~0;
if ((res = find_last(self, vi, 64 * word_b, b)) >= 0)
return res;
@@ -208,11 +208,12 @@
}
return find_last(self, vi, a, 64 * word_a);
}
-#endif
+
if (n > 8) {
- Py_ssize_t byte_a = BYTES(a);
- Py_ssize_t byte_b = b / 8;
- char *buff = self->ob_item, c = vi ? 0 : ~0;
+ const Py_ssize_t byte_a = BYTES(a);
+ const Py_ssize_t byte_b = b / 8;
+ const char *buff = self->ob_item;
+ const char c = vi ? 0 : ~0;
if ((res = find_last(self, vi, 8 * byte_b, b)) >= 0)
return res;
@@ -224,7 +225,7 @@
}
return find_last(self, vi, a, 8 * byte_a);
}
- assert(n <= 8);
+
for (i = b - 1; i >= a; i--) {
if (getbit(self, i) == vi)
return i;
@@ -304,10 +305,11 @@
static PyObject *
binary_function(PyObject *args, const char *format, const char oper)
{
- Py_ssize_t cnt = 0, s, i;
+ Py_ssize_t cnt = 0, cwords, cbytes, i;
bitarrayobject *a, *b;
unsigned char *buff_a, *buff_b;
- int r;
+ uint64_t *wbuff_a, *wbuff_b;
+ int rbits;
if (!PyArg_ParseTuple(args, format,
bitarray_type_obj, (PyObject *) &a,
@@ -318,45 +320,62 @@
buff_a = (unsigned char *) a->ob_item;
buff_b = (unsigned char *) b->ob_item;
- s = a->nbits / 8; /* number of whole bytes in buffer */
- r = a->nbits % 8; /* remaining bits */
+ wbuff_a = (uint64_t *) buff_a;
+ wbuff_b = (uint64_t *) buff_b;
+ cwords = a->nbits / 64; /* number of complete 64-bit words */
+ cbytes = a->nbits / 8; /* number of complete bytes in buffer */
+ rbits = a->nbits % 8; /* remaining bits */
switch (oper) {
#define UZ(x) ((unsigned char) zeroed_last_byte(x))
case '&': /* count and */
- for (i = 0; i < s; i++)
+ for (i = 0; i < cwords; i++)
+ cnt += popcount64(wbuff_a[i] & wbuff_b[i]);
+ for (i = 8 * cwords; i < cbytes; i++)
cnt += bitcount_lookup[buff_a[i] & buff_b[i]];
- if (r)
+ if (rbits)
cnt += bitcount_lookup[UZ(a) & UZ(b)];
break;
case '|': /* count or */
- for (i = 0; i < s; i++)
+ for (i = 0; i < cwords; i++)
+ cnt += popcount64(wbuff_a[i] | wbuff_b[i]);
+ for (i = 8 * cwords; i < cbytes; i++)
cnt += bitcount_lookup[buff_a[i] | buff_b[i]];
- if (r)
+ if (rbits)
cnt += bitcount_lookup[UZ(a) | UZ(b)];
break;
case '^': /* count xor */
- for (i = 0; i < s; i++)
+ for (i = 0; i < cwords; i++)
+ cnt += popcount64(wbuff_a[i] ^ wbuff_b[i]);
+ for (i = 8 * cwords; i < cbytes; i++)
cnt += bitcount_lookup[buff_a[i] ^ buff_b[i]];
- if (r)
+ if (rbits)
cnt += bitcount_lookup[UZ(a) ^ UZ(b)];
break;
case 'a': /* any and */
- for (i = 0; i < s; i++) {
+ for (i = 0; i < cwords; i++) {
+ if (wbuff_a[i] & wbuff_b[i])
+ Py_RETURN_TRUE;
+ }
+ for (i = 8 * cwords; i < cbytes; i++) {
if (buff_a[i] & buff_b[i])
Py_RETURN_TRUE;
}
- return PyBool_FromLong(r && (UZ(a) & UZ(b)));
+ return PyBool_FromLong(rbits && (UZ(a) & UZ(b)));
case 's': /* is subset */
- for (i = 0; i < s; i++) {
+ for (i = 0; i < cwords; i++) {
+ if ((wbuff_a[i] & wbuff_b[i]) != wbuff_a[i])
+ Py_RETURN_FALSE;
+ }
+ for (i = 8 * cwords; i < cbytes; i++) {
if ((buff_a[i] & buff_b[i]) != buff_a[i])
Py_RETURN_FALSE;
}
- return PyBool_FromLong(r == 0 || (UZ(a) & UZ(b)) == UZ(a));
+ return PyBool_FromLong(rbits == 0 || (UZ(a) & UZ(b)) == UZ(a));
default:
Py_UNREACHABLE();
@@ -412,9 +431,10 @@
static PyObject *
correspond_all(PyObject *module, PyObject *args)
{
- Py_ssize_t nff = 0, nft = 0, ntf = 0, ntt = 0, i, s;
+ Py_ssize_t nff = 0, nft = 0, ntf = 0, ntt = 0, cwords, cbytes, i;
bitarrayobject *a, *b;
unsigned char u, v, not_u, not_v;
+ uint64_t u64, v64, not_u64, not_v64;
if (!PyArg_ParseTuple(args, "O!O!:_correspond_all",
bitarray_type_obj, (PyObject *) &a,
@@ -422,9 +442,20 @@
return NULL;
if (same_size_endian(a, b) < 0)
return NULL;
- s = a->nbits / 8; /* number of whole bytes in buffer */
+ cwords = a->nbits / 64; /* number of complete 64-bit words */
+ cbytes = a->nbits / 8; /* number of complete bytes in buffer */
- for (i = 0; i < s; i++) {
+ for (i = 0; i < cwords; i++) {
+ u64 = ((uint64_t *) a->ob_item)[i];
+ v64 = ((uint64_t *) b->ob_item)[i];
+ not_u64 = ~u64;
+ not_v64 = ~v64;
+ nff += popcount64(not_u64 & not_v64);
+ nft += popcount64(not_u64 & v64);
+ ntf += popcount64(u64 & not_v64);
+ ntt += popcount64(u64 & v64);
+ }
+ for (i = 8 * cwords; i < cbytes; i++) {
u = a->ob_item[i];
v = b->ob_item[i];
not_u = ~u;
@@ -436,8 +467,8 @@
}
if (a->nbits % 8) {
unsigned char mask = ones_table[IS_BE(a)][a->nbits % 8];
- u = a->ob_item[s];
- v = b->ob_item[s];
+ u = a->ob_item[cbytes];
+ v = b->ob_item[cbytes];
not_u = ~u;
not_v = ~v;
nff += bitcount_lookup[not_u & not_v & mask];
@@ -962,7 +993,7 @@
return -1;
}
-#ifdef IS_PY3K
+#if IS_PY3K
if (PyLong_Check(item))
c = (unsigned char) PyLong_AsLong(item);
#else
@@ -1030,14 +1061,22 @@
return n;
}
-/* starting from byte i count the remaining population in bitarray buffer */
+/* starting from word `i` count the remaining population in bitarray buffer */
static Py_ssize_t
-count_from(bitarrayobject *a, Py_ssize_t i)
+count_from_word(bitarrayobject *a, Py_ssize_t i)
{
+ const Py_ssize_t cwords = a->nbits / 64;
+ const Py_ssize_t cbytes = a->nbits / 8;
Py_ssize_t cnt = 0;
- while (i < a->nbits / 8)
- cnt += bitcount_lookup[(unsigned char) a->ob_item[i++]];
+ if (64 * i >= a->nbits)
+ return 0;
+
+ while (i < cwords)
+ cnt += popcount64(((uint64_t *) a->ob_item)[i++]);
+
+ for (i = 8 * cwords; i < cbytes; i++)
+ cnt += bitcount_lookup[(unsigned char) a->ob_item[i]];
if (a->nbits % 8)
cnt += bitcount_lookup[(unsigned char) zeroed_last_byte(a)];
@@ -1056,10 +1095,12 @@
#define BSI(n) (((Py_ssize_t) 1) << (8 * (n) - 3))
/* segment size in bytes - Although of little practical value, the code
- below will also work when changing SEGSIZE to 1, 2, 4, 8 or 16, as long
- as a multiple of SEGSIZE is 32. The size 32 is rooted in the fact that
- a bitarray of 32 bytes (256 bits) can be indexed with one index byte
- (BSI(1) = 32). Our entire 'sc' format is constructed around this. */
+ below will also work for SEGSIZE values of: 8, 16 and 32
+ BSI(1) = 32 must be divisible by SEGSIZE.
+ SEGSIZE must also be a multiple of the word size (sizeof(uint64_t) = 8).
+ The size 32 is rooted in the fact that a bitarray of 32 bytes (256 bits)
+ can be indexed with one index byte (BSI(1) = 32). Our entire 'sc' format
+ is constructed around this. */
#define SEGSIZE 32
/* number of 256 bit segments given nbits */
@@ -1127,8 +1168,8 @@
res[j] = cnt;
assert(buff - a->ob_item + SEGSIZE <= Py_SIZE(a));
if (memcmp(buff, zeros, SEGSIZE)) { /* segment has not only zeros */
- for (i = 0; i < SEGSIZE; i++)
- cnt += bitcount_lookup[(unsigned char) buff[i]];
+ for (i = 0; i < SEGSIZE / 8; i++)
+ cnt += popcount64(((uint64_t *) buff)[i]);
}
}
assert(buff - a->ob_item == SEGSIZE * c_seg);
@@ -1138,12 +1179,44 @@
assert(Py_SIZE(a) <= SEGSIZE * n_seg);
assert(a->nbits && a->nbits < 8 * SEGSIZE * n_seg);
- cnt += count_from(a, SEGSIZE * c_seg);
+ cnt += count_from_word(a, (SEGSIZE / 8) * c_seg);
res[n_seg] = cnt;
}
return res;
}
+/* expose sc_calc_rts() to Python during debug mode for testing */
+#ifndef NDEBUG
+static PyObject *
+sc_rts(PyObject *module, PyObject *obj)
+{
+ PyObject *list;
+ bitarrayobject *a;
+ Py_ssize_t *rts, i;
+
+ if (ensure_bitarray(obj) < 0)
+ return NULL;
+
+ a = (bitarrayobject *) obj;
+ if ((rts = sc_calc_rts(a)) == NULL)
+ return NULL;
+
+ if ((list = PyList_New(NSEG(a->nbits) + 1)) == NULL)
+ return NULL;
+
+ for (i = 0; i <= NSEG(a->nbits); i++) {
+ PyObject *item = PyLong_FromSsize_t(rts[i]);
+ if (item == NULL) {
+ Py_DECREF(list);
+ return NULL;
+ }
+ PyList_SET_ITEM(list, i, item);
+ }
+ PyMem_Free(rts);
+ return list;
+}
+#endif /* NDEBUG */
+
/* Equivalent to the Python expression:
a.count(1, 8 * offset, 8 * offset + (1 << (8 * n)))
@@ -1263,9 +1336,6 @@
Py_UNREACHABLE();
}
-#undef SEGSIZE
-#undef NSEG
-
/* Write one sparse block (from `offset`, and up to `k` one bits).
Return number of bytes written to buffer `str` (encoded block size). */
static Py_ssize_t
@@ -1904,7 +1974,7 @@
}
static PyTypeObject CHDI_Type = {
-#ifdef IS_PY3K
+#if IS_PY3K
PyVarObject_HEAD_INIT(NULL, 0)
#else
PyObject_HEAD_INIT(NULL)
@@ -1973,19 +2043,23 @@
METH_VARARGS, vl_decode_doc},
{"canonical_decode",
(PyCFunction) chdi_new, METH_VARARGS, chdi_doc},
+#ifndef NDEBUG
+ /* functionality exposed in debug mode for testing */
+ {"_sc_rts", (PyCFunction) sc_rts, METH_O, 0},
+#endif
{NULL, NULL} /* sentinel */
};
/******************************* Install Module ***************************/
-#ifdef IS_PY3K
+#if IS_PY3K
static PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT, "_util", 0, -1, module_functions,
};
#endif
PyMODINIT_FUNC
-#ifdef IS_PY3K
+#if IS_PY3K
PyInit__util(void)
#else
init_util(void)
@@ -2000,7 +2074,7 @@
if (bitarray_type_obj == NULL)
goto error;
-#ifdef IS_PY3K
+#if IS_PY3K
m = PyModule_Create(&moduledef);
#else
m = Py_InitModule3("_util", module_functions, 0);
@@ -2012,7 +2086,11 @@
goto error;
Py_SET_TYPE(&CHDI_Type, &PyType_Type);
-#ifdef IS_PY3K
+#ifndef NDEBUG
+ PyModule_AddObject(m, "_SEGSIZE", PyLong_FromSsize_t(SEGSIZE));
+#endif
+
+#if IS_PY3K
return m;
error:
return NULL;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/bitarray-2.7.1/bitarray/bitarray.h
new/bitarray-2.7.2/bitarray/bitarray.h
--- old/bitarray-2.7.1/bitarray/bitarray.h 2023-02-10 16:30:12.000000000
+0100
+++ new/bitarray-2.7.2/bitarray/bitarray.h 2023-02-13 00:51:35.000000000
+0100
@@ -4,7 +4,7 @@
Author: Ilan Schnell
*/
-#define BITARRAY_VERSION "2.7.1"
+#define BITARRAY_VERSION "2.7.2"
#ifdef STDC_HEADERS
#include <stddef.h>
@@ -29,10 +29,17 @@
#define Py_UNREACHABLE() abort()
#endif
+/* PY_LITTLE_ENDIAN and PY_BIG_ENDIAN (machine byteorder) are available in
+ Python 3.5.10. Not sure when they were introduced */
+#ifndef PY_LITTLE_ENDIAN
+#define PY_LITTLE_ENDIAN (*((uint64_t *) "\xff\0\0\0\0\0\0\0") == 0xff)
+#endif
+
#if PY_MAJOR_VERSION >= 3
-#define IS_PY3K
+#define IS_PY3K 1
#define BYTES_SIZE_FMT "y#"
#else
+#define IS_PY3K 0
/* the Py_MIN and Py_MAX macros were introduced in Python 3.3 */
#define Py_MIN(x, y) (((x) > (y)) ? (y) : (x))
#define Py_MAX(x, y) (((x) > (y)) ? (x) : (y))
@@ -159,6 +166,26 @@
#undef B6
};
+/* Population count: count the number of 1's in 'x'. */
+static inline int
+popcount64(uint64_t x)
+{
+#if (defined(__clang__) || defined(__GNUC__))
+ return __builtin_popcountll(x);
+#else
+ /* https://en.wikipedia.org/wiki/Hamming_weight popcount64c */
+ const uint64_t m1 = 0x5555555555555555;
+ const uint64_t m2 = 0x3333333333333333;
+ const uint64_t m4 = 0x0f0f0f0f0f0f0f0f;
+ const uint64_t h01 = 0x0101010101010101;
+
+ x -= (x >> 1) & m1;
+ x = (x & m2) + ((x >> 2) & m2);
+ x = (x + (x >> 4)) & m4;
+ return (x * h01) >> 56;
+#endif /* __GNUC__ */
+}
+
/* adjust index a manner consistent with the handling of normal slices */
static inline void
adjust_index(Py_ssize_t length, Py_ssize_t *i, Py_ssize_t step)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/bitarray-2.7.1/bitarray/test_bitarray.py
new/bitarray-2.7.2/bitarray/test_bitarray.py
--- old/bitarray-2.7.1/bitarray/test_bitarray.py 2023-02-10
16:30:12.000000000 +0100
+++ new/bitarray-2.7.2/bitarray/test_bitarray.py 2023-02-13
00:51:35.000000000 +0100
@@ -2841,12 +2841,29 @@
self.assertEqual(a.count(v), ref)
self.assertEqual(a.count(v, n, -n - 1, -1), ref)
+ def test_sparse(self):
+ N = 65536
+ a = zeros(N)
+ indices = set(randint(0, N - 1) for _ in range(256))
+ for i in indices:
+ a[i] = 1
+ self.assertEqual(a.count(1), len(indices))
+ self.assertEqual(a.count(0), N - len(indices))
+
+ for _ in range(100):
+ i = randint(0, N - 1)
+ j = randint(i, N - 1)
+ cnt = sum(1 for k in indices if i <= k < j)
+ self.assertEqual(a.count(1, i, j), cnt)
+ self.assertEqual(a.count(0, i, j), j - i - cnt)
+
def test_zeros(self):
- N = 37
+ N = 300
a = zeros(N)
- for i in range(N):
- for j in range(i, N):
- self.assertEqual(a.count(0, i, j), j - i)
+ for _ in range(10):
+ i = randint(0, N - 1)
+ j = randint(i, N - 1)
+ self.assertEqual(a.count(0, i, j), j - i)
for step in range(-N - 3, N + 3):
if step == 0:
@@ -4629,8 +4646,8 @@
print('pointer size: %d bit' % (8 * SYSINFO[0]))
print('sizeof(size_t): %d' % SYSINFO[1])
print('sizeof(bitarrayobject): %d' % SYSINFO[2])
- print('PY_UINT64_T defined: %s' % SYSINFO[5])
- print('USE_WORD_SHIFT: %s' % SYSINFO[7])
+ print('__clang__ or __GNUC__ defined: %s' % SYSINFO[5])
+ print('PY_LITTLE_ENDIAN (use word shift): %s' % SYSINFO[7])
print('DEBUG: %s' % DEBUG)
suite = unittest.TestSuite()
for cls in tests:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/bitarray-2.7.1/bitarray/test_util.py
new/bitarray-2.7.2/bitarray/test_util.py
--- old/bitarray-2.7.1/bitarray/test_util.py 2023-02-10 16:30:12.000000000
+0100
+++ new/bitarray-2.7.2/bitarray/test_util.py 2023-02-13 00:51:35.000000000
+0100
@@ -18,7 +18,7 @@
from bitarray import (bitarray, frozenbitarray, decodetree, bits2bytes,
get_default_endian, _set_default_endian)
-from bitarray.test_bitarray import Util, skipIf
+from bitarray.test_bitarray import Util, skipIf, DEBUG
from bitarray.util import (
zeros, urandom, pprint, make_endian, rindex, strip, count_n,
@@ -30,6 +30,11 @@
huffman_code, canonical_huffman, canonical_decode,
)
+if DEBUG:
+ from bitarray._util import _sc_rts, _SEGSIZE # type: ignore
+else:
+ _SEGSIZE = -1
+
if sys.version_info[0] == 3:
from io import StringIO
else:
@@ -516,6 +521,14 @@
self.assertEqual(count_n(a, 1), i + 1)
self.assertRaises(ValueError, count_n, a, 2)
+ def test_last(self):
+ for n in range(1, 1000):
+ a = zeros(n)
+ a[-1] = 1
+ self.assertEqual(a.count(), 1)
+ self.assertEqual(count_n(a, 1), n)
+ self.assertRaises(ValueError, count_n, a, 2)
+
def test_large(self):
for _ in range(100):
N = randint(100000, 250000)
@@ -1398,6 +1411,35 @@
a &= urandom(n, endian)
self.round_trip(a)
+ @skipIf(not DEBUG)
+ def test_rts_empty(self):
+ a = bitarray()
+ self.assertEqual(_sc_rts(a), [0])
+
+ @skipIf(_SEGSIZE != 32)
+ def test_rts_example(self):
+ # see example before sc_calc_rts() in _util.c
+ a = zeros(987)
+ a[:5] = a[512:515] = a[768:772] = 1
+ self.assertEqual(a.count(), 12)
+ rts = _sc_rts(a)
+ self.assertEqual(len(rts), 5)
+ self.assertEqual(rts, [0, 5, 5, 8, 12])
+
+ @skipIf(not DEBUG)
+ def test_rts_random(self):
+ segbits = 8 * _SEGSIZE
+ for n in range(2000):
+ a = urandom(n)
+ rts = _sc_rts(a)
+ self.assertEqual(len(rts), (n + segbits - 1) // segbits + 1)
+ self.assertEqual(rts[0], 0)
+ self.assertEqual(rts[-1], a.count())
+
+ for i in range(len(rts) - 1):
+ seg_pop = a.count(1, segbits * i, segbits * (i + 1))
+ self.assertEqual(rts[i + 1] - rts[i], seg_pop)
+
tests.append(SC_Tests)
# ---------------------------------------------------------------------------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/bitarray-2.7.1/doc/changelog.rst
new/bitarray-2.7.2/doc/changelog.rst
--- old/bitarray-2.7.1/doc/changelog.rst 2023-02-10 16:30:12.000000000
+0100
+++ new/bitarray-2.7.2/doc/changelog.rst 2023-02-13 00:51:35.000000000
+0100
@@ -1,6 +1,15 @@
Change log
==========
+**2.7.2** (2023-02-12):
+
+* speedup all count functionality by using ``__builtin_popcountll`` when
+ available, see `#187 <https://github.com/ilanschnell/bitarray/issues/187>`__
+* add ``popcount64()`` to ``bitarray.h`` - we assume now that ``uint64_t`` is
+ always available
+* improve testing
+
+
**2.7.1** (2023-02-10):
* optimize ``util.sc_encode()``
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/bitarray-2.7.1/doc/reference.rst
new/bitarray-2.7.2/doc/reference.rst
--- old/bitarray-2.7.1/doc/reference.rst 2023-02-10 16:30:12.000000000
+0100
+++ new/bitarray-2.7.2/doc/reference.rst 2023-02-13 00:51:35.000000000
+0100
@@ -1,7 +1,7 @@
Reference
=========
-bitarray version: 2.7.1 -- `change log
<https://github.com/ilanschnell/bitarray/blob/master/doc/changelog.rst>`__
+bitarray version: 2.7.2 -- `change log
<https://github.com/ilanschnell/bitarray/blob/master/doc/changelog.rst>`__
In the following, ``item`` and ``value`` are usually a single bit -
an integer 0 or 1.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/bitarray-2.7.1/doc/sparse_compression.rst
new/bitarray-2.7.2/doc/sparse_compression.rst
--- old/bitarray-2.7.1/doc/sparse_compression.rst 2023-02-10
16:30:12.000000000 +0100
+++ new/bitarray-2.7.2/doc/sparse_compression.rst 2023-02-13
00:51:35.000000000 +0100
@@ -47,10 +47,10 @@
compress (ms) decompress (ms) ratio
----------------------------------------------------------
- serialize 4.562 1.188 1.0000
- sc 6.069 2.680 0.0158
- gzip 920.343 16.161 0.0169
- bz2 59.580 33.435 0.0117
+ serialize 3.876 1.002 1.0000
+ sc 5.502 2.703 0.0158
+ gzip 918.937 10.057 0.0169
+ bz2 59.500 32.611 0.0117
Statistics