Python 使用gtk.FileChooserDialog选择大量文件时出现与平台相关的性能问题
我有一个pygtk程序,可以在Windows和Ubuntu上运行。它是Python2.7和带有静态绑定的gtk2(即无gobject内省)。我遇到的问题存在于Ubuntu上,但不存在于Windows上 我的程序应该能够处理大量的文件(这里我测试了大约200个),但是每个文件的实际处理量并不多。我按每个文件对处理进行排队,并向用户显示进度 问题是,在使用gtk.FileChooserDialog(control-a是您的朋友)选择文件后,程序挂起,gtk事件在相当长的一段时间内没有得到处理,即使我的回调函数已返回。在此期间,所有内核上的CPU使用率约为80%,Python 使用gtk.FileChooserDialog选择大量文件时出现与平台相关的性能问题,python,ubuntu,filesystems,gtk,Python,Ubuntu,Filesystems,Gtk,我有一个pygtk程序,可以在Windows和Ubuntu上运行。它是Python2.7和带有静态绑定的gtk2(即无gobject内省)。我遇到的问题存在于Ubuntu上,但不存在于Windows上 我的程序应该能够处理大量的文件(这里我测试了大约200个),但是每个文件的实际处理量并不多。我按每个文件对处理进行排队,并向用户显示进度 问题是,在使用gtk.FileChooserDialog(control-a是您的朋友)选择文件后,程序挂起,gtk事件在相当长的一段时间内没有得到处理,即使我
iotop
显示我的进程正在以每秒20MB的速度写入磁盘,而其他应用程序会间歇性无响应-Chrome、Xorg、compiz、banshee和gedit都有较高的CPU使用率(在选择文件之前使用率较低)
下面是一些示例代码。若要复制,请单击按钮,从某处选择大约200个文件(大约十个屏幕值按住shift键和down键),然后单击“确定”。什么文件都不重要,什么都不做
import gtk,gobject,time
def print_how_long_it_was_frozen():
print time.time() - start_time
def button_clicked(button):
dialog = gtk.FileChooserDialog(
'Select files to add', w, gtk.FILE_CHOOSER_ACTION_OPEN,
buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_OPEN, gtk.RESPONSE_OK))
dialog.set_select_multiple(True)
dialog.set_default_response(gtk.RESPONSE_OK)
response = dialog.run()
files = dialog.get_filenames()
dialog.destroy()
for i, f in enumerate(files):
print i
global start_time
start_time = time.time()
gobject.idle_add(print_how_long_it_was_frozen)
w = gtk.Window()
b = gtk.Button('Select files')
w.add(b)
b.connect('clicked', button_clicked)
w.show_all()
gtk.main()
这将导致回调结束后约60秒的挂起,在此期间,除了正在处理的对话框的销毁(在挂起过程中发生)之外,不应发生任何事情
这在Ubuntu 11.10上。窗户上的悬挂时间不到一秒钟
我怀疑这是由于某些Gnome或Unity的“最近的文件”功能或其他活动跟踪造成的。进程zeitgeist守护进程
在挂起期间也有很高的CPU使用率,尽管杀死它并不能解决问题。使用Zeitgeist活动日志管理器禁用日志记录也是如此。即使时代精神可以被禁用,我也不能期望我的用户禁用它
是否有人知道如何禁用gtk应用程序的最新文件报告,或者知道任何其他可能导致此问题的原因
必须通过“选择文件夹”对话框添加大量文件进行处理,但对于数量较少的文件,每个文件的挂起时间似乎约为半秒,这对于其他响应迅速的应用程序来说是不可接受的
(测试在32位Windows 7和64上进行,但在Ubuntu 11.10.Python 2.7和pygtk 2.24上都进行)这可能不算答案,但可能会有所帮助 在研究了为什么gtk2中的文件选择器对话框打开得如此缓慢之后,我发现
gtk.FileChooserDialog
s不是轻量级对象
你不应该为一次使用创建一个,然后销毁它。相反,您应该重用它们,因为您只需.hide()
它们,当再次调用.run()
时,它们就会重新出现
请注意,使用dialog.set\u current\u folder(dialog.get\u current\u folder())
会强制刷新文件列表
还请注意,隐藏对话框时选择的项目将在对话框重新出现时保持选中状态,除非刷新文件列表或文件不再存在
如果我更改您的代码,使其符合以下要求:
import gtk,gobject,time
def print_how_long_it_was_frozen():
print time.time() - start_time
def button_clicked(button):
response = dialog.run()
files = dialog.get_filenames()
dialog.hide()
for i, f in enumerate(files):
print i
global start_time
start_time = time.time()
gobject.idle_add(print_how_long_it_was_frozen)
w = gtk.Window()
b = gtk.Button('Select files')
w.add(b)
b.connect('clicked', button_clicked)
w.show_all()
dialog = gtk.FileChooserDialog(
'Select files to add', w, gtk.FILE_CHOOSER_ACTION_OPEN,
buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_OPEN, gtk.RESPONSE_OK))
dialog.set_select_multiple(True)
dialog.set_default_response(gtk.RESPONSE_OK)
gtk.main()
dialog.destroy()
减速是因为
gtk.FileChooser
小部件将所有选定的文件放入最近使用的文件列表(gtk.RecentManager.add_item()
)
在示例代码中添加在单独线程中运行的此函数(即使在挂起期间也似乎没有问题获取gtk锁):
def log_n_recent_files():
manager = gtk.recent_manager_get_default()
manager.purge_items()
while True:
time.sleep(1)
with gtk.gdk.lock:
items = manager.get_items()
with open('log.log','a') as f:
f.write('%f %d\n'%(time.time(), len(items)))
显示(在整夜运行后)每个文件的延迟随着最近文件数的增加而增加:
由于没有方法将多个文件添加到RecentManager
,因此每次只添加一个文件
每次添加一个时,其他gtk应用程序都会收到通知,说明最近使用的文件列表(存储在~/.local/share/recently used.xbel
中)已更改。然后,他们解析文件并遍历项目,查找最近的n个项目(其中n是特定于应用程序的),以显示它们。确定每个项目的最新文件时
最近使用的。xbel
能够使问题更加严重。因此,如果您在最近使用的.xbel中有5000个项目,并且您正在使用gtk.FileChooser
选择200个文件,那么对于每个运行的gtk应用程序,您将得到(从n=1到200的总和)(5000+n)~100万次系统调用
gtk.Settings
中有一些属性可以让你的应用程序在历史记录中查找更少的文件,gtk-recent-files-limit
和gtk-recent-files-max-age
,但它们不会阻止写入~/.local/share/recently-used.xbel
要防止最近使用的.xbel被写入,可以对其进行写保护,或者用文件夹替换它。在这种情况下,gtk仍然尝试添加所有文件,但每次尝试都失败。延迟大约为每200个文件1秒-我猜尝试的开销仍然很大
由于似乎无法关闭gtk.FileChooser
的这种行为,因此唯一的其他方法是使用不同的FileChooser小部件。即使有30000个文件,在使用不推荐使用的gtk.FileSelection
小部件时也没有明显的延迟
这是一个丑陋的小部件,但我想我必须使用它并提交一个bug报告/功能请求,以便能够通过gtk禁用最近的文件报告。FileChooser
有趣的是,将hide()改为destroy()似乎将挂起时间大约减少了一半。相关bug报告的事实是gtk.RecentManager.add_item()
速度较慢,看起来它可能已在gtk3中修复,但在gtk2中未修复。