Sto ottimizzando prematuramente?


9

Sono attualmente in fase di progettazione di un'architettura basata su componenti in C ++.

Il mio progetto attuale include l'uso di funzionalità come:

  • std::vectors di std::shared_ptrs per contenere i componenti
  • std::dynamic_pointer_cast
  • std::unordered_map<std::string,[yada]>

I componenti rappresenteranno i dati e la logica di vari elementi necessari in un software simile a un gioco, come grafica, fisica, intelligenza artificiale, audio, ecc.

Ho letto dappertutto che le mancate prestazioni della cache sono difficili per le prestazioni, quindi ho eseguito alcuni test, il che mi ha portato a credere che, in effetti, può rallentare un'applicazione.

Non sono stato in grado di testare le funzioni linguistiche sopra menzionate, ma in molti posti si dice che queste tendono a costare molto e dovrebbero essere evitate se possibile.

Dal momento che sono in fase di progettazione dell'architettura e questi saranno inclusi nel nocciolo della progettazione, dovrei provare a trovare modi per evitarli ora, poiché sarà molto difficile cambiarlo in seguito se ci sono prestazioni problemi?

O sono solo preso nel fare l'ottimizzazione prematura?


3
Sarei molto riluttante ad accontentarmi di un progetto che ha reso molto difficile cambiare in seguito, indipendentemente dai problemi di prestazioni. Evitalo se puoi. Esistono molti design flessibili e veloci.
candied_orange,

1
Senza nemmeno conoscere i dettagli, la risposta a questa domanda è quasi sempre un clamoroso "SÌ !!".
Mawg dice di ripristinare Monica il

2
@Mawg "... Eppure non dovremmo rinunciare alle nostre opportunità in quel 3% critico." Dato che questo è il nocciolo del design, come potrei sapere se sto lavorando su questo 3%?
Vaillancourt,

1
Un eccellente punto, Alexandre (+1), e, sì, so che l'ultima metà della citazione, che non viene quasi mai menzionata :-) Ma, per tornare al mio commento prima di quello (che si riflette nella risposta accettata) , the answer to this question is almost always a resounding "YES !!". Sento ancora che è meglio farlo funzionare prima e ottimizzare in seguito, ma YMMV, ognuno ha la sua opinione, tutte valide, e solo l'OP può davvero rispondere alla sua domanda soggettiva.
Mawg dice di ripristinare Monica il

1
@AlexandreVaillancourt Continua a leggere il documento di Knuth (PDF, la citazione proviene dal lato destro della pagina etichettata 268, pagina 8 in un lettore PDF). "... sarà saggio guardare attentamente il codice critico; ma solo dopo che questo codice è stato identificato. Spesso è un errore dare giudizi a priori su quali parti di un programma sono veramente critiche, poiché l'esperienza universale di i programmatori che hanno utilizzato strumenti di misurazione hanno fallito le loro ipotesi intuitive ". (enfatizza la sua)
8bittree

Risposte:


26

Senza leggere altro che il titolo: Sì.

Dopo aver letto il testo: Sì. Anche se è vero che le mappe, i puntatori condivisi ecc. Non funzionano bene nella cache, troverai sicuramente che ciò per cui vuoi usarli - per quanto ho capito - non è il collo di bottiglia e non verrà trattenuto o utilizzare la cache in modo efficiente indipendentemente dalla struttura dei dati.

Scrivi il software evitando gli errori più stupidi, quindi prova, quindi trova i colli di bottiglia, quindi ottimizza!

Fwiw: https://xkcd.com/1691/


3
Concordato. Fallo funzionare prima, perché non importa quanto velocemente non riesce a funzionare. E ricorda sempre che le ottimizzazioni più efficaci non implicano l'ottimizzazione del codice, implicano la ricerca di un algoritmo diverso, più efficiente.
Todd Knarr,

10
Vorrei sottolineare che la prima riga non è vera perché l'ottimizzazione è sempre prematura, ma piuttosto perché l'ottimizzazione non è prematura solo se sai di averne bisogno, nel qual caso non te lo chiederesti. Quindi, la prima riga è vera solo perché il fatto stesso che stai ponendo una domanda se l'ottimizzazione è prematura significa che non sei sicuro di aver bisogno di ottimizzazione, che per definizione la rende prematura. Uff.
Jörg W Mittag,

@ JörgWMittag: concordato.
Steffen,

3

Non ho familiarità con C ++, ma in generale dipende.

Non è necessario ottimizzare prematuramente gli algoritmi isolati in cui è possibile ottimizzare facilmente quando si tratta di questo.

Tuttavia, è necessario ottenere una progettazione generale dell'applicazione per ottenere gli indicatori di prestazioni chiave desiderati.

Ad esempio, se è necessario progettare un'applicazione per soddisfare milioni di richieste al secondo, è necessario considerare la scalabilità dell'applicazione durante la progettazione, anziché far funzionare l'applicazione.


3

Se devi chiedere, allora sì. Ottimizzazione prematura significa ottimizzazione prima di essere certi che ci sia un problema di prestazioni significative.


1

ECS? In realtà suggerirò che potrebbe non essere prematuro se è così per riflettere molto sul lato orientato ai dati del design e confrontare diverse ripetizioni perché potrebbe avere un impatto sui progetti dell'interfaccia e quest'ultimo è molto costoso cambiare in ritardo il gioco. Inoltre, ECS richiede solo molto lavoro e pensato in anticipo e penso che valga la pena utilizzare un po 'di quel tempo per essere sicuro che non ti darà un peggioramento delle prestazioni a livello di progetto più in basso, dato come sarà al centro del tuo intero motore pazzesco. Questa parte mi abbaglia:

unordered_map<string,[yada]>

Anche con ottimizzazioni di stringhe di piccole dimensioni, è presente un contenitore di dimensioni variabili (stringhe) all'interno di un altro contenitore di dimensioni variabili (unordered_maps). In realtà, le piccole ottimizzazioni stringa potrebbe essere in realtà nocivo come utile in questo caso, se il tavolo è molto scarsa, dal momento che la piccola ottimizzazione stringa implicherebbe che ogni indice non utilizzata della tabella hash sarà ancora possibile utilizzare più memoria per l'ottimizzazione SS ( sizeof(string)sarebbe essere molto più grande) al punto in cui il sovraccarico di memoria totale della tua tabella hash potrebbe costare di più di qualsiasi cosa tu stia memorizzando in esso, specialmente se si tratta di un componente semplice come un componente di posizione, oltre a incorrere in più mancate cache con il passo falso per passare da una voce nella tabella hash alla successiva.

Suppongo che la stringa sia una specie di chiave, come un ID componente. In tal caso, ciò rende già le cose notevolmente più economiche:

unordered_map<int,[yada]>

... se vuoi i vantaggi di poter avere nomi facili da usare che gli script possono usare, ad esempio, le stringhe internate possono darti il ​​meglio di entrambi i mondi qui.

Detto questo, se riesci a mappare la stringa su un intervallo ragionevolmente basso di indici densamente usati, potresti essere in grado di farlo:

vector<[yada]> // the index and key become one and the same

Il motivo per cui non considero questo prematuro è perché, di nuovo, potrebbe influire sulla progettazione dell'interfaccia. Il punto di DOD non dovrebbe essere quello di cercare di fornire le rappresentazioni dei dati più efficienti che si possano immaginare in una volta IMO (che dovrebbero essere raggiunte in modo iterativo secondo necessità), ma di pensarci abbastanza per progettare interfacce in cima per lavorare con quello dati che ti lasciano abbastanza spazio per il profilo e l'ottimizzazione senza cambiamenti a cascata del design.

Ad esempio ingenuo, un software di elaborazione video che abbina tutto il suo codice a questo:

// Abstract pixel that could be concretely represented by
// RGB, BGR, RGBA, BGRA, 1-bit channels, 8-bit channels, 
// 16-bit channels, 32-bit channels, grayscale, monochrome, 
// etc. pixels.
class IPixel
{
public:
    virtual ~IPixel() {}
    ...
};

Non andrà lontano senza una riscrittura potenzialmente epica, poiché l'idea di astrarre a livello di singolo pixel è già estremamente inefficiente (lo vptrstesso costa spesso più memoria dell'intero pixel) rispetto all'astrazione a livello di immagine (che rappresentano spesso milioni di pixel). Quindi metti abbastanza in anticipo le tue rappresentazioni dei dati in modo da non dover affrontare uno scenario da incubo, e idealmente non più, ma qui penso che valga la pena pensare a questa roba in anticipo poiché non vuoi costruire un motore intricato intorno al tuo ECS e scopri che l'ECS stesso è il collo di bottiglia in modi che richiedono di cambiare le cose a livello di progettazione.

Per quanto riguarda gli errori della cache ECS, a mio avviso gli sviluppatori spesso si sforzano troppo per rendere la loro cache ECS compatibile. Comincia a dare troppo poco botto per il dollaro per provare ad accedere a tutti i tuoi componenti in modo perfettamente contiguo, e spesso implica la copia e lo shuffle dei dati ovunque. Di solito è abbastanza buono, per esempio, semplicemente radicare gli indici dei componenti prima di accedervi in ​​modo da accedervi in ​​un modo in cui almeno non si sta caricando una regione di memoria in una linea di cache, solo per sfrattarla e quindi caricare tutto di nuovo nello stesso loop solo per accedere a una parte diversa della stessa linea di cache. E un ECS non deve fornire un'efficienza straordinaria su tutta la linea. Non è che un sistema di input ne tragga vantaggio tanto quanto una fisica o un sistema di rendering, quindi consiglio di puntare a "bene" efficienza su tutta la linea e "eccellente" proprio nei luoghi in cui ne hai davvero bisogno. Detto questo, l'uso diunordered_mape stringqui sono abbastanza facili da evitare.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.