因此,我正在优化游戏机器人,并且在纯Python中已经耗尽了优化。目前,大部分时间用于将一个游戏状态转换为用于alpha-beta搜索的下一个游戏状态。目前的想法是我可以通过在C中编写状态转换代码来加快速度。我的问题来自于尝试将python状态转换为C可以再次运行的结构。
目前,状态由字节字符串唯一表示:
import itertools
import struct
BINCODE = struct.Struct('BBBBBBBBBBBBBBb')
class State:
__slots__ = '_bstring'
TOP = 1
BOTTOM = 0
def __init__(self, seeds=4, *, top=None, bottom=None, turn=0):
top = top if top else [seeds] * 6 + [0]
bottom = bottom if bottom else [seeds] * 6 + [0]
self._bstring = BINCODE.pack(*itertools.chain(bottom, top), turn)
@property
def top():
...
这个想法是,state._bstring,它已经被打包成二进制数据,可以很好地转换成类似于这样的c结构:
struct State
{
unsigned int bottom[7];
unsigned int top[7];
int turn;
}
我的C代码可以操作,生成生成的C状态作为新的二进制数据,并直接插入新的python State
对象。
然而,我似乎无法找到有关如何解决这个问题的任何信息。我能找到的几乎所有信息都是关于从文件中打包和解包C数据。
我考虑在bytes对象上使用PyObject_GetBuffer
,但游戏逻辑非常复杂,我更喜欢将数据作为结构而不是数组来处理。此外,我希望将复制量降至最低。
我研究的另一个选项是使用在C中定义的PyCapsule
对象作为python的新状态,但是我将丢失所有State类的python特定功能。我真的宁愿将对python代码的更改保持在绝对最小值,因为之前的许多python优化都依赖于数据格式。
Cython似乎没有办法将二进制数据强制转换为C结构指针。重写State
与numba兼容会丢失关键功能,如独特的哈希等。
似乎应该有一个相当直接的方法来做到这一点,但我似乎无法找到它。任何和所有的帮助将不胜感激。
答案 0 :(得分:1)
理想情况下,你应该编写一个简单的python模块,如下所示,
#include <Python.h>
struct State {
unsigned int bottom[7];
unsigned int top[7];
int turn;
};
struct PyState {
PyObject_HEAD
struct State *internal;
};
static void PyState_free(PyObject *self);
static PyMethodDef py_mygamestate_module_methods[] = {{NULL, NULL, 0, NULL}};
static struct PyModuleDef py_mygamestate_module = {
PyModuleDef_HEAD_INIT,
/* name of module */
"mygamestate",
/* module documentation, may be NULL */
NULL,
/* size of per-interpreter state of the module, or -1
* if the module keeps state in global variables.
*/
-1,
py_mygamestate_module_methods,
NULL,
NULL,
NULL,
NULL
};
static PyObject *py_state_show(PyObject *self, PyObject *args);
static PyMethodDef py_state_methods[] = {
{"show", py_state_show, METH_NOARGS, NULL},
{NULL, NULL, 0, NULL}
};
static PyObject *py_state_new(PyTypeObject *type, PyObject *parent, PyObject *args);
#define Py_BASE_TYPE_FLAGS (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE)
static PyTypeObject py_state_type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"State", /* tp_name */
sizeof(struct PyState), /* tp_basicsize */
0, /* tp_itemsize */
PyState_free, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_as_async */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_BASE_TYPE_FLAGS, /* tp_flags */
"Docstring", /* tp_doc */
0, /* tp_travers */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_next */
py_state_methods, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
&PyBaseObject_Type, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
0, /* tp_init */
0, /* tp_alloc */
py_state_new, /* tp_new */
0, /* tp_free */
0, /* tp_is_gc */
0, /* tp_bases */
0, /* tp_mro */
0, /* tp_cache */
0, /* tp_subclasses */
0, /* tp_weaklist */
0, /* tp_del */
0, /* tp_version_tag */
0 /* tp_finalize */
};
static void
PyState_free(PyObject *self)
{
free(((struct PyState *) self)->internal);
}
static PyObject *
py_state_new(PyTypeObject *type, PyObject *parent, PyObject *args)
{
struct PyState *state;
PyObject *self;
self = PyType_GenericNew(type, parent, args);
if (self == NULL)
return NULL;
// Cast the object to the appropriate type
state = (struct PyState *) self;
// Initialize your internal structure
state->internal = malloc(sizeof(*state->internal));
if (state->internal == NULL)
return NULL;
memset(state->internal, 0, sizeof(*state->internal));
// This means no error occurred
return self;
}
static PyObject *
py_state_show(PyObject *self, PyObject *args)
{
struct State *state;
// Cast the object to the appropriate type
state = ((struct PyState *) self)->internal;
if (state == NULL)
return NULL;
fprintf(stdout, "bottom: ");
for (size_t i = 0; i < 7; ++i)
fprintf(stdout, "%d, ", state->bottom[i]);
fprintf(stdout, "top: ");
for (size_t i = 0; i < 7; ++i)
fprintf(stdout, "%d, ", state->bottom[i]);
fprintf(stdout, "turn: %d\n", state->turn);
return self;
}
PyObject *
PyInit_mygamestate(void)
{
PyObject *module;
// Prepare the base classes to add them
if (PyType_Ready(&py_state_type) < 0)
return NULL;
// Create the apache module
module = PyModule_Create(&py_mygamestate_module);
if (module == NULL)
return NULL;
// Add the base classes
PyModule_AddObject(module, "State", (PyObject *) &py_state_type);
return module;
}
请注意,模块 dll 或 so 文件的名称应与PyInit_mygamestate
下划线后面的部分匹配。
现在,如果您将 so 文件安装到 site-packages 目录,那么从python可以执行此操作
import mygamestate
state = mygamestate.State()
state.show()
这样你可以同时拥有任何类型,如python类型和c类型。
你当然可以将py_state_methods
数组增长到你想要的大小,并且在python代码中有任何有用的方法。您还可以将参数传递给构造函数和每个方法,依此类推。
还有另一个数组,即py_mygamestate_module_methods
,它将是python代码中模块可直接访问的方法。
注意:orignal代码已修改,所以现在它允许继承,你可以做这样的事情
from mygamestate import State
class CustomState(State):
def __init__(self):
pass
# add all your custom methods here
def sample_method(self):
print('Cool, it works')
state = CustomState()
state.show()
state.sample_method()
变化是,
将Py_TPFLAGS_BASETYPE
添加到tp_flags
成员。这允许类型是可子类化的,但是不再使用tp_init
函数,而且需要在tp_new
中执行初始化 - 我不知道它为什么会这样,恕我直言,这是愚蠢的 - 。
删除py_state_init
函数,改为创建py_state_new
函数,替换PyType_GenericNew
实例的tp_new
成员的PyTypeObject
函数是我们的自定义类型。
我们调用原始PyType_GenericNew()
来创建我们的类型对象,然后执行旧py_state_init()
初始化内部结构数据的操作。
通过继承这个课程,你可以拥有你想要的两件事。
答案 1 :(得分:0)
好的,所以我已经制定了我的解决方案,而且相当直接。我似乎误解了Py_buffer
的工作方式。
python bytes
类型实现缓冲区协议,因此您可以使用PyObject_GetBuffer
函数来获取对基础数据的引用。 buffer.buf
指向数据作为void
指针,可以自由地转换为您想要的任何结构。
这里有一些(简化的)代码:
#include <Python.h>
typedef struct State{
char board[14];
char turn;
} State;
static PyObject *py_after_move(PyObject *self, PyObject *args){
PyObject *buffobj;
Py_buffer view;
int move;
//get the passed PyObject
if (!PyArg_ParseTuple(args, "Oi", &buffobj, &move)) {
return NULL;
}
//get buffer info
if (PyObject_GetBuffer(buffobj, &view, PyBUF_ANY_CONTIGUOUS | PyBUF_FORMAT) == -1) {
return NULL;
}
//copy and cast as a state
State state;
memcpy(&state, view.buf, sizeof(state));
for (int i=0; i < 14; i++) {
state.board[i] += 1; /// example modifications
}
state.turn++;
//re-cast as characters and place in new bytes object
char *aschr = (char*) &state;
PyBuffer_Release(&view);
return Py_BuildValue("y#", aschr, sizeof(state));
}
/* python module boilerplate goes here */
这里我做了memcpy
缓冲区,因为我不想改变输入。
view.len == 15
并提出错误,如果没有。如果不这样做可能会导致seg-fault和安全问题view.buf
到State
的广告素材很好,因为view.buf
的类型为*void
。然而,从州到焦的演员不是。理想情况下,这将是一个无效指针。