Domanda

Attualmente sto scrivendo un'applicazione C # che esegue molta elaborazione del segnale digitale, che comporta molte piccole operazioni di xfer di memoria perfezionate. Ho scritto queste routine usando puntatori non sicuri e sembrano funzionare molto meglio di quanto pensassi. Tuttavia, voglio che l'app sia il più veloce possibile.

Potrei trarre vantaggio dalle prestazioni riscrivendo queste routine in C o C ++ o dovrei attenermi a puntatori non sicuri? Mi piacerebbe sapere quali puntatori non sicuri portano sul tavolo in termini di prestazioni, rispetto a C / C ++.

EDIT: non sto facendo nulla di speciale all'interno di queste routine, ma solo le normali cose DSP: trasferimenti di dati compatibili con la cache da un array all'altro con molte moltiplicazioni, aggiunte, spostamenti di bit ecc. Mi aspetto che le routine C / C ++ sembrino più o meno le stesse (se non identiche) delle loro controparti C #.

EDIT: grazie mille a tutti per tutte le risposte intelligenti. Quello che ho imparato è che non otterrò alcun aumento significativo delle prestazioni solo facendo una porta diretta, a meno che non avvenga una sorta di ottimizzazione SSE. Supponendo che tutti i moderni compilatori C / C ++ possano trarne vantaggio, non vedo l'ora di provarlo. Se qualcuno è interessato ai risultati fammelo sapere e li posterò da qualche parte. (Potrebbe richiedere un po 'di tempo).

È stato utile?

Soluzione

In realtà ho fatto praticamente esattamente quello che mi stai chiedendo, solo in un'area di elaborazione delle immagini. Ho iniziato con i puntatori non sicuri di C #, poi sono passato a C ++ / CLI e ora codice tutto in C ++. E infatti, da lì sono passato dai puntatori in C ++ alle istruzioni del processore SSE, quindi sono andato fino in fondo. Non ho ancora raggiunto l'assemblatore, anche se non so se ne ho bisogno, ho visto un articolo su CodeProject che mostrava che SSE può essere veloce come l'assemblatore in linea, posso trovarlo se lo desideri.

Quello che è successo mentre andavo avanti è stato che il mio algoritmo è passato da circa 1,5-2 frame al secondo in C # con puntatori non sicuri, a 40 frame al secondo ora. C # e C ++ / CLI erano decisamente più lenti di C ++, anche con i puntatori, non sono stato in grado di superare i 10 frame al secondo con quelle lingue. Non appena sono passato al C ++, ho ricevuto all'istante qualcosa come 15-20 frame al secondo. Alcune modifiche più intelligenti e SSE mi ha portato fino a 40 frame al secondo. Quindi sì, vale la pena scendere se vuoi la velocità nella mia esperienza. C'è un chiaro miglioramento delle prestazioni.

Altri suggerimenti

Un altro modo per ottimizzare il codice DSP è renderlo compatibile con la cache. Se hai molti filtri da applicare al tuo segnale, dovresti applicare tutti i filtri a ciascun punto, vale a dire che il tuo ciclo più interno dovrebbe essere sopra i filtri e non sopra i dati, ad es .:

for each n do t´[n] = h(g(f(t[n])))

In questo modo eliminerai la cache molto meno e molto probabilmente otterrai un buon aumento di velocità.

Penso che dovresti scrivere le tue routine DSP in C ++ (gestito o non gestito) o in C #, usando un design solido ma senza cercare di ottimizzare tutto dall'inizio, quindi dovresti profilare il tuo codice e trovare i colli di bottiglia e provare per ottimizzare quelli via.

Cercando di produrre "ottimale" il codice dall'inizio ti distrarrà dalla scrittura del codice di lavoro in primo luogo. Ricorda che l'80% della tua ottimizzazione influenzerà solo il 20% del tuo codice poiché in molti casi solo il 10% del tuo codice è responsabile del 90% del tempo della tua CPU. (YMMV, in quanto dipende dal tipo di applicazione)

Quando stavo cercando di ottimizzare il nostro uso della fusione alfa nel nostro toolkit grafico, stavo cercando di usare SIMD il "bare metal" primo modo: assemblatore in linea. Presto ho scoperto che è meglio usare gli intrinseci SIMD su un assembly puro, poiché il compilatore è in grado di ottimizzare ulteriormente il C ++ leggibile con gli intrinseci riorganizzando i singoli codici operativi e massimizzando l'uso delle diverse unità di elaborazione nella CPU.

Non sottovalutare la potenza del tuo compilatore!

  

Vorrei ottenere qualche vantaggio in termini di prestazioni   dalla riscrittura di queste routine in C / C ++   o dovrei attenermi a puntatori non sicuri?

In teoria non importa - un compilatore perfetto ottimizzerà il codice, sia C che C ++, nel miglior assemblatore possibile.

In pratica, tuttavia, C è quasi sempre più veloce, specialmente per gli algoritmi di tipo puntatore: è il più vicino possibile al codice macchina senza codificare nell'assembly.

Il C ++ non porta nulla alla tabella in termini di prestazioni - è costruito come una versione orientata agli oggetti di C, con molte più capacità e facilità d'uso per il programmatore. Mentre per alcune cose funzionerà meglio perché una determinata applicazione trarrà beneficio da un punto di vista orientato agli oggetti, non era pensata per funzionare meglio, ma doveva fornire un altro livello di astrazione in modo che la programmazione di applicazioni complesse fosse più semplice.

Quindi, no, probabilmente non vedrai un aumento delle prestazioni passando a C ++.

Tuttavia, è probabilmente più importante per te scoprirlo, piuttosto che evitare di passare del tempo su di esso - penso che sarebbe un'attività utile portarlo e analizzarlo. È del tutto possibile che se il tuo processore ha determinate istruzioni per l'uso di C ++ o Java, e il compilatore ne è a conoscenza, potrebbe essere in grado di sfruttare le funzionalità non disponibili in C. Improbabile, ma possibile.

Tuttavia, i processori DSP sono notoriamente complessi, e più ci si avvicina all'assemblaggio, migliori sono le prestazioni che si ottengono (ovvero, più il codice è ottimizzato a mano). C è molto più vicino all'assemblaggio di C ++.

-Adam

Prima lasciami rispondere alla domanda su " sicuro " vs "non sicuro": hai detto nel tuo post "Voglio che l'app sia il più veloce possibile" e ciò significa che non vuoi fare confusione con " sicuro " o " gestito " puntatori (non menzionare nemmeno la garbage collection).

Per quanto riguarda la scelta della lingua: C / C ++ ti consente di lavorare con i dati sottostanti molto più facilmente senza alcun sovraccarico associato ai contenitori di fantasia che tutti stanno utilizzando in questi giorni. Sì, è bello essere coccolati da contenitori che ti impediscono di seg-fault ... ma il livello più alto di astrazione associato ai contenitori ROVINE delle tue prestazioni.

Nel mio lavoro il nostro codice deve essere veloce. Un esempio sono i nostri ricampionatori polifase al lavoro che giocano con puntatori e operazioni di mascheramento e filtro DSP a punto fisso ... nessuno di questi trucchi intelligenti è davvero possibile senza un controllo a basso livello della memoria e manipolazioni di bit == > quindi dico stick con C / C ++.

Se vuoi davvero essere intelligente, scrivi tutto il tuo codice DSP a basso livello C. E poi mescolalo con i contenitori più sicuri / puntatori gestiti ... quando arriva la velocità devi togliere le ruote di allenamento .. ti rallentano troppo.

(Cordiali saluti, per quanto riguarda il decollo delle ruote di addestramento: è necessario testare il codice DSP C extra offline per assicurarsi che il loro utilizzo del puntatore sia buono ... o / w sarà seg.)

EDIT: p.s. "errore di seg" è un LUSSO per tutti gli sviluppatori PC / x86. Quando stai scrivendo un codice incorporato ... un errore seg significa semplicemente che il tuo processore entrerà nelle wuides e sarà recuperato solo dal power cycling;).

Per sapere come ottenere un guadagno in termini di prestazioni, è bene conoscere le parti di codice che potrebbero causare colli di bottiglia.

Dato che stai parlando di piccoli trasferimenti di memoria, presumo che tutti i dati si adattino alla cache della CPU. In tal caso, l'unico guadagno che puoi ottenere sarebbe sapere come far funzionare i componenti intrinseci della CPU. In genere, il compilatore che ha più familiarità con i valori intrinseci della CPU è un compilatore C. Quindi qui, penso che potresti migliorare le prestazioni eseguendo il porting.

Un altro collo di bottiglia sarà sul percorso tra CPU e memoria: mancati errori di cache a causa dell'elevato numero di trasferimenti di memoria nell'applicazione. Il vantaggio maggiore risiederà quindi nel ridurre al minimo i mancati cache, che dipendono dalla piattaforma utilizzata e dal layout dei dati (sono locali o diffusi nella memoria?).

Ma poiché stai già utilizzando puntatori non sicuri, hai quel bit sotto il tuo controllo, quindi la mia ipotesi è: su questo aspetto, non trarrai alcun vantaggio da una porta a C (o C ++).

Concludendo: potresti voler trasferire piccole porzioni della tua applicazione in C.

Visto che stai già scrivendo in un codice non sicuro, presumo che sarebbe relativamente facile convertirlo in una DLL Cll e chiamarli da C #. Fallo dopo aver identificato le parti più lente del tuo programma e poi sostituiscile con C.

La tua domanda è in gran parte filosofica. La risposta è questa: non ottimizzare fino al tuo profilo.

Chiedi se otterrai miglioramenti. Bene, otterrai un miglioramento dell'N percento. Se è abbastanza (come se avessi bisogno di un codice che viene eseguito 200 volte in 20 millisecondi su alcuni sistemi integrati), stai bene. E se non fosse abbastanza?

Devi prima misurare e poi scoprire se alcune parti del codice possono essere riscritte nella stessa lingua ma più velocemente. Forse puoi riprogettare le strutture di dati per evitare calcoli non necessari. Forse puoi saltare un po 'di riallocazione della memoria. Forse qualcosa viene fatto con complessità quadratica quando potrebbe essere fatto con complessità lineare. Non lo vedrai fino a quando non lo avrai misurato. Di solito si tratta di una perdita di tempo molto inferiore rispetto alla semplice riscrittura di tutto in un'altra lingua.

C # non ha supporto per SSE (tuttavia esiste un progetto mono per le operazioni SSE). Pertanto C / C ++ con SSE sarebbe sicuramente più veloce.

È necessario, tuttavia, prestare attenzione alle transizioni da gestite a native e da native a gestite, in quanto sono piuttosto costose. Resta il più a lungo possibile in entrambi i mondi.

Vuoi davvero che l'app sia il più veloce possibile o semplicemente abbastanza veloce? Questo ti dice cosa dovresti fare dopo.

Se insisti a rimanere con il tuo rullo manuale, senza l'ottimizzazione manuale in assemblatore o simile, il C # dovrebbe andare bene. Sfortunatamente, questo è il tipo di domanda a cui si può veramente rispondere solo sperimentalmente. Sei già nello spazio puntatore non gestito, quindi la mia sensazione è che una porta diretta su C ++ non vedrebbe una differenza significativa nella velocità.

Dovrei dire, però, che ho avuto un problema simile di recente, e abbiamo finito per buttare via il rotolo dopo aver provato libreria Intel Integrated Performance Primitives . I miglioramenti delle prestazioni che abbiamo visto sono stati davvero impressionanti.

Mono 2.2 ora ha SIMD con questo puoi avere il meglio di entrambi i mondi gestiti codice e velocità raw.

Potresti anche dare un'occhiata a L'uso di SSE in c # è possibile?

Suggerirei che se hai degli algoritmi nel tuo codice DSP che necessitano di essere ottimizzati, dovresti davvero scriverli in assembly, non in C o C ++.

In generale, con processori e hardware moderni, non ci sono molti scenari che richiedono o giustificano lo sforzo di ottimizzazione. Hai effettivamente identificato problemi di prestazioni? In caso contrario, probabilmente è meglio attenersi a ciò che hai. È improbabile che C # non sicuro sia significativamente più lento di C / C ++ nella maggior parte dei casi di aritmetica semplice.

Hai considerato C ++ / CLI? Allora potresti avere il meglio dei due mondi. Ti consentirebbe persino di utilizzare l'assemblatore in linea, se necessario.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top