Visualizzazione dei risultati da 1 a 10 su 13

Discussione: Semi tecnico :p

Visualizzazione discussione

  1. #3
    Quote Originariamente inviata da SkyLinx Visualizza il messaggio
    In un progetto per il quale mi hanno chiesto aiuto, la test suite completa impiegava piu' di venti minuti - non e' la suite piu' grande su cui ho lavorato ma e' cmq abbastanza grande.

    Dopo 2 giorni di lavoro, adesso impiega 1'40" circa

    Avete qualche esempio di casi in cui avete ottenuto guadagni notevoli in poco tempo? Sono curioso
    Calcolo di area di poligoni originariamente in Montecarlo, riscritto in maniera esatta, credo sia passato a un decimo del tempo.

    Algoritmo di espansione di aree su immagine, scritto in maniera cretina con astrazioni e allocazioni inutili ci metteva tipo un secondo e mezzo, riscritto a modino 200 ms o qualcosa del genere (con tra l'altro una feature di conservazione delle aree piccole ma significative che mancava all'altro).

    Parsing di un formato vagamente tipo ISO in C++; lettura malamente bufferizzata di una riga alla volta, ogni volta allocata dinamicamente ex-novo (buffer fuori dal ciclo), con una trim (due allocazioni inutili) e diverse substr solo per fare confronti di inizio stringa (peraltro in if in sequenza invece che in else if). Sistemato il buffering, spostata la stringa fuori dal loop (con clear ogni volta, ma senza effettivamente liberare la memoria), uccise tutte le substr, trim inplace, da una decina di allocazioni inutili nel loop a zero. È passato da circa 8 secondi (su un caso di test) a un centinaio di ms.

    Dump di file testuale lento come lo schifo. Andando di profiler salta fuori che il collo di bottiglia è la fprintf, che perde una marea di tempo a fare delle getenv.
    Long story short, fino ad una certa versione di MinGW non sapevano decidersi se la printf con il %f di default dovesse avere non so se 3 o 4 decimali, perché di default mi sembra glibc aveva 4 mentre il runtime Microsoft ne aveva 3 e quindi hanno fatto che si poteva andare in override con una variabile d'ambiente.
    La cosa, già discutibile di per sé, è resa disastrosa dal fatto che questa variabile d'ambiente viene riletta ad ogni chiamata (cosa abbastanza senza senso, visto che di norma le variabili d'ambiente non cambiano dopo l'avvio del processo), e la getenv è una funzione mediamente lenta rispetto a printf "semplici", dato che tutte le volte ri-effettua una ricerca lineare dentro il blocco dell'environment del processo (senza mai trovare la stringa che cerca, quindi caso pessimo). Morale della cosa, le printf in media erano 8 volte più lente del dovuto.

    Nei MinGW successivi avevano risolto la cosa cachando il risultato di questo lookup, ma cambiare il compilatore (e ricompilare una montagna di librerie tra cui Qt) due minuti prima di un rilascio era lungo e rischioso. Ergo: ho fatto una patch binaria alla libreria standard.
    La funzione in causa è __mingw_pformat in libmingwex.a, in particolare a 0x1699:
    codice:
        1687:	31 c0                	xor    eax,eax
        1689:	66 89 44 24 50       	mov    WORD PTR [esp+0x50],ax
        168e:	8b 84 24 88 00 00 00 	mov    eax,DWORD PTR [esp+0x88]
        1695:	89 44 24 58          	mov    DWORD PTR [esp+0x58],eax
        1699:	e8 00 00 00 00       	call   169e <___mingw_pformat+0x8e>
        169e:	85 c0                	test   eax,eax
        16a0:	74 10                	je     16b2 <___mingw_pformat+0xa2>
        16a2:	0f be 10             	movsx  edx,BYTE PTR [eax]
        16a5:	b8 02 00 00 00       	mov    eax,0x2
        16aa:	83 ea 30             	sub    edx,0x30
        16ad:	83 fa 02             	cmp    edx,0x2
        16b0:	76 0d                	jbe    16bf <___mingw_pformat+0xaf>
        16b2:	e8 00 00 00 00       	call   16b7 <___mingw_pformat+0xa7>
        16b7:	83 e0 01             	and    eax,0x1
    La call in questione nella libreria ha come target l'offset 0, ma è un placeholder che viene riempito dal linker con l'indirizzo del trampolino per la getenv (motivo per cui non si può semplicemente asfaltare con dei nop, il linker li sovrascriverebbe).

    La modifica effettuata è:
    codice:
        1687:	31 c0                	xor    eax,eax
        1689:	66 89 44 24 50       	mov    WORD PTR [esp+0x50],ax
        168e:	8b 84 24 88 00 00 00 	mov    eax,DWORD PTR [esp+0x88]
        1695:	89 44 24 58          	mov    DWORD PTR [esp+0x58],eax
        1699:	b8 00 00 00 00       	mov    eax,0x0
        169e:	85 c0                	test   eax,eax
        16a0:	75 10                	jne    16b2 <___mingw_pformat+0xa2>
        16a2:	0f be 10             	movsx  edx,BYTE PTR [eax]
        16a5:	b8 02 00 00 00       	mov    eax,0x2
        16aa:	83 ea 30             	sub    edx,0x30
        16ad:	83 fa 02             	cmp    edx,0x2
        16b0:	76 0d                	jbe    16bf <___mingw_pformat+0xaf>
        16b2:	e8 00 00 00 00       	call   16b7 <___mingw_pformat+0xa7>
        16b7:	83 e0 01             	and    eax,0x1
    La call a 0x1699 diventa una mov (per cui in eax va a finire l'indirizzo del trampolino della getenv, sicuramente diverso da zero); la je a 0x16a0 invece diventa una jne. In questa maniera, attiviamo sempre il percorso di codice relativo al caso in cui getenv restituisce NULL (che è quello che di fatto verrebbe attivato sempre). L'unica differenza è che in questo caso eax resta diverso da zero, ma viene immediatamente sovrascritto dalla call a 0x16b2 (a cui punta la jne), quindi non è un problema.
    Cambiando tre bit nella libreria statica il tempo di dump è passato da un 30 secondi a 5.
    Ultima modifica di MItaly; 20-10-2017 a 00:57
    Amaro C++, il gusto pieno dell'undefined behavior.

Permessi di invio

  • Non puoi inserire discussioni
  • Non puoi inserire repliche
  • Non puoi inserire allegati
  • Non puoi modificare i tuoi messaggi
  •  
Powered by vBulletin® Version 4.2.1
Copyright © 2025 vBulletin Solutions, Inc. All rights reserved.