1. 从Python GIL系列文章中我们已经对Python的GIL有了一个比较清醒的认识
2. 要提高Python程序在多核CPU情况下的性能,除了使用进程替代线程外,一个更为实用的方法就是绑定Python进程运行于指定CPU。
3. 接下来看下如何在Kivy中做到这一点
4. 修改src/jni/application/python/start.c
#define PY_SSIZE_T_CLEAN #include "Python.h" #ifndef Py_PYTHON_H #error Python headers needed to compile C extensions, please install development version of Python. #else #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <jni.h> #include "SDL.h" #include "android/log.h" #include "jniwrapperstuff.h" #define LOG(x) __android_log_write(ANDROID_LOG_INFO, "python", (x)) #define _GNU_SOURCE #include <sched.h> #include <linux/unistd.h> typedef unsigned int cpu_set_t; static PyObject *androidembed_log(PyObject *self, PyObject *args) { char *logstr = NULL; if (!PyArg_ParseTuple(args, "s", &logstr)) { return NULL; } LOG(logstr); Py_RETURN_NONE; } static int sched_setaffinity(pid_t pid, size_t len, cpu_set_t const * cpusetp) { return syscall(__NR_sched_setaffinity, pid, len, cpusetp); } static int sched_getaffinity(pid_t pid, size_t len, cpu_set_t const * cpusetp) { return syscall(__NR_sched_getaffinity, pid, len, cpusetp); } static PyObject * get_process_affinity_mask(PyObject *self, PyObject *args) { unsigned long cur_mask; unsigned int len = sizeof(cur_mask); pid_t pid; if (!PyArg_ParseTuple(args, "i:get_process_affinity_mask", &pid)) return NULL; if (sched_getaffinity(pid, len, (cpu_set_t *)&cur_mask) < 0) { PyErr_SetFromErrno(PyExc_ValueError); return NULL; } return Py_BuildValue("l", cur_mask); } static PyObject * set_process_affinity_mask(PyObject *self, PyObject *args) { unsigned long new_mask; unsigned long cur_mask; unsigned int len = sizeof(new_mask); pid_t pid; if (!PyArg_ParseTuple(args, "il:set_process_affinity_mask", &pid, &new_mask)) return NULL; if (sched_getaffinity(pid, len, (cpu_set_t *)&cur_mask) < 0) { PyErr_SetFromErrno(PyExc_ValueError); return NULL; } if (sched_setaffinity(pid, len, (cpu_set_t *)&new_mask)) { PyErr_SetFromErrno(PyExc_ValueError); return NULL; } return Py_BuildValue("l", cur_mask); } static PyMethodDef AndroidEmbedMethods[] = { {"log", androidembed_log, METH_VARARGS,"Log on android platform"}, {"get_process_affinity_mask", get_process_affinity_mask, METH_VARARGS,"get_process_affinity_mask"}, {"set_process_affinity_mask", set_process_affinity_mask, METH_VARARGS,"set_process_affinity_mask"}, {NULL, NULL, 0, NULL} }; PyMODINIT_FUNC initandroidembed(void) { (void) Py_InitModule("androidembed", AndroidEmbedMethods); } int file_exists(const char * filename) { FILE *file; if (file = fopen(filename, "r")) { fclose(file); return 1; } return 0; } int main(int argc, char **argv) { char *env_argument = NULL; int ret = 0; FILE *fd; LOG("Initialize Python for Android"); env_argument = getenv("ANDROID_ARGUMENT"); setenv("ANDROID_APP_PATH", env_argument, 1); //setenv("PYTHONVERBOSE", "2", 1); Py_SetProgramName(argv[0]); Py_Initialize(); PySys_SetArgv(argc, argv); /* ensure threads will work. */ PyEval_InitThreads(); /* our logging module for android */ initandroidembed(); /* inject our bootstrap code to redirect python stdin/stdout * replace sys.path with our path */ PyRun_SimpleString( "import sys, posix\n" \ "private = posix.environ['ANDROID_PRIVATE']\n" \ "argument = posix.environ['ANDROID_ARGUMENT']\n" \ "sys.path[:] = [ \n" \ " private + '/lib/python27.zip', \n" \ " private + '/lib/python2.7/', \n" \ " private + '/lib/python2.7/lib-dynload/', \n" \ " private + '/lib/python2.7/site-packages/', \n" \ " argument ]\n" \ "import androidembed\n" \ "class LogFile(object):\n" \ " def __init__(self):\n" \ " self.buffer = ''\n" \ " def write(self, s):\n" \ " s = self.buffer + s\n" \ " lines = s.split(\"\\n\")\n" \ " for l in lines[:-1]:\n" \ " androidembed.log(l)\n" \ " self.buffer = lines[-1]\n" \ " def flush(self):\n" \ " return\n" \ "sys.stdout = sys.stderr = LogFile()\n" \ "import site; print site.getsitepackages()\n"\ "print 'Android path', sys.path\n" \ "print 'Android kivy bootstrap done. __name__ is', __name__"); /* run it ! */ LOG("Run user program, change dir and execute main.py"); chdir(env_argument); /* search the initial main.py */ char *main_py = "main.pyo"; if ( file_exists(main_py) == 0 ) { if ( file_exists("main.py") ) main_py = "main.py"; else main_py = NULL; } if ( main_py == NULL ) { LOG("No main.pyo / main.py found."); return -1; } fd = fopen(main_py, "r"); if ( fd == NULL ) { LOG("Open the main.py(o) failed"); return -1; } /* run python ! */ ret = PyRun_SimpleFile(fd, main_py); if (PyErr_Occurred() != NULL) { ret = 1; PyErr_Print(); /* This exits with the right code if SystemExit. */ if (Py_FlushLine()) PyErr_Clear(); } /* close everything */ Py_Finalize(); fclose(fd); LOG("Python for android ended."); return ret; } JNIEXPORT void JNICALL JAVA_EXPORT_NAME(PythonService_nativeStart) ( JNIEnv* env, jobject thiz, jstring j_android_private, jstring j_android_argument, jstring j_python_home, jstring j_python_path, jstring j_arg ) { jboolean iscopy; const char *android_private = (*env)->GetStringUTFChars(env, j_android_private, &iscopy); const char *android_argument = (*env)->GetStringUTFChars(env, j_android_argument, &iscopy); const char *python_home = (*env)->GetStringUTFChars(env, j_python_home, &iscopy); const char *python_path = (*env)->GetStringUTFChars(env, j_python_path, &iscopy); const char *arg = (*env)->GetStringUTFChars(env, j_arg, &iscopy); setenv("ANDROID_PRIVATE", android_private, 1); setenv("ANDROID_ARGUMENT", android_argument, 1); setenv("PYTHONOPTIMIZE", "2", 1); setenv("PYTHONHOME", python_home, 1); setenv("PYTHONPATH", python_path, 1); setenv("PYTHON_SERVICE_ARGUMENT", arg, 1); char *argv[] = { "service" }; /* ANDROID_ARGUMENT points to service subdir, * so main() will run main.py from this dir */ main(1, argv); } #endif
相比原来的代码添加了两个python接口:
get_process_affinity_mask
set_process_affinity_mask
还有由于sched_setaffinity,sched_getaffinity这两个函数在 在ndk中没有定义,所以参考网上的资料自己实现了下。
注意unistd.h不要包含出错了,ndk中有好几个unistd.h,应该是linux下的unistd.h,要不然传统编译出错。
5. 编译,具体的可参考《Kivy的编译环境的搭建以及编译和运行》一文
5.1先设置环境变量,
export ANDROIDSDK="/path/to/android/android-sdk-linux_86" export ANDROIDNDK="/path/to/android/android-ndk-r8c" export ANDROIDNDKVER=r8c export ANDROIDAPI=14 export PATH=$PATH:/mnt/develop/android_dev/kivy/apache-ant-1.9.3/bin:$ANDROIDNDK
5.2 在python-for-android目录执行:
./distribute.sh -m 'openssl pyjnius pil kivy'
6. 使用方法:在代码初始化时加上
import androidembed;androidembed.set_process_affinity_mask(0,1)
7. 打包python程序到apk
../../build/hostpython/Python-2.7.2/hostpython build.py --package org.test.touchtracer --name touchtracer --version 1.0 --dir ../../build/kivy/kivy-stable/examples/demo/touchtracer debug
8. 但是在实际的手机上测试时,Python在多线程情况下即使不绑定CPU,也不会造成性能上的的损失,看来在arm平台上和intel平台上还真不一样,但是总之有备无患吧。
9. 参考资料
https://code.google.com/p/android/issues/detail?id=19851