Problèmes de performances liés à la plate-forme lors de la sélection d’un grand nombre de fichiers avec gtk.FileChooserDialog

J’ai un programme pygtk conçu pour fonctionner sur Windows et Ubuntu. Ce sont Python 2.7 et gtk2 avec les liaisons statiques (c.-à-d. Pas d’introspection gobject). Le problème que je rencontre existe sur Ubuntu mais pas sur Windows.

Mon programme est censé pouvoir traiter un grand nombre de fichiers (ici, je teste avec environ 200), mais le traitement réel par fichier n’est pas beaucoup. Je file le traitement par fichier et présente les progrès à l’utilisateur.

Le problème est que, après avoir choisi les fichiers avec un gtk.FileChooserDialog (control-A est votre ami), le programme se bloque et les événements gtk ne sont pas traités pendant un certain temps – même si ma fonction de rappel est revenue. Pendant ce temps, l’utilisation du processeur sur tous les cœurs est d’environ 80%, iotop montre que mon processus écrit sur le disque à environ 20 Mo par seconde et que les autres applications ne répondent plus par intermittence – Chrome, Xorg, compiz, banshee et gedit (ayant eu une faible utilisation avant de sélectionner les fichiers).

Voici un exemple de code. Pour reproduire, cliquez sur le bouton, sélectionnez environ 200 fichiers à partir de quelque part (une dizaine d’écrans valent la peine d’être décalés et vers le bas) et cliquez sur OK. Peu importe quels fichiers, rien ne se fait avec eux.

 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() 

Cela se traduit par un blocage de ~ 60 secondes après la fin du rappel. Pendant ce temps, rien ne doit se passer sauf la destruction de la boîte de dialog en cours de traitement (ce qui se produit pendant le blocage).

C’est sur Ubuntu 11.10. Sous Windows, il y a moins d’une seconde de blocage.

Je soupçonne que cela est dû à une fonctionnalité de «fichiers récents» de Gnome ou Unity ou à un autre suivi d’activité. Le processus zeitgeist-daemon a également une utilisation élevée du processeur pendant le blocage, mais sa suppression ne résout pas le problème. Désactiver non plus la journalisation avec le gestionnaire de journal d’activité Zeitgeist. Même si Zeitgeist peut être désactivé, je ne peux pas vraiment m’attendre à ce que les utilisateurs le désactivent.

Est-ce que quelqu’un sait comment désactiver le rapport des fichiers récents d’une application gtk, ou savoir quelque chose qui pourrait causer cela?

Un très grand nombre de fichiers devra être ajouté pour le traitement via une boîte de dialog “select-folder” à la place, mais pour un petit nombre de fichiers, le temps de suspension semble être d’environ une demi-seconde par fichier, ce qui n’est pas vraiment acceptable. application réactive

(tests effectués sur Windows 7 et 64 bits, mais sur Ubuntu 11.10. Python 2.7 et pygtk 2.24 sur les deux)

Le ralentissement est dû au fait que le widget gtk.FileChooser place automatiquement tous les fichiers sélectionnés dans la liste des fichiers récemment utilisés ( gtk.RecentManager.add_item() ).

L’ajout de cette fonction s’exécutant dans un thread séparé (et n’ayant apparemment aucun problème à acquérir le verrou gtk même pendant le blocage) dans l’exemple de code:

 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))) 

révèle (après avoir été exécuté pendant la nuit) que le délai par fichier augmente à mesure que le nombre de fichiers récents:

Nombre de fichiers ajoutés au fil du tempsTaux d'ajout de fichier

Comme il n’existe aucune méthode pour append plusieurs fichiers au fichier RecentManager , ils sont ajoutés un par un.

Chaque fois que l’une est ajoutée, les autres applications gtk sont informées que la liste des fichiers récents (stockée dans ~/.local/share/recently-used.xbel ) a été modifiée. Ils parsingnt ensuite le fichier et parcourent les éléments, recherchant les n éléments les plus récents (où n est spécifique à l’application), pour les afficher. Pour déterminer quels fichiers sont les plus récents, un appel d’heure système est effectué pour chaque élément.

Le problème est exacerbé par le fait que recently-used.xbel est capable de croître sans limite . Donc, si vous avez 5000 éléments dans recently-used.xbel , et que vous sélectionnez 200 fichiers avec un gtk.FileChooser , vous obtiendrez (sum de n = 1 à 200) (5000 + n) ~ 1 million d’appels de temps système pour chaque application gtk en cours d’exécution.

Il y a des propriétés dans gtk.Settings qui font que votre application recherche moins de fichiers dans l’historique, gtk-recent-files-limit et gtk-recent-files-max-age , mais ils n’empêchent pas ~/.local/share/recently-used.xbel d’être écrit.

Pour empêcher que recently-used.xbel soit écrit, vous pouvez le protéger en écriture ou le remplacer par un dossier. Dans ce cas, gtk tente toujours d’append tous les fichiers, mais chaque tentative échoue. Le délai est d’environ 1 seconde par 200 fichiers – je suppose que la tâche de réaliser la tentative est toujours importante.

Comme il ne semble pas possible de désactiver ce comportement de gtk.FileChooser , la seule autre méthode consiste à utiliser un autre widget filechooser. Même avec 30000 fichiers, il n’y a pas de délai perceptible lors de l’utilisation du widget obsolète gtk.FileSelection .

C’est un widget laid, mais je pense que je vais devoir l’utiliser et déposer un rapport de bogue / une demande de fonctionnalité pour pouvoir désactiver les rapports de fichiers récents par gtk.FileChooser .

Cela pourrait ne pas être considéré comme une réponse, mais cela pourrait aider.

Après avoir examiné pourquoi les dialogs de sélection de fichiers dans gtk2 étaient si lents à ouvrir, j’ai découvert que gtk.FileChooserDialog s ne sont pas des objects légers.

Vous ne devriez pas en créer un pour un usage unique, puis le détruire. Vous devriez plutôt les réutiliser comme vous pouvez juste les .hide() et ils réapparaîtront lorsque vous .run() nouveau .run() .

notez que l’utilisation de dialog.set_current_folder(dialog.get_current_folder()) force l’actualisation de la liste de fichiers.

Notez également que les éléments sélectionnés lorsque la boîte de dialog est masquée restront sélectionnés lorsque la boîte de dialog réapparaît, à moins que la liste de fichiers ne soit actualisée ou que les fichiers n’existent plus.


Si je change votre code pour suivre cela, il devient:

 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()