这一切都始于昨晚,当我制作一个需要8个左右的软件包的脚本时,包括pygame.mixer
,在我的计算机上导入这个需要几秒钟。
这意味着在脚本开始之前我必须等待10秒左右才能加载所有导入。因为我希望脚本显然尽可能快,所以我可以开始运行脚本,同时通过以下方式获取导入:
import threading
def import_modules():
import tkinter as tk
from pygame import mixer
import json
import webbrowser
print('imports finished')
a = threading.Thread(target=import_modules)
a.start()
for i in range(10000):
print('Getting Modules')
所以我的问题是:
这被认为是不好的做法会导致问题吗?
如果有,我可以使用替代品吗?
或者可以这样做吗?
答案 0 :(得分:3)
如果您正在使用CPython,这可能不会产生与您期望的一样多的改进。
CPython有一个全局解释器锁(" GIL"),确保一次只有一个线程可以执行Python字节码。
因此,只要导入线程执行Python代码,另一个线程就不会运行。例如,GIL由一个线程释放。等待I / O.因此可以节省一些时间。
对于tkinter是否真正的线程安全存在意见分歧。在原始线程中运行tkinter主循环并且不调用来自其他线程的tkinter调用仍然是明智的,因为可能导致崩溃。
GIL也会导致GUI程序出现问题。如果您使用第二个线程进行长时间运行的计算,则用户界面的响应速度可能会降低。至少有两种可能的解决方案。第一个是将长时间运行的计算分成小块,每个小块由after
方法执行。第二种是在不同的进程中运行计算。
评论中的后续问题:
还有什么可以加快执行时间吗?
您必须做的第一件事是 measure ;什么完全导致问题。然后,您可以查看问题区域并尝试改进它们。
例如模块加载时间。在profiler下运行您的应用,查看模块加载的时间和原因。
如果pygame.mixer
加载时间太长,您可以使用平台的原生调音台。类UNIX操作系统通常具有/dev/mixer
设备,而ms-windows具有不同的API。使用那些绝对不会花费10秒钟。
与此相关的成本:您将失去操作系统之间的可移植性。
有哪些替代方案
使用多核是尝试加快速度的常用策略。目前在CPython上,唯一的通用方式可以让代码在多个内核上并行运行multiprocessing
或concurrent.futures
。
但是,如果这种策略可行,则取决于问题的性质。
如果您的问题涉及对大量数据进行相同的计算,则相对容易并行化。在这种情况下,您可以期望最大加速大致相当于您使用的核心数。
可能是您的问题包含多个步骤,每个步骤都取决于上一步的结果。这些问题本质上是连续的,并且难以并行执行。
可能加快速度的其他方法可能是使用另一个Python实现,如Pypy。或者,您可以将cython与类型提示结合使用,将性能关键部分转换为已编译的C代码。
答案 1 :(得分:1)
我知道这是一个旧线程,但是我正在寻找一种方法来最小化我的应用程序的加载时间,并希望用户看到gui,以便他可以在后台导入其他模块的同时与它进行交互
我已经读到一些答案,暗示了一种懒惰的导入技术,我发现这很复杂“对我来说”,然后我偶然发现了一个建议,使用线程在后台导入模块,然后给了我一个机会,然后发现了它是最适合我需求的主意
下面的是使用PySimpleGUI的示例gui应用程序的代码,它要求用户输入URL,它将在默认浏览器窗口中打开它,唯一需要这样做的模块是webbrowser
,因此可以在加载其他模块时完成此工作
我在这段代码中添加了注释,以解释大部分内容,希望对您有所帮助, 在python 3.6,windows10上进行了测试。
请注意:这只是一个虚拟代码,作为展示。
# import essentials first
import PySimpleGUI as sg
import time, threading
# global variable names to reference to the imported modules, this way will
# solve the problem of importing inside a function local namespace
pg = None
js = None
wb = None
progress = 0 # for our progress bar
def importer():
# we will simulate a time consuming modules by time.sleep()
global progress
progress = 10
start = time.time()
global pg, js, wb
import pygame as pg
time.sleep(3)
print(f'done importing pygame mixer in {time.time()-start} seconds')
progress = 40
start = time.time()
import webbrowser as wb
time.sleep(2)
print(f'done importing webbrowser in {time.time()-start} seconds')
progress = 70
start = time.time()
import json as js
time.sleep(10)
print(f'done importing json in {time.time()-start} seconds')
progress = 100
print('imports finished')
# start our importer in a separate thread
threading.Thread(target=importer).start()
# main app
def main():
# window layout
layout = [[sg.Text('Enter url:', size=(15,1)), sg.Input(default_text='https://google.com', size=(31, 1), key='url')],
[sg.Text('Loading modules:', size=(15,1), key='status'),
sg.ProgressBar(max_value=100, orientation='horizontal', size=(20,10), key='progress')],
[sg.Button('Open url', disabled=True, key='open_url'), sg.Button('joysticks', disabled=True, key='joysticks'), sg.Cancel()]]
window = sg.Window('test application for lazy imports', layout=layout) # our window
while True: # main application loop
event, values = window.Read(timeout=10) # non blocking read from our gui
if event in [None, 'Cancel']:
window.Close()
break
elif event == 'open_url':
wb.open(values['url'])
print('helllooooooooooooo')
elif event == 'joysticks':
# show joystics number currently connected
pg.init()
n = pg.joystick.get_count() # Get count of joysticks
sg.Popup(f'joysticks number currently connected to computer = {n}')
# open url button is disabled by default and will be enabled once our webbrowser module imported
if wb:
window.Element('open_url').Update(disabled= False)
if pg:
window.Element('joysticks').Update(disabled= False)
# progress bar
window.Element('progress').UpdateBar(progress)
if progress >= 100:
window.Element('status').Update('Loading completed', background_color='green')
main()