songxu 发布的文章

python多进程和多线程的效率

  • python多进程和多线程通过multiprocessing和threading两个模块实现,由于python是c写的,所以和C语言的多进程和多线程基本相同,唯一不同的是python多线程并没有利用多核cpu的并行处理能力,无论开启多少个线程,始终只占用一个核心的cpu利用率,原因在于GIL(全局解释器锁)限制了同一时刻只能有一个线程在运行,GIL有其历史原因,至今依然存在主要在于python本身运行效率远低于c,若需要多线程处理,直接调用c的接口即可,也可以采用python的多进程来处理。下面具体分析一下python多进程和多线程的效率孰优孰劣
  • 单核cpu
    多线程效率高于多进程
  • 多核cpu
    对于cpu密集型任务,多进程的效率高于多线程,对于IO密集型任务,当任务数超出核心一定数量后,多线程效率高于多进程

python pip安装

1. 下载pip源码

https://pypi.org/project/pip/#files

2. 安装setuptools

可参考Python MySQLdb 模块安装

3. 解压运行setup.py

python setup.py install

从c语言层分析python的实现原理

python作为一门开源的、跨平台的脚本语言,其底层全部用c来实现,下面从c语言的角度来分析python的执行过程

1. python解释器

编译python源码后,会在根目录生成python程序,此程序就是python脚本的解释器,python解释器主要由语法解析、编译和执行三个模块组成

2. python语法解析模块

python语法解析模块负责把python字符串或脚本文件根据其语法解析成c语言的数据结构

3. python编译模块

python编译模块负责把解析后的数据结构转换成虚拟机可执行的字节码对象PyCodeObject

4. python执行模块

python执行模块负责执行python字节码,通常也叫做python虚拟机

当执行python file时,python首先会判断file是否为字节码文件,通常字节码文件的扩展名为pyc或pyo,而普通python脚本为py,判断的逻辑如下:

if (maybe_pyc_file(fp, filename, ext, closeit)) {
        /* Try to run a pyc file. First, re-open in binary */
        if (closeit)
            fclose(fp);
        if ((fp = fopen(filename, "rb")) == NULL) {
            fprintf(stderr, "python: Can't reopen .pyc file\n");
            goto done;
        }
        /* Turn on optimization if a .pyo file is given */
        if (strcmp(ext, ".pyo") == 0)
            Py_OptimizeFlag = 1;
        v = run_pyc_file(fp, filename, d, d, flags);
    } else {
        v = PyRun_FileExFlags(fp, filename, Py_file_input, d, d,
                              closeit, flags);
    }

若是字节码文件,则直接调用执行模块来执行,执行函数如下:

PyAPI_FUNC(PyObject *) PyEval_EvalCodeEx(PyCodeObject *co,
                    PyObject *globals,
                    PyObject *locals,
                    PyObject **args, int argc,
                    PyObject **kwds, int kwdc,
                    PyObject **defs, int defc,
                    PyObject *closure);

若是脚本文件,则先调用解析模块再调用编译模块,执行函数如下:

PyAPI_FUNC(struct _mod *) PyParser_ASTFromString(const char *, const char *,
                                                 int, PyCompilerFlags *flags,
                                                 PyArena *);
PyAPI_FUNC(struct _mod *) PyParser_ASTFromFile(FILE *, const char *, int,
                                               char *, char *,
                                               PyCompilerFlags *, int *,
                                               PyArena *);
PyAPI_FUNC(PyCodeObject *) PyAST_Compile(struct _mod *, const char *,
                        PyCompilerFlags *, PyArena *);

通常很多人会认为,编译后的python脚本会执行很快,但事实并非如此,编译后的字节码文件只是省去了语法解析和编译两部分,所以执行效率和普通python脚本一样,只是加载速度相对较快。目前由于个人能力有限,暂时大概分析了python的执行过程,至于语法解析、编译和执行的细节,逻辑比较复杂,待内功修炼到一定层次,再一一揭开python的神秘面纱

自动生成GNU项目的configure和Makefile

通常开源的GNU项目编译和安装只需以下两步:

./configure
make && make install

而我们发布自己的个人项目通常手工写Makefile文件,总感觉跟GNU的Makefile文件相比很low,而且很多时候依赖系统的配置,因此就有以下工具帮助我们自动生成标准的configure文件和Makefile文件,下图就是工具的流程图:
2009052517385173.png

1. 自动扫描工具autoscan

在工程的根目录下运行autoscan命令,autoscan会自动扫描工程中的源码,生成configure.scan文件,然后把configure.scan文件重命名为configure.ac或configure.in,并做相应的修改,详细可参考文末的demo

2. m4文件生成工具aclocal

aclocal是一个perl脚本,可根据configure.ac生成aclocal.m4

3. configure文件生成工具autoconf

autoconf可根据configure.ac和aclocal.m4文件生成configure文件,configure是一个shell脚本,能根据不同的操作系统生成相应的Makefile

4. Makefile.in文件生成工具automake

首先编写Makefile.am文件,详细可参考文末的demo,automake会根据configure.ac和Makefile.am文件生成Makefile.in文件,运行automake通常带--add-missing选项,此选项会让automake加入一个标准的软件包所必须的一些文件

5. 生成最终Makefile文件

运行./confiugre,configure会根据Makfile.in文件生成最终的Makefile文件

由此可见,为了生成标准的Makefile文件,需要以上复杂的步骤,因此很多项目通常会有configure.ac、Makefile.am、autogen.sh三个文件,由autogen.sh根据configure.ac和Makefile.am文件自动完成以上步骤来生成Makefile文件,详情参考github地址:https://github.com/song2010040402102/autoconf_demo.git

对于autoconf系列的工具集,目前我也是仅知其然而不知其所以然,按以往的性格会把这整块吃透,只是最近才突然感觉到,时间和精力有限,应把主要的时间和精力放在内功修炼上,而不是类似工具这种外功上,工具会用即可,想搞明白只是时间问题,体现不出一个人的能力高低,所以此篇只介绍使用而不了解其细节实现

c++类函数编译链接问题

1. 缘由

由于前段时间遇到类外定义函数出现链接错误,而类内定义却链接成功,所以花了点时间从汇编角度来分析c++类函数编译链接问题

2. 对函数的理解

可能很多人对类函数和全局函数的理解不够深刻,所以先简单说明一下。从机器语言角度理解,函数本质上就是代码区的地址,所以无论全局还是类函数都仅是一个地址而已。C全局函数用函数名唯一标记这个地址,C++全局函数用函数名和参数类型名来唯一标记,而C++类函数则用类名、函数名、参数类型名来唯一标记,所以C++有面向对象、同名函数可重载等特性。事实上,类静态函数和非静态函数的调用机制本质上都是函数的调用机制,参数进栈->跳转至函数地址->局部变量进栈->函数逻辑->局部变量出栈->参数出栈->返回调用前地址,对于非静态函数,无非把类对象本身作为入栈参数传递给函数,所以类非静态函数至少有一个参数入栈,以下是类非静态函数的反汇编

图一.jpg

3. 函数在类内定义和类外定义区别

很多人甚至很多权威书籍认为类内定义的函数属于内联函数,事实上并非如此,若编译时采用默认优化选项(O=0),则不属于内联函数,只有设置优化选项(O>=1),类内定义的函数才是内联的,而类外定义的函数,除非加上inline关键字,否则始终不是内联函数,以下是类内定义函数的反汇编

图二.jpg

4. 链接器对函数的处理

若a.h中定义一个函数,b.cpp和c.cpp均包含a.h,则链接时会提示函数重复定义。同样若在a.h中定义一个类,并在类外部定义一个函数,则链接同样会提示函数重复定义,但在类内部定义一个函数,则链接不会报错。很多人认为在类内部定义函数被内联了,当然不会报错,但事实不然,若此时采用默认优化选项(O=0),同样也不会报错。学过汇编的都应该知道,代码区是分节的,通常代码区首节是.init用于进程初始化,我们写的代码基本都在.text节,通过对b.o和c.o进行反汇编发现,全局函数和类外定义的函数均在.text节,而类内定义的函数则在.text.fun节,fun为函数名。因此对于全局和类外定义的函数而言,都在.text节,链接器在合并.text内的代码时,当然会报函数重复定义,而对于类内定义的函数而言,在.text.fun节中,链接器首先把b.o中.text.fun节合并到.text,之后在合并c.o中.text.fun节时,发现已经合并过了,则会跳过,当然就不会报函数重复定义。对于模板函数就更特殊了,无论是全局模板函数还是在类内部或外部定义的模板函数,编译器均会给模板函数分配代码节.text.fun,因此模板函数不存在链接时重复定义问题。以下是类内和类外定义函数的反汇编

图三.jpg