|
Publicité ' | |||||||||||||||||||||||
|
|
#1 |
|
Nouveau Membre du Club
![]() Yann LE PETITCORPSAnalyste d'exploitation Inscription : avril 2011 Messages : 47 ![]() |
Bonjour,
Après avoir implémenté les codages de Golomb/Rice, le codage arithmétique/par intervalles et testé un nouvel algo de compression combinant nombres premiers et codes de Rice [hyper efficace pour coder les multiples de nombres premiers et/ou pas mal de valeurs particulières, mais qui semble par contre moins efficace que le codage de Rice dans le cas général Pour l'instant, j'utilise un arbre de Huffman statique (cf. je lis toutes les données d'un fichier avant de construire l'arbre de Huffman qui sera donc spécifique à ce fichier), mais pense adapter ça assez vite en un arbre dynamique ou semi-dynamique, cf. par blocs (ça dépendra du gain obtenu) Les codages de Rice/Golomb et de Huffman ont pour points commun de générer un code dont la taiile des symboles les plus fréquents est plus petite que la taille des symboles les moins fréquents, les codes de Huffman ayant l'avantages d'être théoriquement optimaux Enfin en théorie car mon implémentation du codage de Huffman à tendance à me générer des codes d'au moins deux bits pour les symboles les plus fréquents alors qu'avec un code sur un seul bit pour le symbole le plus fréquent, la compression devrait encore être meilleure, surtout quand 99% des caractères sont les mêmes (mais bon, je suis vraiment persuadé que c'est mon implémentation qui a un pb à ce niveau là ...) Pour en revenir au sujet, j'ai remarqué que les codes générés par mon codage de Huffman semblent universels (cf. deux codes distincts n'ont jamais le même préfixe => c'est peut-être de là que vient mon pb de codes à 2 bits minimum ...) et j'utilise actuellement pour la décompression une table de conversion "code de Huffman" -> "symbole non compressé" plutôt que de devoir parcourir [au moins partiellement] l'arbre de Huffman à chaque code/symbole de Huffman rencontré Ca m'évite de devoir transmettre l'arbre pour la décompression (c'est une simple table de conversion "octet lu -> premier code de Huffman + taille de ce premier code + symbole décompressé" qui le remplace) , ce qui me permet de décoder plus rapidement plusieurs symboles lorsqu'ils sont présents dans le même octet + me donne de plus la possibilité d'effectuer un maximum de lectures/traitements octet/octet et de minimiser les lectures/traitements bit/bit (je pense d'ailleurs très vite remplacer la lecture octet/octet par une lecture de 16 ou 32 bits à chaque fois, voir même 64/128 bits avec l'aide des registres MMX/SSE) Je pense de plus étendre le principe en construisant une table de conversion qui me permette de générer directement plusieurs caractères en sortie lorsque plusieurs codes de Huffman sont compactés dans le même octet (et à plus forte raison quand ce sera un mot/double mot ou une valeur sur 64 ou 128 bits ...) , plutôt que de devoir retirer du dernier octet lu le code déjà décodé, cf. décalage à gauche du nombre de bits occupés par le dernier code rencontré, puis de tester à chaque fois pour chaque nouveau bit lu si c'est bien un code de huffman valide ou pas qui se trouve dans les bits de poids fort de l'octet ainsi mis à jour bit par bit (et si non, faire un nouvel essai en lisant un bit de plus et ainsi de suite ...) [rectificatif : les bits de poids forts contiennent maintenant obligatoirement un code valide car j'y ajoute autant de bits lus que la taille du dernier code rencontré => ça va plus vite et ça me rajoute en plus la détection automatique d'éventuels codes erronés => quelqu'un aurait-ils des infos concernant le décodage de codes de Huffman directement à partir d'une table et ne nécessitant donc pas le stockage de l'arbre + le parcours partiel de cet arbre à chaque code rencontré ? (c'est ce qui est fait dans les fichiers jpeg mais c'est fait avec des tables statiques alors que je voudrais un truc équivalent mais avec des tables qui puissent être dynamiquement mises à jour) PS : ça ressemble une peu à ce qui est décrit dans ce pdf http://www.google.fr/url?sa=t&rct=j&...oiGi1g&cad=rja sauf que le décodage d'un symbole est fait en un seul coup, cf. il n'y a pas d'indirection vers des sous-tables (mais je pense que vais sûrement rajouter cette indirection car ça rendra mon algo plus "générique/adaptable/APIsable") @+ Yannoo |
|
|
10
|
|
|
#2 |
|
Nouveau Membre du Club
![]() Yann LE PETITCORPSAnalyste d'exploitation Inscription : avril 2011 Messages : 47 ![]() |
Mon implémentation de la compression de Huffman commence enfin à devenir fonctionnelle
Bon ok, le taux de compression n'est pas très faramineux car je reste persuadé qu'il y a un pb dans mon générateur d'arbre de Huffman (qui génère des codes d'au moins 2 bits alors que je pense qu'un code d'un seul bit pour le symbole le plus courant serait plus optimal, du moins quand il est bien plus fréquent que la somme des fréquences de tous les autres ...) Il y a de plus une copie de la table qui a permis de créer l'arbre de Huffman qui est stockée en entête du fichier .huf (ce qui rajoute à peu prêt 6 Ko au fichier), =>je pense pouvoir assez fortement réduire cet entête en n'y stockant que les valeurs réellement utiles (ça devrait réduire l'entête à qqchose de l'ordre du Ko, voir moins) A noter que le décodeur lit pour l'instant les données compressées par blocs de 16 bits mais j'ai des pb de décodage sur le tout dernier symbole => il faut absolument que je rêgle ce pb avant de pouvoir commencer à traiter les données par blocs de 32 ou 64 bits ... Et question rapidité, c'est encore à l'état de prémisses de prototypage donc ce n'est pas encore ça ... mais ça devrait venir vite assez une fois le pb du dernier symbole à décoder résolu Car une fois ce pb rêglé, je pourrais alors passer à un traitement des données compressées par blocs de 32 bits (il fonctionne actuellement avec des blocs compressés de 16 bits), puis ensuite en 64/128 bits avec le MMX/SSE si ça se passe bien Je retirerais ensuite enfin tous les fprint(stderr, ...) que j'y ai ajouté petit à petit afin de m'aider à débugger car ça à l'air de ralentir pas mal le traitement ... + je boucle pour l'instant sur chaque symbole compressé dans un bloc de 16 bits mais pense carrément mettre tous les symboles décompressés correspondants au bloc compressé dans la table de correspondances codes compressés -> valeurs décompressées (cf. plutôt que d'aller dans la table chercher les symboles un par un, la table de correspondance me donnera directement toute la suite des symboles décompressés correspondants au bloc compressé de 8/16/32/64/128 bits) Par contre, c'est là qu'il faudra que j'y gère des indirections afin de ne pas avoir à gérer une table de correspondance de plusieurs Mo car vu qu'en 16 bits ça fait déjà dans les 6 Ko, en 32 bits ça fera dans les 34 Mo et qqchose de l'ordre du giga-octet en 64 bits => même pas la peine de penser à gérer ça en 128 bits sans une bonne dose d'indirection dans la table ... => mais j'ai déjà une idée sur la méthode à utiliser pour gérer les indirections dans la table de correspondance ... qui devrait assez sûrement réduire la table de l'ordre du Mo maximum pour gérer des blocs de 128 bits + de permettre de générer cette table au moins des centaines de fois plus vite [au bas mot car je ne serais pas du tout étonné que ça puisse même être plutôt de l'ordre de milliers ou de millions de fois ...] => avec tout ça, je pense que ça devrait y donner une accélération de l'ordre de 5x à 10x, voir même bien plus avec un peu de chance (pour l'instant, la table de correspondance "multi-codes" n'est construite/gérée que du côté décodeur, cf. huffman_t Remap[100000] en tout début du source dehuffman.c, mais je pense rajouter dans un très proche avenir l'équivalent du côté codeur [avec la maj de cette table en dynamique bien sûr]) @+ Yannoo |
|
|
00
|
|
|
#3 | ||
|
Nouveau Membre du Club
![]() Yann LE PETITCORPSAnalyste d'exploitation Inscription : avril 2011 Messages : 47 ![]() |
Voici un exemple du pb du "symbole utilisé en très grande majorité mais toujours codé sur 2 bits à la place d'un seul" :
Code :
1 ASCII 97 (58 occurrences 1 bit/symbole 58x1 = 58 bits) 00 ASCII 10 (4 occurrences 2 bits/symbole 4x2 = 8 bits) 011 ASCII 100 (1 occurence 3 bits/symbole 1x3 = 3 bits) 0100 ASCII 98 (1 occurence 4 bits/symbole 1x4 = 4 bits) 0101 ASCI 99 (1 occurrence 4 bits/symbole 1x4 = 4 bits) soit **seulement** 77 bits à la place des 133 actuellement générés => mon générateur d'arbre de Huffman devrait donc normalement y compresser ces données à peu prêt 2x mieux qu'actuellement ... (la compression devrait s'approcher des 13% avec ces données, soit un taux de compression d'environ 8x) ==> par contre, j'ai beau essayer d'analyser le code, je n'y trouve toujours pas la source du pb (si ce n'est que ça génère des codes = 0 alors qu'il me semblerait plus logique/simple qu'il ne puisse pas en générer pour laisser l'entrée 0 libre afin de s'en servir comme marqueur de fin ou qqchose du style) PS : le décodage "multi-codes par table[s] 8/16/32/64/128 bits" devrait arriver très vite du côté décodeur et faire ses premières apparitions dans la partie codeur d'ici quelques jours PS2 : je pense temporairement repasser par le codage/décodage de Golomb/Rice car il me semble plus optimum pour gérer le cas d'une très forte fréquence d'un symbole par rapport aux autres et qu'il me permettra de générer beaucoup plus facilement/rapidement des données de tests "sur mesures pour tester les cas les plus problêmatiques" afin de m'aider à mettre au point cette table de conversion "code de qqchose -> séries de valeurs qu'il doit remplacer" en 8/16/32/64/128 bits (en passant, ça me permettra d'y rajouter une gestion des symboles "par mots" plutôt que par caractère/symbole/valeur comme actuellement + des codes du style "x fois le symbole y" [cf. y incorporer une RLE quoi] ) @+ Yanooo |
||
|
|
00
|
|
|
#4 |
![]() ![]() Ingénieur systèmes embarqués Inscription : juin 2009 Messages : 1 821 ![]() |
Ca a l'air passionnant, mais trop compliqué.... Je voulais quand même te dire que tu es lu ^^
__________________
Si Code::Blocks vous dit undefined reference to 'socket@12', cela signifie que vous avez un problème d'édition des liens. Allez dans Projects / Build Options / Linker Settings / Add et renseigner ici les .a qui vont bien. Exemple pour les sockets : C:\Program Files\CodeBlocks\MinGW\lib\libws2_32.a Pour les adeptes du langage SMS, allez ici et ramenez la traduction française ^^ Pour vos problèmes d'embarqué, utilisez le forum dédié ! |
|
00
|
|
|
#5 |
|
Nouveau Membre du Club
![]() Yann LE PETITCORPSAnalyste d'exploitation Inscription : avril 2011 Messages : 47 ![]() |
C'est assez compliqué à la base mais j'espère bien en retirer assez vite qqchose de simple, rapide et fonctionnel
Je reviendrais donc sûrement dans quelques temps ... une fois que j'aurais gagné un ratio de l'ordre de 10x entre la vitesse et le taux de compression/décompression actuel ... avec une compression effective bien sûr Heu ... avec 2 fonctions dans ce style : int CompressData(void * original, int size, void *compressed) [qui retourne la taille du bloc *original compressé dans *compressed ou < 0 en cas d'erreur] et int DecompressData(void *compressed, int size, void *decompressed) [qui retourne la taille du bloc *compressed decompressé dans *decompressed ou <0 en cas d'erreur ] => ce serait toujours trop compliqué ??? @+ Yannoo |
|
|
00
|
|
|
#6 |
|
Nouveau Membre du Club
![]() Yann LE PETITCORPSAnalyste d'exploitation Inscription : avril 2011 Messages : 47 ![]() |
Je me permet de revenir car j'ai déjà gagné prêt de 20x sur la vitesse de construction de l'arbre de Huffman qui ne prend plus maintenant que 1 ms pour générer les codes de Huffman de mon fichier de test
(alors qu'il prenait avant 19 ms pour faire la même chose ...) => la ruse ? j'y ai simplement retiré l'affichage de l'arbre en mode texte Côté décodeur, j'ai à peine doublé sa vitesses en commentant l'affichage des commentaires (c'est l'initialisation du tableau de conversion qui prend maintenant 91% du temps ...) + je commence à avoir des doutes sur l'utilité de devoir passer par des tables 32/64/128 bits côté codeur et décodeur car ça semble pour l'instant plutôt ralentir qu'autre chose ... (le passage par une table 16 bits est vraiment efficace mais vu le résultat sur le décodeur avec des tests en 32 bits, je commence franchement à en douter ...) @+ Yannoo |
|
|
00
|
|
|
#7 |
|
Nouveau Membre du Club
![]() Yann LE PETITCORPSAnalyste d'exploitation Inscription : avril 2011 Messages : 47 ![]() |
J'ai radicalement modifié la structure de ma table de conversion
(c'était 256 recopies des 256 premières nodes huffman_t du tableau HuffmanNodes[] afin de pouvoir gérer les 65536 valeurs possibles [16 bits] à chaque lecture d'un bloc de donnée compressé et c'est maintenant devenues 256x256 index sur le tableau HuffmanCodes[]) => le temps d'initalisation de la table de conversion "codes de Huffman -> première valeur décompressée" y est maintenant passé de 22 ms à 1 ms ==> temps total de décompression : 4 ms (26 ms avant la modif) (le "taux de squattage" de l'initialisation de cette table sur le temps total entre le lancement de la décompression et le retour à l'invite de commande est passé de 91% à 15% avec mon fichier de test) Côté compression c'est passé de 28 ms à 13 ms (plus de 2x plus rapide) mais je pense que ça devrait très vite passer en dessous des 10 ms ... Bon ok, c'est encore assez loin des 10x mais je n'ai même pas encore commencé à y implémenter la méthode de compression/décompression multi-codes PS : en plus, ça a résolu mon pb du dernier caractère qui n'était pas systématiquement décompressé (de l'autre côté, j'ai maintenant le pb inverse cad qu'il me rajoute des fois quelques caractères en plus à la fin mais ça devrait être relativement facile à corriger [bien plus facilement que dans l'autre sens ...] => j'ai fait quelques tests sur des .pdf, .tif, .doc et autres et ça à l'air de passer car c'est des 0 qu'il y rajoute) @+ Yannoo |
|
|
00
|
|
|
#8 | ||||||
|
Nouveau Membre du Club
![]() Yann LE PETITCORPSAnalyste d'exploitation Inscription : avril 2011 Messages : 47 ![]() |
Côté codeur, la vitesse de génération des codes de Huffman s'est maintenant plus que largement améliorée car elle ne représente désormais même pas 5% du temps total nécessaire à la compression d'un gros fichier
Par exemple, ça donne ça sur un fichier texte de 6,5 Mo : Initialisation : 4 ms Lecture "à vide" : 72 ms (simple comptage du nombre d'occurences / caractère dans le fichier) Calcul de l'arbre de Huffman : 24 ms (mais **seulement** 1 ms sans l'affichage de l'arbre [cf. verbose=0] Conversion/écriture séquentielle des caractères/codes compressés : 444 ms => les améliorations notables de performance sur le codeur ne peuvent donc désormais plus se faire qu'au niveau de la routine d'écriture séquentielle des caractères convertis en codes de Huffman dans le fichier compressé Code :
(c'est ici qu'est maintenant pris 80% du temps total nécessaire à la compression d'un fichier ...) Code :
(car elle y minimise les accès en lecture, travaillant par groupe de 16 caractères à chaque fois) Code :
PS : ce qui est hallucinant c'est que le codeur va déjà plus vite que le décodeur ... (car le décodeur doit en plus vérifier qu'on est pas en fin de fichier à chaque lecture d'un bloc de 16 bits [ + surtout l'équivalent pour chaque bit retiré du buffer ...] + que je n'y ai pas encore implémenté le décodage "multi-codes" [mais ça devrait arriver assez vite, la partie théorique étant quasiment terminée]) PS2 : il y un très gros bug dans le décodeur quand les codes de Huffman dépasse 16 bits (j'ai uniquement trouvé le pb avec de gros fichier texte et des .pdf bien plus gros y sont passés sans pb) @+ Yannoo |
||||||
|
|
00
|
|
|
#9 |
|
Nouveau Membre du Club
![]() Yann LE PETITCORPSAnalyste d'exploitation Inscription : avril 2011 Messages : 47 ![]() |
Pour l'instant, j'arrive à ces performances sur le codeur :
Huffman compression by YLP v0.2 Open the file big.txt : 1 ms Init Huffman Codes : 5 ms Read symbols : 45 ms 144 Mbits/s (6336 Kbytes) Generate Huffman Table : 1 ms 348 Knodes/s (348 nodes) Aggregate Codes : 0 ms 6488 Msyms/s (28774 Kbits / 6488 Ksyms = 4.541 bits/symbol) Write the compressed data : 386 ms 76 Mbits/s (3599 Kbytes) Compression : 3686165 / 6488666 = 56.81% Input : 118.514 Mbits/s (6488666 bytes / 438 ms) Output : 67.327 Mbits/s (3686165 bytes / 438 ms) Total time : 438 ms => je pense que la mise en place de l'optimisation multi-codes sera effective dans les prochains jours (cf. compacter "intelligemment" plusieurs codes de Huffman consécutifs dans des octets/mots/double-mots afin de permettre le décodage **direct** de 1, 2, 3 ou 4 octets compressés consécutifs via une "simple" table de conversion du côté décodeur => jusqu'à 28 valeurs décompressées d'un coup par la lecture/conversion d'une seule valeur 32 bits de codes compactés) PS : c'est la partie "Write the compressed data" qui est concernée par le multi-code => je pense que ça devrait à peu prêt doubler les performances de conversion/déconversion et ça devrait faire qqchose comme du 150 Mb/s en E/S (ok, c'est encore faible quand je vois que mon DD peut faire jusqu'à 450 Mb/s mais il n'y a plus qu'un rapport de 3x à gagner, contre un rapport d'environ 1000x il y a à peine plus d'une semaine @+ Yannoo |
|
|
00
|
|
|
#10 | ||||
|
Nouveau Membre du Club
![]() Yann LE PETITCORPSAnalyste d'exploitation Inscription : avril 2011 Messages : 47 ![]() |
Je viens de tester le remplacement de la génération de l'arbre de Huffman (cf. codage caractère par caractère) par la génération d'un tableau mémorisant toutes les mots/syllabes/groupes de lettres différent(e)s trouvé(e)s
[cf. codage par mots/syllabes/groupe de lettres et non plus caractère par caractère]. Ce tableau est ensuite trié par nombre d'occurence en première clé, par le nombre de caractères en seconde clé puis par l'ordre lexicographique en troisième clé. Puis les codes sont alors générés séquentiellement de la première entrée du tableau (le mot le plus commun) à la dernière entrée (la syllabe la moins commune, comportant le moins de lettres et "en dernière position lexicographique dans le groupe des syllabes ayant autant de lettres") [en passant, ça m'a permis de retrouver comment générer séquentiellement des codes de Golomb incrémentaux grace à la formule codesize = (int)(log(++code) / log(2))*2 + 1 => j'y gagne entre 5 et 10% en taux de compression **MAIS** j'ai dû très grandement réduire la taille de mon fichier de texte big.txt de 6.5 Mo à un peu moins de 1 Mo pour avoir un temps de traitement "humain" (presque une minute de calcul pour même pas 10% de compression de gagnée sur un fichier de 1 Mo => le ratio rapidité/compression y perd d'un facteur de 1000 ou qqchose comme ça ==> j'ai dû lacher l'affaire avec le fichier big.txt original de 6.5 Mo car il n'avait toujours terminé la génération des codes au boût de 10 minutes ... vs à peine 1 ms pour générer l'arbre de Huffman ===> y'a pas, Huffman c'est vraiment très fort [c'est la recherche/mémorisation des mots/syllabes déjà utilisés qui bouffe tout le temps CPU => ça me génére déjà 12430 symboles sur mon fichier de test de 1 Mo à la place de seulement 348 nodes pour l'arbre de Huffman qui marche caractère par caractère et sur un fichier 6x plus gros Avec l'arbre de Huffman Compression : 3686165 / 6488666 = 56.81% Total time : 438 ms vs Avec la compression "par mot/syllabes/groupes de lettres" compression = 47% (407228/824824) et environ une minute de calcul (je n'y ai pas mis encore implémenté le timing) Code :
Code :
Le bon côté, c'est que ça m'a fait réimplémenter WC [WorldCount) sans le vouloir => l'optimisation "par mots/syllabique" n'ayant pas tout l'air performante, je vais donc maintenant pouvoir revenir à 100% sur l'optimisation multi-codes qui devrait quant à elle être bien plus performante PS : de l'autre côté, je vais regarder comment est faites l'implémentation de wc sous Linux => je pense qu'il doivent au moins aller de 100 à 1000x plus vite que moi pour compter/stocker tous les mots utilisés ... PS2 : vu pour le wc => il ne fait que compter les mots/lignes/caractères, il ne compte pas le nombre de mots différents (normal qu'il aille sans aucun pb dans les 1000x plus vite car il en fait 1000x moins ...) PS3 : je pense que wc devrait devenir bien plus rapide s'il utilisait des lectures par blocs plutôt que des lectures caractère par caractère ... [cf. ça marche via des c = getc() / isword(c) => 2 appels de fonction pour chaque caractère lu ...] @+ Yannoo |
||||
|
|
00
|
Copyright © 2000-2013 - www.developpez.com