SOFTWARE > Linguaggi di programmazione e scripting
Compilatori vs. Uomo
dsar:
.
cdimauro:
--- Citazione da: "Z80Fan" ---programmazione amichevole
--- Termina citazione ---
e assembly non vanno d'accordo.
Per il resto l'assembly è meglio non usarlo, come giustamente osservava dsar, se non in particolarissimi casi.
Oggi i compilatori fanno veramente un ottimo lavoro. Soltanto alcune volte ho sentito la necessità di riscrivere il codice prodotto in assembly, perché quello generato non mi soddisfaceva.
Ad esempio, ecco il codice generato dal compilatore (Visual Studio C++ 2008):
--- Codice: ---static int long_compare(PyLongObject *a, PyLongObject *b)
{ Py_ssize_t sign;
Py_ssize_t i = Py_SIZE(a), j = Py_SIZE(b);
push ebx
mov ebx,dword ptr [edx+8]
push ebp
push esi
mov esi,dword ptr [edi+8]
mov eax,ebx
if (i != j)
cmp eax,esi
je @else
sign = (FAST_MSD(a, i) < 0 ? -i : i) - (FAST_MSD(b, j) < 0 ? -j : j);
cmp dword ptr [edx+eax*4+8],0
jge @a_pos
neg eax
@a_pos:
cmp dword ptr [edi+esi*4+8],0
jge @b_pos
neg esi
@b_pos:
sub eax,esi
mov ecx,eax
jmp @compute_sign
else {
--- Termina codice ---
e quello che ho riscritto a mano:
--- Codice: ---static int long_compare(PyLongObject *a, PyLongObject *b)
{
Py_ssize_t sign;
Py_ssize_t i = Py_SIZE(a), j = Py_SIZE(b);
mov eax,dword ptr [esi+8]
mov ecx,dword ptr [edi+8]
if (i != j)
cmp eax,ecx
je @else
sign = (FAST_MSD(a, i) < 0 ? -i : i) - (FAST_MSD(b, j) < 0 ? -j : j);
cmp dword ptr [esi+eax*4+8],0
jge @a_pos
neg eax
@a_pos:
cmp dword ptr [edi+ecx*4+8],0
jge @b_pos
neg ecx
@b_pos:
sub eax,ecx
jmp @compute_sign
else {
--- Termina codice ---
Fanno parte di alcune slide che non ho incluso nella presentazione dei talk che ho tenuto al recente EuroPython, per alleggerire la discussione.
Come potete vedere l'allocazione e l'uso di registri manuali risulta migliore. Per il resto ci sono stati un paio di casi in cui sono rimasto sorpreso dalla qualità del codice prodotto, superiore alle mie soluzioni (non me lo sarei mai aspettato, anche se sono anni ormai che non scrivo codice assembly).
cdimauro:
--- Citazione da: "dsar" ---
--- Citazione da: "cdimauro" ---Come potete vedere l'allocazione e l'uso di registri manuali risulta migliore.
--- Termina citazione ---
Posso muovere una critica? :-) Mi permetto di farlo perché i compilatori sono il mio campo (anche se hobbistico).
--- Termina citazione ---
Certamente, ci mancherebbe. ;)
--- Citazione ---Ovviamente è corretto il codice di msvc 2008, prima di operare sui registri si fa sempre il backup sullo stack. Pushare e poppare dallo stack è un'operazione lenta, motivo per cui la chiamata di una procedura ha un certo overhead (tutti i registri vengono backuppati nello stack).
Considera che il compilatore ha sempre una variabile riservata per registro, viene assegnato con il liveness analysis (le variabili più utilizzate), ci sono alcuni casi in cui serve più velocità, quindi prende in prestito un registro da una variabile backuppandola nello stack, e dopo lo ripoppa nel registro.
Quindi non puoi fare un mov su un registro senza prima salvare il contenuto, dipende cosa avviene dopo quel pezzo di codice, l'eseguibile potrebbe avere comportamenti indeterministici.
--- Termina citazione ---
In realtà alcuni registri (come eax, ecx, edx) sono liberi (e liberamente utilizzabili) già in partenza, per cui non è necessario farne il backup e si possono utilizzare immediatamente.
Mostro un esempio a riguardo:
--- Codice: ---static int long_compare(PyLongObject *a, PyLongObject *b)
{
Py_ssize_t sign;
if (Py_SIZE(a) != Py_SIZE(b)) {
mov edx,dword ptr [ebx+8]
mov eax,dword ptr [esi+8]
push ebp
push edi
cmp edx,eax
je @else
sign = Py_SIZE(a) - Py_SIZE(b);
sub edx,eax
mov ecx,edx
jmp @compute_sign
}
else {
--- Termina codice ---
Questa funzione è quella usata correntemente da CPython (quelle precedenti riguardano la nuova implementazione che ho proposto) e il codice è quello generato dal solito VS 2008.
Come vedi eax ed edx vengono utilizzati immediatamente senza farne il backup; successivamente il compilatore fa il push di ebp ed edi perché probabilmente gli serviranno dopo, anche se io avrei aspettato il risultato del confronto, perché nel caso comune & semplice si deve soltanto calcolare il segno (-1, 0 o 1) e restituirne il valore (quindi ebp ed edi non vengono utilizzati).
Quest'esempio mostra, a mio avviso, come l'algoritmo di register allocation potrebbe essere ulteriormente migliorato da Microsoft (magari l'avranno già fatto nella 2010, ma io sono costretto a usare la 2008 finché gli sviluppatori di Python non decideranno di aggiornare il progetto) procedendo al backup (e successivo restore) dei registri soltanto nel ramo dell'else (quello più complicato).
--- Citazione ---Se vuoi fare in modo che il compilatore usi direttamente i registri dovresti fare una funzione inline (o macro, se è in C). Se le variabili che compari vengono usate abbastanza spesso ovviamente in quel punto te le ritrovi già nei registri e il compilatore fa il folding del codice (essendo inline/macro).
--- Termina citazione ---
Purtroppo quello è codice C (CPython non utilizza C++) per cui non si può utilizzare l'inline (che, comunque, ormai un compilatore può benissimo ignorare perché può decidere che non ne vale la pena; d'altra parte è soltanto un'indicazione).
Inoltre, anche se funzione è definita come static, viene comunque referenziata all'esterno di quel modulo tramite una tabella pubblica che ne contiene un puntatore (in pratica si simula la programmazione a oggetti), per cui comunque il compilatore sarebbe costretto a evitare l'inline e a generarne una copia.
Z80Fan:
Mi inserisco un attimo in questa interessante discussione. :)
--- Citazione da: "cdimauro" ---e assembly non vanno d'accordo.
--- Termina citazione ---
Ovviamente parlavo di "programmazione amichevole" per un programmatore Assembly, non di "programmazione amichevole" in generale.
Poi sappiamo tutti che non c'è nulla di amichevole nell'x86 ma quello è un altro discorso! :mrgreen:
--- Citazione da: "cdimauro" ---Inoltre, anche se funzione è definita come static, viene comunque referenziata all'esterno di quel modulo tramite una tabella pubblica che ne contiene un puntatore (in pratica si simula la programmazione a oggetti), per cui comunque il compilatore sarebbe costretto a evitare l'inline e a generarne una copia.
--- Termina citazione ---
Una volta, giocando con l'output di GCC (v. 4+) ho notato che piccole funzioni venivano messe inline, ma veniva cmq generata una copia a se stante; non era stato specificato nulla di particolare nel codice, solo -O2 (o O3 non ricordo) alla linea di comando.
TheKaneB:
--- Citazione da: "Z80Fan" ---Mi inserisco un attimo in questa interessante discussione. :)
--- Citazione da: "cdimauro" ---e assembly non vanno d'accordo.
--- Termina citazione ---
Ovviamente parlavo di "programmazione amichevole" per un programmatore Assembly, non di "programmazione amichevole" in generale.
Poi sappiamo tutti che non c'è nulla di amichevole nell'x86 ma quello è un altro discorso! :mrgreen:
--- Citazione da: "cdimauro" ---Inoltre, anche se funzione è definita come static, viene comunque referenziata all'esterno di quel modulo tramite una tabella pubblica che ne contiene un puntatore (in pratica si simula la programmazione a oggetti), per cui comunque il compilatore sarebbe costretto a evitare l'inline e a generarne una copia.
--- Termina citazione ---
Una volta, giocando con l'output di GCC (v. 4+) ho notato che piccole funzioni venivano messe inline, ma veniva cmq generata una copia a se stante; non era stato specificato nulla di particolare nel codice, solo -O2 (o O3 non ricordo) alla linea di comando.
--- Termina citazione ---
La copia della funzione verrebbe comunque strippata in fase di linking, a meno che tu non usi un puntatore a quella funzione (quindi il puntatore a funzione deve spuntare da qualche parte del codice come RValue).
Navigazione
[0] Indice dei post
Vai alla versione completa