13 votes

Algorithme de tri en place interruptible

Je dois écrire un programme de tri en C et ce serait bien si le fichier pouvait être trié sur place pour économiser de l'espace disque. Les données sont précieuses, je dois donc m'assurer que si le processus est interrompu (ctrl-c), le fichier ne sera pas corrompu. Je peux garantir que le cordon d'alimentation de la machine ne sera pas arraché.

Détails supplémentaires : le fichier est ~40GB, les enregistrements sont 128-bit, la machine est 64-bit, le système d'exploitation est POSIX.

Des conseils pour y parvenir, ou des notes en général ?

Merci !

Pour clarifier : Je m'attends à ce que l'utilisateur veuille ctrl-c le processus. Dans ce cas, je veux quitter le processus de manière élégante et m'assurer que les données sont en sécurité. Cette question porte donc sur la gestion des interruptions et le choix d'un algorithme de tri qui peut se terminer rapidement si nécessaire.

Suivi (2 ans plus tard) : Juste pour la postérité, j'ai installé le gestionnaire SIGINT et il a très bien fonctionné. Cela ne me protège pas contre les pannes de courant, mais c'est un risque que je peux gérer. Code à https://code.google.com/p/pawnsbfs/source/browse/trunk/hsort.c et https://code.google.com/p/pawnsbfs/source/browse/trunk/qsort.c

12voto

Steve Jessop Points 166970

Jerry a raison, si c'est juste le Ctrl-C qui vous inquiète, vous pouvez ignorer le SIGINT pendant certaines périodes. Si vous voulez être à l'abri de la mort du processus en général, vous avez besoin d'une sorte de journalisation minimale. Afin d'échanger deux éléments :

1) Ajoutez un enregistrement dans une structure de contrôle à la fin du fichier ou dans un fichier séparé, indiquant les deux éléments du fichier que vous allez échanger, A et B.

2) Copiez A sur l'espace de grattage, enregistrez que vous l'avez fait, tirez la chasse.

3) Copiez B sur A, puis enregistrez dans l'espace scratch que vous l'avez fait, tirez la chasse.

4) Copier de l'espace scratch sur B.

5) Retirez l'enregistrement.

Il s'agit d'un espace supplémentaire de O(1) à toutes fins pratiques, ce qui compte toujours comme un espace en place selon la plupart des définitions. En théorie, l'enregistrement d'un index est O(log n) si n peut être arbitrairement grand : en réalité, c'est un très petit log n, et les limites raisonnables de matériel / temps d'exécution sont au-dessus de 64.

Dans tous les cas, quand je dis "flush", je veux dire valider les changements "suffisamment loin". Parfois, votre opération de purge de base ne fait que purger les tampons au sein du processus, mais elle ne synchronise pas réellement le support physique, car elle ne purge pas les tampons jusqu'aux niveaux du système d'exploitation, du pilote du périphérique et du matériel. C'est suffisant quand tout ce qui vous préoccupe est la mort du processus, mais si vous vous inquiétez d'un démontage brutal du support, vous devrez purger au-delà du pilote. Si vous vous inquiétez d'une panne de courant, vous devriez synchroniser le matériel, mais ce n'est pas le cas. Avec un onduleur ou si vous pensez que les coupures de courant sont si rares que cela ne vous dérange pas de perdre des données, c'est parfait.

Au démarrage, vérifiez l'espace d'effacement pour tout enregistrement de "swap-in-progress". Si vous en trouvez un, déterminez où vous en êtes et terminez l'échange à partir de là pour remettre les données dans un état sain. Puis recommencez votre tri.

Il est évident qu'il y a un problème de performance, puisque vous écrivez deux fois plus d'enregistrements qu'auparavant, et que les flushes/syncs peuvent être étonnamment coûteux. En pratique, votre tri in-place peut comporter des opérations de déplacement composées, impliquant de nombreux swaps, mais que vous pouvez optimiser pour éviter que chaque élément ne touche l'espace scratch. Vous devez simplement vous assurer qu'avant d'écraser une donnée, vous en avez une copie en sécurité quelque part et un enregistrement de l'endroit où cette copie doit aller afin de remettre votre fichier dans un état où il contient exactement une copie de chaque élément.

Jerry a également raison de dire qu'un véritable tri sur place est trop difficile et trop lent pour la plupart des besoins pratiques. Si vous pouvez épargner une fraction linéaire de la taille du fichier d'origine en tant qu'espace brouillon, vous aurez beaucoup plus de facilité avec un tri par fusion.

D'après vos précisions, vous n'auriez pas besoin d'opérations de vidage, même avec un tri sur place. Vous avez besoin d'un espace de grattage en mémoire qui fonctionne de la même manière et auquel votre gestionnaire SIGINT peut accéder afin d'obtenir des données sûres. avant quitter, plutôt que de restaurer au démarrage après une sortie anormale, et vous devez accéder à cette mémoire d'une manière sécurisée par rapport aux signaux (ce qui, techniquement, signifie utiliser un fichier de type sig_atomic_t pour indiquer quelles modifications ont été apportées). Malgré cela, il est probablement préférable d'utiliser un mergesort plutôt qu'un véritable tri in-place.

5voto

Jerry Coffin Points 237758

La partie concernant la protection contre le ctrl-c est assez facile : signal(SIGINT, SIG_IGN); .

En ce qui concerne le tri proprement dit, un tri par fusion fonctionne généralement bien pour le tri externe. L'idée de base est de lire autant d'enregistrements que possible en mémoire, de les trier, puis de les réécrire sur le disque. La façon la plus simple de procéder est d'écrire chaque exécution dans un fichier séparé sur le disque. Ensuite, vous les fusionnez : lisez le premier enregistrement de chaque exécution en mémoire, et écrivez le plus petit d'entre eux dans le fichier d'origine ; lisez un autre enregistrement de l'exécution qui a fourni cet enregistrement, et répétez jusqu'à ce que vous ayez terminé. La dernière phase est le seul moment où vous modifiez le fichier d'origine, donc c'est le seul moment où vous avez vraiment besoin de vous assurer contre les interruptions et autres.

Une autre possibilité consiste à utiliser un tri par sélection. Le mauvais point est que le tri lui-même est assez lent. Le bon point est qu'il est assez facile de l'écrire pour qu'il survive à presque tout, sans utiliser beaucoup d'espace supplémentaire. L'idée générale est assez simple : trouver le plus petit enregistrement du fichier, et le placer en première position. Ensuite, trouvez le plus petit enregistrement de ce qui reste et remplacez-le par le deuxième, et ainsi de suite jusqu'à la fin. Le bon côté de la chose est que la journalisation est triviale : avant de faire un échange, vous enregistrez les valeurs des deux enregistrements que vous allez échanger. Comme le tri s'effectue du premier au dernier enregistrement, la seule autre chose que vous devez suivre est le nombre d'enregistrements déjà triés à un moment donné.

5voto

caf Points 114951

Installer un gestionnaire pour SIGINT qui définit simplement un drapeau "le processus devrait bientôt se terminer".

Dans votre tri, vérifiez le drapeau après chaque échange de deux enregistrements (ou après chaque N échanges). Si le drapeau est activé, sortez.

3voto

R.. Points 93718

Utilisez le tri du tas et évitez les interruptions (par exemple, les signaux de bloc) pendant chaque opération d'échange.

0voto

Victor Hurdugaci Points 3794

Sauvegardez tout ce que vous prévoyez de modifier. Le mettre un drapeau qui marque un tri réussi. Si tout est OK alors gardez le résultat, sinon restaurez la sauvegarde.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X