N-PN White-Hat Project
Le mystère de GNU yes - Version imprimable

+- N-PN White-Hat Project (https://dev.n-pn.fr/forum)
+-- Forum : Informatique (https://dev.n-pn.fr/forum/forumdisplay.php?fid=12)
+--- Forum : GNU/Linux | Unix (https://dev.n-pn.fr/forum/forumdisplay.php?fid=31)
+--- Sujet : Le mystère de GNU yes (/showthread.php?tid=2089)



Le mystère de GNU yes - spin - 05-07-2012

Salut, je suis confronté à un problème curieux, qui me fait penser que les programmeurs GNU sont vraiment des soricers Big Grin

J'imagine que vous connaissez tous le programme yes, qui affiche "y\n" en boucle quand lancé sans argument. J'ai voulu programmer un clone de yes en assembler, dont voici le code :

Code :
section .text

global _start

_start:

push WORD 0x0a79   ;;  "y\n"

x:
    mov eax, 4
    mov ebx, 1
    mov ecx, esp
    mov edx, 2
    int $80
    jmp x

Voici maintenant le code de GNU yes (en C, 88 lignes) : http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/yes.c

Je pense pouvoir dire sans prétention que mon yes devrait être plus optimisé, pourtant il n'en est rien. Voici les pseudo-benchmarks que j'ai effectué :

Code :
$ timeout 3s yes > lol1      # GNU yes (/usr/bin/yes)
$ timeout 3s ./yes > lol2    # mon yes

Sur 3 secondes d'exécution, GNU yes peut me produire 146944000 lignes de 'y', tandis que mon ./yes maison n'en produit qu'un nombre à 5 chiffres, la différence n'est pas moindre !

Je me suis dit que GNU yes devait avoir une priorité d'exécution plus haute que mon ./yes, ce qui faisait de lui un programme plus rapide. En regadant via top, les deux sont fixés à la même priorité NI = 0.

Cela demeure un mystère, avez-vous une idée de ce qui peut rendre GNU yes plus rapide que mon programme de 7 instructions assembleur ? Ou bien les codeurs de GNU sont des vraiment sorciers ? Avez-vous une idée d'un autre facteur qu'il faut prendre en considération ?


RE: Le mystère de GNU yes - ark - 05-07-2012

Hum, j'ai pas compris ce qui fait que ça va plus vite chez eux ^^' Par contre, ce que tu peux faire, je pense, c'est juste faire ton syscall dans la boucle, ça sera plus rapide déjà. Et sinon, tu peux aussi éventuellement tout stocker dans un buffer, et tu print juste a la fin, comme ça tu limite le nombre de syscall.


RE: Le mystère de GNU yes - spin - 05-07-2012

galex-713 m'avait suggéré cette idée de ne mettre dans la boucle que le int, mais le problème c'est que l'exécution de int $80 modifie l'état des registres, il faut donc le remettre bien à chaque fois.

En ce qui concerne les buffers, j'ai essayé une solution en utilisant comme pointeur sur ma chaîne une donnée dans .data plutôt que directement la stack. Le résultat est le même, mon yes est infiniment plus lent que GNU yes. Cela dit, je n'ai pas compris grand chose au code de GNU (le C et moi...), mais après relecture plus attentive, je pense qu'il y a en effet une approche avec un buffer, comme tu le dis. À la ligne 84, on affiche argv[ i] et on pourrait supposer qu'il y a donc en effet un buffer contenant les 'y'. Mais je ne comprends pas comment le buffer peut être infini alors.

EDIT :
Après un coup de strace yes, je m'aperçois que GNU yes aussi affiche deux octets par deux octets ("y\n") en boucle :

Code :
write(1, "y\n", 2y
)                      = 2
write(1, "y\n", 2y
)                      = 2
write(1, "y\n", 2y
)                      = 2
write(1, "y\n", 2y
)                      = 2
write(1, "y\n", 2y
)                      = 2
write(1, "y\n", 2y
)                      = 2
write(1, "y\n", 2y
)                      = 2
...


L'output de mon yes, strace ./yes, est exactement le même (mise à part les trucs du début que je n'ai pas).


RE: Le mystère de GNU yes - ark - 06-07-2012

Ah, oui, logique que ça modifie les registres en fait... x)

Hum, c'est foireux, j'vais regarder la source en détails parce que y a pas de raisons qu'il aille plus vite...

[EDIT] Bon, ben j'ai regardé un peu plus en détails, et j'ai toujours pas compris comment il peux aller plus vite... Mais je pense que ça doit venir de certaines options de compilation, en fait faudrait look le code asm... j'vais voir ça !

[EDIT2] C'est trop abusé, j'ai rien compris x)


RE: Le mystère de GNU yes - gruik - 24-11-2012

je déterre un peu le topic, mea culpa, au pif comme ça je dirais que c'est int 80h qui est en cause, essaye avec sysenter Wink


RE: Le mystère de GNU yes - spin - 25-11-2012

Je suis désormais en 64 bits depuis la dernière fois où j'ai posté, et après quelques recherches j'ai cru comprendre que l'instruction SYSCALL était utilisé à la place du INT $80 ou du SYSENTER en 32 bits, je me trompe ?

Donc je vais essayer de ré-écrire yes en utilisant SYSCALL cette fois, et voir ce qu'il en est. Mais il me semble que je peux faire tourner des ELF32 même avec un système 64 bits, mais dans ce cas les comparaisons avec le GNU yes du système (qui lui est un ELF64 a priori) ne seront plus équitables. Le mieux serait que je me fasse une VM ou que j'aille squatter les Ubuntu du lycée qui sont en 32 bits, il me semble bien :p

Bref, merci de ta réponse, je vais tester ça !


RE: Le mystère de GNU yes - spin - 26-11-2012

Voici de nouveaux résultats, mais d'abord le code :

Code :
format ELF64 executable 3

segment readable executable  ; .text
entry $
@@:
    mov eax, 1
    mov rdi, 1 ; STDOUT
    mov rsi, msg
    mov rdx, 2
    syscall
    jmp @b

segment readable writable    ; .data
msg db 'y', $0a

Et les résultats :

Code :
$ timeout 3s yes > gnyes
$ timeout 3s ./yes > myyes
$ wc -l gnyes
178276352 gnyes
$ wc -l myyes
2047836 myyes

Ça devient suspect... Je ne sais plus quoi en penser.


RE: Le mystère de GNU yes - gruik - 26-11-2012

yep, j'ai fait quelques tests aussi de mon coté, au temps pour moi ce n'est pas int 80h VS sysenter/syscall qui est en cause, c'est write()...

si on fait un test :

gruik-write.c :
Code C :

#include <unistd.h>

int main (void)
{
        while (1) write(STDOUT_FILENO, "y\n", 2);
        return 0;
}
 


gruik-fwrite.c :
Code C :

#include <stdio.h>

int main (void)
{
        while (1) fwrite ("y\n", 2, 1, stdout);
        return 0;
}
 


et la diff entre les deux :
Code BASH :

$ timeout 3s ./gruik-write > log-write
$ timeout 3s ./gruik-fwrite > log-fwrite
$ wc -l log-*
164644864 log-fwrite
  3200165 log-write
 


bingo Wink
les miracles de la bufferisation

en bonus gruik-fwrite.asm pour la peine :
Code ASM :

; nasm -f elf gruik-fwrite.asm
; gcc gruik-fwrite.o -o gruik-fwrite

        extern fwrite
        extern stdout

        section .data
msg:    db "y", 10              ; 79h,0ah
len:    equ $ - msg

        section .text
        global main

main:
        ; fwrite (msg,len,1,stdout)
        mov eax, stdout
        mov eax, [eax]
        push eax
        push len
        push 1
        push msg
.loop:
        call fwrite
        jmp .loop       ; les parametres sont toujours là, on boucle directement