7

我需要快速处理 XOR 字节数组,在 Python 的一个变体中

for i in range(len(str1)): str1[i]=str1[i] ^ 55

工作很慢
我用 C 写了这个模块。我对 C 语言非常了解,在我没有写之前。
在一个变体中

PyArg_ParseTuple (args, "s", &str))

一切都按预期工作,但我需要使用而不是 ss* 因为元素可以包含嵌入的 null,但是如果我在调用 python 崩溃时将 s 更改为 s*

PyArg_ParseTuple (args, "s*", &str)) // crash

也许像我这样的初学者想使用我的示例作为开始编写他自己的东西,因此请在 Windows 上将本示例中使用的所有信息带上。在http://docs.python.org/dev/c-api/arg.html
页面上解析参数和构建值

test_xor.c

#include <Python.h>

static PyObject* fast_xor(PyObject* self, PyObject* args)
{
    const char* str ;
    int i;

    if (!PyArg_ParseTuple(args, "s", &str))
        return NULL;

    for(i=0;i<sizeof(str);i++) {str[i]^=55;};
    return Py_BuildValue("s", str);

}

static PyMethodDef fastxorMethods[] =
{
     {"fast_xor", fast_xor, METH_VARARGS, "fast_xor desc"},
     {NULL, NULL, 0, NULL}
};

PyMODINIT_FUNC

initfastxor(void)
{
     (void) Py_InitModule("fastxor", fastxorMethods);
}

test_xor.py

import fastxor
a=fastxor.fast_xor("World") # it works with s instead s*
print a
a=fastxor.fast_xor("Wo\0rld") # It does not work with s instead s*

编译.bat

rem use http://bellard.org/tcc/
tiny_impdef.exe C:\Python26\python26.dll
tcc -shared test_xor.c python26.def -IC:\Python26\include -LC:\Python26\libs -ofastxor.pyd
test_xor.py 
4

5 回答 5

9

您无需构建扩展模块即可快速完成此操作,您可以使用 NumPy。但是对于您的问题,您需要一些像这样的 c 代码:

#include <Python.h>
#include <stdlib.h> 

static PyObject * fast_xor(PyObject* self, PyObject* args)
{
    const char* str;
    char * buf;
    Py_ssize_t count;
    PyObject * result;
    int i;

    if (!PyArg_ParseTuple(args, "s#", &str, &count))
    {
        return NULL;
    }

    buf = (char *)malloc(count);

    for(i=0;i<count;i++)
    {
        buf[i]=str[i] ^ 55;
    }

    result = Py_BuildValue("s#", buf, count);
    free(buf);
    return result;
}

您不能更改字符串对象的内容,因为 Python 中的字符串是不可变的。您可以使用“s#”来获取char *指针和缓冲区长度。

如果你可以使用 NumPy:

In [1]: import fastxor

In [2]: a = "abcdsafasf12q423\0sdfasdf"

In [3]: fastxor.fast_xor(a)
Out[3]: 'VUTSDVQVDQ\x06\x05F\x03\x05\x047DSQVDSQ'


In [5]: import numpy as np

In [6]: (np.frombuffer(a, np.int8)^55).tostring()
Out[6]: 'VUTSDVQVDQ\x06\x05F\x03\x05\x047DSQVDSQ'

In [7]: a = a*10000

In [8]: %timeit fastxor.fast_xor(a)
1000 loops, best of 3: 877 us per loop

In [15]: %timeit (np.frombuffer(a, np.int8)^55).tostring()
1000 loops, best of 3: 1.15 ms per loop
于 2013-03-17T11:13:29.697 回答
5

这可以使用 numpy 快速完成。您不太可能在 C 中手动滚动您自己的 xor 例程得到更快的速度:

In [1]: import numpy

In [2]: data = numpy.uint8(numpy.random.randint(0, 256, 10000))

In [3]: timeit xor_data = numpy.bitwise_xor(data, 55)
100000 loops, best of 3: 17.4 us per loop

如果您使用的是大型数据集(例如 1 亿个点),则它与您为代码引用的时间相当:

In [12]: data = numpy.uint8(numpy.random.randint(0, 256, 100000000))

In [13]: timeit xor_data = numpy.bitwise_xor(data, 55)
1 loops, best of 3: 198 ms per loop
于 2013-03-17T11:12:37.897 回答
5

另一种方法是使用PyObject_GetBuffer. 下面的模块定义fast_xor了支持缓冲区协议的任何对象,以及fast_xor_inplace具有可写缓冲区的对象,例如bytearray. 这个版本返回None。我还添加了unsigned char默认值为 55 的第二个参数。

例子:

>>> s = 'abc'
>>> b = bytearray(s)
>>> fast_xor(s), fast_xor(s, 0x20)
('VUT', 'ABC')
>>> fast_xor_inplace(b, 0x20)
>>> b
bytearray(b'ABC')

>>> fast_xor_inplace(s)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
BufferError: Object is not writable.

>>> fast_xor(b, 256)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OverflowError: unsigned byte integer is greater than maximum

来源:

#include <Python.h>

static PyObject *fast_xor_inplace(PyObject *self, PyObject *args)
{
    PyObject *arg1;
    unsigned char arg2 = 55;
    Py_buffer buffer;
    char *buf;
    int i;

    if (!PyArg_ParseTuple(args, "O|b:fast_xor_inplace", &arg1, &arg2))
        return NULL;

    if (PyObject_GetBuffer(arg1, &buffer, PyBUF_WRITABLE) < 0)
        return NULL;

    buf = buffer.buf;
    for(i=0; i < buffer.len; i++)
        buf[i] ^= arg2;

    PyBuffer_Release(&buffer);
    Py_INCREF(Py_None);
    return Py_None;
}

static PyObject *fast_xor(PyObject *self, PyObject *args)
{
    PyObject *arg1;
    unsigned char arg2 = 55;
    PyObject *result;
    Py_buffer buffer;
    char *buf, *str;
    int i;

    if (!PyArg_ParseTuple(args, "O|b:fast_xor", &arg1, &arg2))
        return NULL;

    if (PyObject_GetBuffer(arg1, &buffer, PyBUF_SIMPLE) < 0)
        return NULL;

    result = PyString_FromStringAndSize(NULL, buffer.len);
    if (result == NULL)
        return NULL;

    buf = buffer.buf;
    str = PyString_AS_STRING(result);
    for(i=0; i < buffer.len; i++)
        str[i] = buf[i] ^ arg2;

    PyBuffer_Release(&buffer);
    return result;
}

static PyMethodDef fastxorMethods[] =
{
     {"fast_xor", fast_xor, METH_VARARGS, "fast xor"},
     {"fast_xor_inplace", fast_xor_inplace, METH_VARARGS, "fast inplace xor"},
     {NULL, NULL, 0, NULL}
};

PyMODINIT_FUNC
initfastxor(void)
{
    Py_InitModule3("fastxor", fastxorMethods, "fast xor functions");
}
于 2013-03-17T13:47:44.473 回答
0

尝试

  char pybuffer[1024];
  ...
  PyArg_ParseTuple (args, "s#", pybuffer, sizeof(pybuffer));

不确定缓冲区大小要求是什么 - 我假设 1024 就足够了。

于 2013-03-17T10:32:24.670 回答
0

不要使用for-loop。改为使用列表推导,它们要快得多:

In [1]: import random

In [2]: t = bytearray([random.randint(0,255) for i in xrange(10000)])

In [3]: u = bytearray([b^55 for b in t])

这非常快:

In [11]: %timeit u = bytearray([b^55 for b in t])
1000 loops, best of 3: 1.36 ms per loop

这并不慢。对于 1 MB(10**6 字节),大约需要 130 毫秒。

当然,使用 numpy 作为 Henry Gomersall 的回答是更好的解决方案。

于 2013-03-17T10:51:05.390 回答