原文地址:http://developer.51cto.com/art/201405/438178.htm
在Python编码中我们经常讨论的一个方面就是如何优化模拟执行的性能。尽管在考虑量化代码时NumPy、SciPy和pandas在这方面已然非常有用,但在构建事件驱动系统时我们无法有效地使用这些工具。有没有可以加速我们代码的其他办法?答案是肯定的,但需要留意!
在这篇文章中,我们看一种不同的模型-并发,我们可以将它引入我们Python程序中。这种模型在模拟中工作地特别好,它不需要共享状态。Monte Carlo模拟器可以用来做期权定价以及检验算法交易等类型的各种参数的模拟。
我们将特别考虑Threading库和Multiprocessing库。
Python并发
当Python初学者探索多线程的代码为了计算密集型优化时,问得最多的问题之一是:”当我用多线程的时候,为什么我的程序变慢了?“
在多核机器上,我们期望多线程的代码使用额外的核,从而提高整体性能。不幸的是,主Python解释器(CPython)的内部并不是真正的多线程,是通过一个全局解释锁(GIL)来进行处理的。
GIL是必须的,因为Python解释器是非线程安全的。这意味着当从线程内尝试安全的访问Python对象的时候将有一个全局的强制锁。在任何时候,仅仅一个单一的线程能够获取Python对象或者C API。每100个字节的Python指令解释器将重新获取锁,这(潜在的)阻塞了I/0操作。因为锁,CPU密集型的代码使用线程库时,不会获得性能的提高,但是当它使用多处理库时,性能可以获得提高。
并行库的实现
现在,我们将使用上面所提到的两个库来实现对一个“小”问题进行并发优化。
线程库
上面我们提到: 运行CPython解释器的Python不会支持通过多线程来实现多核处理。不过,Python确实有一个线程库。那么如果我们(可能)不能使用多个核心进行处理,那么使用这个库能取得什么好处呢?
许多程序,尤其是那些与网络通信或者数据输入/输出(I/O)相关的程序,都经常受到网络性能或者输入/输出(I/O)性能的限制。这样Python解释器就会等待哪些从诸如网络地址或者硬盘等“远端”数据源读写数据的函数调用返回。因此这样的数据访问比从本地内存或者CPU缓冲区读取数据要慢的多。
因此,如果许多数据源都是通过这种方式访问的,那么就有一种方式对这种数据访问进行性能提高,那就是对每个需要访问的数据项都产生一个线程 。
举个例子,假设有一段Python代码,它用来对许多站点的URL进行扒取。再假定下载每个URL所需时间远远超过计算机CPU对它的处理时间,那么仅使用一个线程来实现就会大大地受到输入/输出(I/O)性能限制。
通过给每个下载资源生成一个新的线程,这段代码就会并行地对多个数据源进行下载,在所有下载都结束的时候再对结果进行组合。这就意味着每个后续下载都不会等待前一个网页下载完成。此时,这段代码就受收到客户/服务端带宽的限制。
不过,许多与财务相关的应用都受到CPU性能的限制,这是因为这样的应用都是高度集中式的对数字进行处理。这样的应用都会进行大型线性代数计算或者数值的随机统计,比如进行蒙地卡罗模拟统计。所以只要对这样的应用使用Python和全局解释锁(GIL),此时使用Python线程库就不会有任何性能的提高。
Python实现
下面这段依次添加数字到列表的“玩具”代码,举例说明了多线程的实现。每个线程创建一个新的列表并随机添加一些数字到列表中。这个已选的“玩具”例子对CPU的消耗非常高。
下面的代码概述了线程库的接口,但是他不会比我们用单线程实现的速度更快。当我们对下面的代码用多处理库时,我们将看到它会显著的降低总的运行时间。
让我们检查一下代码是怎样工作的。首先我们导入threading库。然后我们创建一个带有三个参数的函数list_append。第一个参数count定义了创建列表的大小。第二个参数id是“工作”(用于我们输出debug信息到控制台)的ID。第三个参数out_list是追加随机数的列表。
__main__函数创建了一个107的size,并用两个threads执行工作。然后创建了一个jobs列表,用于存储分离的线程。threading.Thread对象将list_append函数作为参数,并将它附加到jobs列表。
最后,jobs分别开始并分别“joined”。join()方法阻塞了调用的线程(例如主Python解释器线程)直到线程终止。在打印完整的信息到控制台之前,确认所有的线程执行完成。
- # thread_test.pyimport randomimport threadingdef list_append(count, id, out_list):
- """
- Creates an empty list and then appends a
- random number to the list 'count' number
- of times. A CPU-heavy operation!
- """
- for i in range(count):
- out_list.append(random.random())if __name__ == "__main__":
- size = 10000000 # Number of random numbers to add
- threads = 2 # Number of threads to create
- # Create a list of jobs and then iterate through
- # the number of threads appending each thread to
- # the job list
- jobs = []
- for i in range(0, threads):
- out_list = list()
- thread = threading.Thread(target=list_append(size, i, out_list))
- jobs.append(thread)
- # Start the threads (i.e. calculate the random number lists)
- for j in jobs:
- j.start()
- # Ensure all of the threads have finished
- for j in jobs:
- j.join()
- print "List processing complete."
我们能在控制台中调用如下的命令time这段代码
- time python thread_test.py
将产生如下的输出
- List processing complete.
- real 0m2.003s
- user 0m1.838s
- sys 0m0.161s
注意user时间和sys时间相加大致等于real时间。这表明我们使用线程库没有获得性能的提升。我们期待real时间显著的降低。在并发编程的这些概念中分别被称为CPU时间和挂钟时间(wall-clock time)
多进程处理库
为了充分地使用所有现代处理器所能提供的多个核心 ,我们就要使用多进程处理库 。它的工作方式与线程库完全不同 ,不过两种库的语法却非常相似 。
多进程处理库事实上对每个并行任务都会生成多个操作系统进程。通过给每个进程赋予单独的Python解释器和单独的全局解释锁(GIL)十分巧妙地规避了一个全局解释锁所带来的问题。而且每个进程还可独自占有一个处理器核心,在所有进程处理都结束的时候再对结果进行重组。
不过也存在一些缺陷。生成许多进程就会带来很多I/O管理问题,这是因为多个处理器对数据的处理会引起数据混乱 。这就会导致整个运行时间增多 。不过,假设把数据限制在每个进程内部 ,那么就可能大大的提高性能 。当然,再怎么提高也不会超过阿姆达尔法则所规定的极限值。
Python实现
使用Multiprocessing实现仅仅需要修改导入行和multiprocessing.Process行。这里单独的向目标函数传参数。除了这些,代码几乎和使用Threading实现的一样:
- # multiproc_test.pyimport randomimport multiprocessingdef list_append(count, id, out_list):
- """
- Creates an empty list and then appends a
- random number to the list 'count' number
- of times. A CPU-heavy operation!
- """
- for i in range(count):
- out_list.append(random.random())if __name__ == "__main__":
- size = 10000000 # Number of random numbers to add
- procs = 2 # Number of processes to create
- # Create a list of jobs and then iterate through
- # the number of processes appending each process to
- # the job list
- jobs = []
- for i in range(0, procs):
- out_list = list()
- process = multiprocessing.Process(target=list_append,
- args=(size, i, out_list))
- jobs.append(process)
- # Start the processes (i.e. calculate the random number lists)
- for j in jobs:
- j.start()
- # Ensure all of the processes have finished
- for j in jobs:
- j.join()
- print "List processing complete."
控制台测试运行时间:
- time python multiproc_test.py
得到如下输出:
- List processing complete.
- real 0m1.045s
- user 0m1.824s
- sys 0m0.231s
在这个例子中可以看到user和sys时间基本相同,而real下降了近两倍。之所以会这样是因为我们使用了两个进程。扩展到四个进程或者将列表的长度减半结果如下(假设你的电脑至少是四核的):
- List processing complete.
- real 0m0.540s
- user 0m1.792s
- sys 0m0.269s
使用四个进程差不多提高了3.8倍速度。但是,在将这个规律推广到更大范围,更复杂的程序上时要小心。数据转换,硬件cacha层次以及其他一些问题会减弱加快的速度。
在下一篇文章中我们会将Event-Driben Basketer并行化,从而提高其运行多维参数寻优的能力。
相关推荐
最后,异步IO(非阻塞IO)和协程是Python并发编程的高级主题。Python的asyncio库提供了一种事件驱动的编程模型,通过async/await关键字实现协程,可以实现高效的并发执行,特别适用于I/O密集型任务。理解事件循环、...
"Python并发编程GPU"这个主题涉及到如何利用Python的库和框架来实现GPU的并行计算,以便高效地处理大量数据和复杂的数学运算。 并发编程是指在同一时间执行多个任务或子任务,以提高程序的执行效率。在Python中,...
Python并发编程是一种高效的编程方式,它可以使程序更加快速、可靠和高效。通过使用多进程和多线程,Python可以同时执行多个任务,提高程序的执行速度和效率。 进程和线程 在Python中,进程和线程是两个不同的...
总的来说,Python并发编程资料合集应该包含了对以上概念的详细讲解,包括如何使用asyncio、线程和进程,以及如何利用Pool类进行任务调度。通过学习这些内容,开发者能够编写出高效、可扩展的并发程序,应对各种复杂...
Python并发编程是Python编程语言中的一个重要领域,它涉及到如何在单个程序中同时处理多个任务。这通常是通过线程(threads)和进程(processes)实现的,它们允许程序并行执行,从而提高效率和响应速度。在多核...
总之,理解并熟练掌握Python的多线程编程,特别是`thread`、`threading`和`Queue`库的使用,是提升Python程序性能和响应能力的重要手段。在设计多线程程序时,应合理选择线程同步机制,充分利用队列进行线程间通信,...
Python并发编程是提升程序效率和性能的关键技术,它允许程序同时执行多个任务,特别是在处理I/O密集型和计算密集型任务时。Python提供了多种并发模型,包括多线程、多进程和协程。 多线程编程是通过Python的`...
Python的多线程编程是开发高效并发应用的关键技术之一。在Python中,多线程允许程序同时执行多个任务,这可以充分利用多核处理器的计算能力。然而,由于GIL(全局解释器锁)的存在,Python的多线程在CPU密集型任务上...
本文将深入探讨Python中的异步编程概念,包括回顾多线程、多进程以及生成器,并结合提供的课件和代码示例进行详细解释。 首先,多线程是并发执行任务的一种方式。在Python中,`threading`模块提供了创建和管理线程...
Python并发编程是提升软件效率和响应能力的关键技术,它涵盖了多线程、多进程和协程等多种并发模型。本文详细探讨了这些技术的实践指南。 首先,了解并发编程的概念至关重要。并发编程允许在同一时间片内执行多个...
### Python并发编程技术详解 随着计算机硬件性能的不断提升,尤其是多核处理器的普及,软件并发编程成为了提升程序性能的...通过不断实践和探索,开发者可以更好地掌握Python并发编程技术,为用户提供更高质量的服务。
"Python高级编程"涵盖了Python语言的深入理解和高级用法,这包括但不限于元编程、装饰器、生成器、协程、多线程与多进程、高级数据结构、错误处理、模块化编程、性能优化以及Python在大型项目中的应用。 1. **元...
总结来说,本文通过实例展示了Python中如何使用多线程实现并发执行,以及如何构建简单的并发测试框架。了解这些知识对于开发高效、可扩展的Python程序至关重要。同时,它也提醒我们在实际编程中要考虑Python的GIL...
与Python标准库中的`threading`相比,vthread库强调的是最小化代码改动就能实现多线程和线程池功能,这使得开发者无需对原始代码进行大规模重构,只需少量的代码调整就能利用多核处理器的优势,提高程序的并发执行...
在并发处理方面,Python提供了多种方式来实现多线程和多进程。尽管Python的全局解释器锁(GIL)限制了多线程的并行性,但通过多进程和异步I/O(如`asyncio`库)可以有效地利用多核处理器资源。书中会讲解如何在...
多线程编程可以让服务器程序更好地利用多核处理器的能力,提高服务的响应速度和吞吐量。 #### 四、多线程编程的注意事项 在实际应用多线程编程的过程中,还需要注意以下几点: 1. **线程安全性**:多线程环境下,...
8. Python在并行世界的应用:Python提供了多种库和工具,用于实现并行计算,如多线程和多进程模块。 9. 线程和进程概念:线程是操作系统能够进行运算调度的最小单位,进程则是系统进行资源分配和调度的一个独立单位...
在Python编程领域,多线程爬虫是一种高效的数据抓取技术,尤其在处理大量网页时,可以显著提高爬取速度。本主题“基于Linux的Python多线程爬虫程序设计”将深入探讨如何在Linux环境下利用Python实现多线程爬虫,以...
Stackless Python是Python的一个增强版本,它引入了一种低开销、轻量级的微线程扩展,这种扩展允许开发者利用线程式的编程模式,同时避免传统多线程编程中的性能瓶颈与复杂性问题。通过正确应用Stackless Python提供...