【问题标题】:Platform-dependent performance issues when selecting a large number of files with gtk.FileChooserDialog使用 gtk.FileChooserDialog 选择大量文件时与平台相关的性能问题
【发布时间】:2012-02-14 10:57:48
【问题描述】:

我有一个设计用于在 Windows 和 Ubuntu 上运行的 pygtk 程序。它是 Python 2.7 和带有静态绑定的 gtk2(即没有 gobject 自省)。我遇到的问题在 Ubuntu 上存在,但在 Windows 上不存在。

我的程序应该能够处理大量文件(这里我测试了大约 200 个文件),但每个文件的实际处理量并不多。我按文件排队处理并向用户展示进度。

问题是,在使用 gtk.FileChooserDialog 选择文件后(control-A 是你的朋友),程序挂起并且 gtk 事件在很长一段时间内都没有处理 - 即使我的回调函数已经返回。在此期间,所有内核的 CPU 使用率都保持在 80% 左右,iotop 显示我的进程正在以大约每秒 20MB 的速度写入磁盘,并且其他应用程序间歇性地无响应 - Chrome、Xorg、compiz 、banshee 和 gedit 的 CPU 使用率都很高(在选择文件之前使用率很低)。

这是一些示例代码。要重现,请单击按钮,从某处选择大约 200 个文件(大约十个屏幕值得按住 shift 和 down),然后单击 OK。什么文件无关紧要 - 对它们什么都不做。

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 上。在 Windows 上,挂起时间不到一秒。

我怀疑这是由于某些 Gnome 或 Unity 的“最近文件”功能或其他活动跟踪所致。进程 zeitgeist-daemon 在挂起期间 CPU 使用率也很高,但杀死它并不能解决问题。使用 Zeitgeist 活动日志管理器也不会禁用日志记录。即使可以禁用 Zeitgeist,我也不能指望我的用户禁用它。

有谁知道如何禁用 gtk 应用程序对最近文件的报告,或者知道其他任何可能导致此问题的原因?

必须通过“选择文件夹”对话框添加大量文件以进行处理,但对于较少数量的文件,每个文件的挂起时间似乎约为半秒,这实际上是不可接受的对于其他响应式应用。

(在 32 位 Windows 7 和 64 但 Ubuntu 11.10 上完成测试。Python 2.7 和 pygtk 2.24 都在)

【问题讨论】:

    标签: python ubuntu filesystems gtk


    【解决方案1】:

    速度变慢是因为gtk.FileChooser 小部件automatically 将所有选择的文件放入最近使用的文件列表(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 是特定于应用程序的),以显示它们。在确定哪些文件是最新的,a system time call is made 为每个项目。

    recently-used.xbel 能够grow without limit 使问题更加严重。因此,如果您在 recently-used.xbel 中有 5000 个项目,并且您选择了 200 个带有 gtk.FileChooser 的文件,您将获得(n=1 到 200 的总和)(5000 + n)~ 100 万次系统时间调用gtk 应用程序正在运行。

    gtk.Settings 中有一些属性可以让您的应用在历史记录中查找更少的文件,gtk-recent-files-limitgtk-recent-files-max-age,但它们不会阻止 ~/.local/share/recently-used.xbel 被写入。

    为防止recently-used.xbel 被写入,可以对其进行写保护,或将其替换为文件夹。在这种情况下,gtk 仍会尝试添加所有文件,但每次尝试都会失败。每 200 个文件的延迟约为 1 秒 - 我猜尝试的开销仍然很大。

    由于似乎没有办法关闭gtk.FileChooser 的这种行为,唯一的另一种方法是使用不同的文件选择器小部件。即使有 30000 个文件,在使用已弃用的 gtk.FileSelection 小部件时也不会出现明显的延迟。

    这是一个丑陋的小部件,但我想我将不得不使用它并提交错误报告/功能请求,以便能够禁用 gtk.FileChooser 的最近文件报告。

    【讨论】:

    【解决方案2】:

    这可能不算作答案,但可能会有所帮助。

    在查看了为什么 gtk2 中的文件选择器对话框打开如此缓慢后,我发现 gtk.FileChooserDialogs 不是轻量级对象。

    您不应该为一次性使用创建一个然后销毁它。相反,您应该重复使用它们,因为您可以只使用 .hide() 它们,并且当再次调用 .run() 时它们会重新出现。

    请注意,使用 dialog.set_current_folder(dialog.get_current_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()
    

    【讨论】:

    • 有趣的是,更改为 hide() 而不是 destroy() 似乎将挂起时间大致减半。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多