用SSE2指令协助Python进行高性能计算
介绍
在本文的教程中,我要讨论一下怎样用Python调用外部库来进行高性能计算。Python调用库的计算结果,计算由汇编语言调用的SSE指令集,此速度在极限条件下(不作分支判断,只在一个循环中做加减乘除计算)与纯Python计算相比可以提升2000倍。 在进入教程之前,我们先简单介绍一下本文所讲述调用过程。 Python先调用这个库,这个库的计算由HLA提供,C语言调用HLA函数。然后计算结果通过标准Python接口传给Python。
SSE介绍
SSE指令集也叫单指令多数据流扩展。最早是由英特尔提出,是为了加强浮点运算、图像处理等多媒体应用的能力,能更好的对整个系统进行控制,提高处理性能的指令的集合。 SSE2指令集是Intel公司在SSE指令集的基础上发展起来的.相比于SSE,SSE2 使用了144个新增指令,扩展了MMX技术和SSE技术,这些指令提高了广大应用程序的运行性能.随MMX技术引进的SIMD整数指令从64位扩展到了 128 位,使SIMD整数类型操作的有效执行率成倍提高.双倍精度浮点SIMD指令允许以 SIMD格式同时执行两个浮点操作,提供双倍精度操作支持有助于加速财务、工程和科学应用.除SSE2指令之外,最初的SSE指令也得到增强,通过支持多种数据类型的算术运算,支持灵活并且动态范围更广的计算功能.SSE2指令可让软件开发员极其灵活的实施算法,并在运行诸如MPEG-2、MP3、3D图形等之类的软件时增强性能. 从SSE1到SSE4都是由英特尔所提出,而AMD除了自己的指令集3DNow!,也支持这些SSE指令集。
HLA介绍
HLA(High Level Assemble)是一种高级汇编语言。它提供了一些高级语言的特性,比如一些高级控制结构和过程调用。当然这些高级特性也是有代价的。好在它的自由度也很高,使用者也可以忽略它的这些特性,发挥低级汇编的效率特点。它的源文件还可以编译成FASM,Gas,MASM,TASM。
开始
HLA
开始用HLA写程序之前,先确保HLA已经安装。
unit hlaFuncUnit; #include( "stdlib.hhf" ) procedure hla_Vec3Add( var vector1:real128; var vector2:real128; var vector3:real128 ); @cdecl; @external( "_hla_Vec3Add" ); //声名过程hla_Vec3Add及其传递的参数,还有外部调用方式和调用名 procedure hla_Vec3Add( var vector1:real128; var vector2:real128; var vector3:real128); begin hla_Vec3Add; mov(vector1, eax); movdqa((type real128 [eax]), xmm0); // 将vector1放入xmm0寄存器 mov(vector2, eax); movdqa((type real128 [eax]), xmm1); // 将vector2放入xmm1寄存器 addps(xmm1, xmm0); // vector1 + vector2 mov(vector3, eax); movdqa(xmm0, (type real128 [eax])); // 将结果保存到vector3 end hla_Vec3Add; end hlaFuncUnit;
将上面这段程序用记事本保存成hlaFunc.hla。运行cmd.exe,在控制台上输入
> hla –c hlaFunc.hla
之后生成hlaFunc.obj。
C
{{{ 用VC新建一个win32项目。生成类型选择Dynamic Library (.dll),项目头文件和库文件路径选择Python安装路径下的头文件和库文件目录。在Link 配制页的input项中,Additional Dependencies这一项填入刚刚生成的hlaFunc.obj和HLA库文件hlalib.lib。在Link 配制页的General项中,Output File这一项填入$(OutDir)\sse.pyd。 将下面的程序保存成C源文件。}}}
#include "Python.h" typedef __declspec(align(16)) struct { float x; float y; float z; } Vec3; typedef __declspec(align(16)) struct { float f1; float f2; float f3; float f4; } __m128; extern void hla_Vec3Add( Vec3* vector1, Vec3* vector2, Vec3* vector3); static PyObject *sseError; static PyObject *sse_vec3Add(self, args) PyObject *self; PyObject *args; { Vec3 vector1 = {0.0f, 0.0f, 0.0f}; Vec3 vector2 = {0.0f, 0.0f, 0.0f}; Vec3 vector3 = {0.0f, 0.0f, 0.0f}; if (!PyArg_ParseTuple(args, "(fff)(fff)", &(vector1.x), &(vector1.y), &(vector1.z), &(vector2.x), &(vector2.y), &(vector2.z))) // 将Python传过的的参数进行解析。 return NULL; hla_Vec3Add( &vector1, &vector2, &vector3); // 调用HLA函数 return Py_BuildValue("(fff)" , vector3.x, vector3.y, vector3.z); // 返回结果 } static PyMethodDef sseMethods[] = { {"Vec3Add", sse_vec3Add, METH_VARARGS, "vec3 = vec1 + vect2"}, {NULL, NULL} /* Sentinel */ }; #ifdef MS_WIN32 __declspec(dllexport) #endif void initsse() { PyObject *m, *d; m = Py_InitModule("sse", sseMethods); d = PyModule_GetDict(m); sseError = PyErr_NewException("sse.error", NULL, NULL); PyDict_SetItemString(d, "error", sseError);
}
Build之后,生成sse.pyd。将其放到Python的安装目录下。
Python
打开Python,输入
>>> import sse >>> sse.SSEAdd((0.1, 0.2, 0.3), (0.4, 0.5, 0.6,)) ##输入两个数
这样我们就可以在汇编程序那端进行计算,然后将结果告诉Python。
需要注意的是小规模的计算还是用Python比较方便,Python在调用库函数时进行的频繁出栈入栈和解析打包参数比较耗时。如果计算只计算一次两数相加,那还不如用Python自己来算快。