L'orientamento agli oggetti influenza davvero le prestazioni dell'algoritmo?


14

L'orientamento agli oggetti mi ha aiutato molto nell'implementazione di molti algoritmi. Tuttavia, i linguaggi orientati agli oggetti a volte ti guidano in un approccio "diretto" e dubito che questo approccio sia sempre positivo.

OO è davvero utile nella codifica degli algoritmi in modo rapido e semplice. Ma questo OOP potrebbe essere uno svantaggio per il software basato sulle prestazioni, ovvero quanto velocemente esegue il programma?

Ad esempio, la memorizzazione di nodi grafici in una struttura di dati sembra in primo luogo "semplice", ma se gli oggetti Node contengono molti attributi e metodi, ciò potrebbe portare a un algoritmo lento?

In altre parole, molti riferimenti tra molti oggetti diversi o l'utilizzo di molti metodi di molte classi potrebbero comportare un'implementazione "pesante"?


1
Piuttosto una domanda strana. Posso capire come OOP aiuta a livello di architettura. Ma un livello di implementazione degli algoritmi è normalmente costruito su astrazioni che sono molto estranee a tutto ciò che OOP rappresenta. Quindi, è probabile che le prestazioni non siano il problema più grande per le implementazioni degli algoritmi OOP. Per quanto riguarda le prestazioni, con OOP il singolo maggiore collo di bottiglia è normalmente correlato alle chiamate virtuali.
SK-logic,

@ SK-logic> l'orientamento agli oggetti tende a manipolare tutto con il puntatore, il che implica un carico di lavoro più importante dal lato dell'allocazione della memoria, e i dati non localizzati tendono a non trovarsi nella cache della CPU e, ultimo ma non meno importante, implicano molti indiretti diramazione (funzioni virtuali) che è mortale per la pipeline della CPU. OO è una buona cosa, ma può certamente avere un costo in alcuni casi.
deadalnix,

Se i nodi nel tuo grafico hanno cento attributi, avrai bisogno di spazio per memorizzarli indipendentemente dal paradigma utilizzato per l'implementazione effettiva, e non vedo come ogni singolo paradigma abbia un vantaggio su questo in generale. @deadalnix: Forse i factore costanti possono essere peggiori a causa di alcune ottimizzazioni più difficili. Ma nota che dico più difficile , non impossibile - ad esempio, PyPy può decomprimere gli oggetti in loop stretti e le JVM hanno integrato le chiamate di funzione virtuali da sempre.

Python è buono per gli algoritmi di prototipazione, eppure spesso non è necessaria una classe quando si implementa un algoritmo tipico in esso.
Giobbe

1
+1 Per mettere in relazione l'orientamento agli oggetti con gli algoritmi, qualcosa che viene trascurato in questi giorni, sia nell'industria del software che nell'accademia ...
Umlcat,

Risposte:


16

L'orientamento agli oggetti può impedire determinate ottimizzazioni algoritmiche, a causa dell'incapsulamento. Due algoritmi possono funzionare particolarmente bene insieme, ma se sono nascosti dietro le interfacce OO, si perde la possibilità di utilizzare la loro sinergia.

Guarda le biblioteche numeriche. Molti di loro (non solo quelli scritti negli anni '60 o '70) non sono OOP. C'è una ragione per questo: gli algoritmi numerici funzionano meglio come un insieme di disaccoppiamenti modulespiuttosto che come gerarchie OO con interfacce e incapsulamento.


2
La ragione principale di ciò è che solo C ++ è riuscito a utilizzare i modelli di espressione per rendere la versione OO altrettanto efficiente.
DeadMG

4
Guarda le moderne librerie C ++ (STL, Boost) - non sono nemmeno OOP. E non solo per le prestazioni. Gli algoritmi non potevano normalmente essere ben rappresentati in uno stile OOP. Cose come la programmazione generica sono molto più adatte agli algoritmi di basso livello.
SK-logic,

3
Wha-wha-cosa? Immagino di venire da un pianeta diverso da quant_dev e dalla logica SK. No, un universo diverso. Con diverse leggi della fisica e tutto il resto.
Mike Nakis,

5
@MikeNakis: la differenza nel punto di vista sta nel (1) se un certo pezzo di codice computazionale può trarre beneficio in termini di leggibilità umana dall'OOP (che non sono ricette numeriche); (2) se il design della classe OOP si allinea con la struttura e l'algoritmo dei dati ottimali (vedi la mia risposta); e (3) se ogni livello di riferimento indiretto fornisce "valore" sufficiente (in termini di lavoro svolto per chiamata di funzione o chiarezza concettuale per livello) giustifica il sovraccarico (a causa di riferimento indiretto, chiamata di funzione, livelli o copia dei dati). (4) Infine, la sofisticazione del compilatore / JIT / ottimizzatore è un fattore limitante.
rwong

2
@MikeNakis, che vuoi dire? Pensi che STL sia una libreria OOP? La programmazione generica non va comunque bene con OOP. E inutile menzionare che OOP è un framework troppo ristretto, adatto solo per pochissimi compiti pratici, estraneo a qualsiasi altra cosa.
SK-logic,

9

Cosa determina le prestazioni?

I fondamenti: strutture dati, algoritmi, architettura informatica, hardware. Più spese generali.

Un programma OOP può essere progettato per allinearsi esattamente con la scelta di strutture dati e algoritmi ritenuti ottimali dalla teoria CS. Avrà le stesse caratteristiche prestazionali del programma ottimale, oltre ad alcune spese generali. L'overhead di solito può essere ridotto al minimo.

Tuttavia, un programma inizialmente progettato solo con preoccupazioni OOP, senza riguardo ai fondamenti, potrebbe inizialmente non essere ottimale. La subottimalità è talvolta rimovibile dal refactoring; a volte non lo è - richiede una riscrittura completa.

Avvertenza: le prestazioni sono importanti nel software aziendale?

Sì, ma il time-to-market (TTM) è più importante, per ordini di grandezza. I software aziendali pongono l'accento sull'adattabilità del codice a regole aziendali complesse. Le misurazioni delle prestazioni dovrebbero essere prese durante l'intero ciclo di vita dello sviluppo. (Vedi la sezione: cosa significano prestazioni ottimali? ) Dovrebbero essere apportati solo miglioramenti commercializzabili e dovrebbero essere gradualmente introdotti nelle versioni successive.

Cosa significano prestazioni ottimali?

In generale, il problema con le prestazioni del software è che: per dimostrare che "esiste una versione più veloce", quella versione più veloce deve nascere prima (cioè nessuna prova diversa da se stessa).

A volte quella versione più veloce viene vista per la prima volta in una lingua o paradigma diversi. Questo dovrebbe essere preso come un suggerimento per il miglioramento, non un giudizio di inferiorità di alcune altre lingue o paradigmi.

Perché stiamo facendo OOP se ciò potrebbe ostacolare la nostra ricerca di prestazioni ottimali?

OOP introduce un overhead (nello spazio e nell'esecuzione), in cambio del miglioramento della "lavorabilità" e quindi del valore commerciale del codice. Ciò riduce i costi di ulteriore sviluppo e ottimizzazione. Vedi @MikeNakis .

Quali parti di OOP possono incoraggiare un design inizialmente non ottimale?

Le parti di OOP che (i) incoraggiano la semplicità / intuitività, (ii) l'uso di metodi di progettazione colloquiale anziché fondamentali, (iii) scoraggia molteplici implementazioni su misura dello stesso scopo.

  • BACIO
  • YAGNI
  • ASCIUTTO
  • Progettazione di oggetti (ad es. Con carte CRC) senza dare uguale pensiero ai fondamenti)

L'applicazione rigorosa di alcune linee guida OOP (incapsulamento, passaggio di messaggi, fare bene una cosa) comporterà inizialmente un codice più lento. Le misurazioni delle prestazioni aiuteranno a diagnosticare questi problemi. Fintanto che la struttura dei dati e l'algoritmo si allineano con la progettazione ottimale prevista dalla teoria, le spese generali di solito possono essere ridotte al minimo.

Quali sono le mitigazioni comuni alle spese generali OOP?

Come affermato in precedenza, l'utilizzo di strutture dati ottimali per la progettazione.

Alcune lingue supportano l'integrazione del codice che può ripristinare alcune prestazioni di runtime.

Come potremmo adottare OOP senza sacrificare le prestazioni?

Impara e applica sia OOP che i fondamenti.

È vero che l'adesione rigorosa a OOP potrebbe impedirti di scrivere una versione più veloce. A volte una versione più veloce può essere scritta solo da zero. Questo è il motivo per cui aiuta a scrivere più versioni di codice usando algoritmi e paradigmi diversi (OOP, generico, funzionale, matematico, spaghetti) e quindi utilizzare strumenti di ottimizzazione per far sì che ciascuna versione si avvicini alle prestazioni massime osservate.

Esistono tipi di codice che non beneficeranno di OOP?

(Ampliato dalla discussione tra [@quant_dev], [@ SK-logic] e [@MikeNakis])

  1. Ricette numeriche, che provengono dalla matematica.
    • Le equazioni matematiche e le trasformazioni stesse possono essere comprese come oggetti.
    • Per generare codice eseguibile efficiente sono necessarie tecniche di trasformazione del codice molto sofisticate. L'implementazione ingenua ("lavagna") avrà prestazioni spaventose.
    • Tuttavia, i compilatori mainstream di oggi non sono in grado di farlo.
    • I software specializzati (MATLAB e Mathematica, ecc.) Dispongono sia di JIT che di risolutori simbolici in grado di generare codice efficiente per alcuni sotto-problemi. Questi solutori specializzati possono essere visti come compilatori per scopi speciali (mediatori tra codice leggibile dall'uomo e codice eseguibile dalla macchina) che trarranno essi stessi vantaggio da un design OOP.
    • Ogni sotto-problema richiede il proprio "compilatore" e "trasformazioni di codice". Pertanto, si tratta di un'area di ricerca aperta molto attiva con nuovi risultati che compaiono ogni anno.
    • Poiché la ricerca richiede molto tempo, gli autori di software devono eseguire l'ottimizzazione su carta e trascrivere il codice ottimizzato nel software. Il codice trascritto potrebbe in effetti essere incomprensibile.
  2. Codice di livello molto basso.
      *

8

In realtà non si tratta dell'orientamento agli oggetti come dei contenitori. Se hai utilizzato un doppio elenco collegato per archiviare i pixel nel tuo lettore video ne risentirà.

Tuttavia, se si utilizza il contenitore corretto, non vi è alcun motivo per cui uno std :: vector sia più lento di un array e, dal momento che tutti gli algoritmi comuni sono già stati scritti per esso - dagli esperti - è probabilmente più veloce del codice dell'array di casa.


1
Poiché i compilatori non sono ottimali (o le regole del linguaggio di programmazione vietano di trarre vantaggio da determinati presupposti o ottimizzazioni), c'è davvero un sovraccarico che non può essere rimosso. Inoltre, alcune ottimizzazioni, ad esempio la vettorializzazione, hanno requisiti di organizzazione dei dati (ad es. Struttura di array anziché array di strutture) che OOP può migliorare o ostacolare. (Di recente ho appena lavorato su un'attività di ottimizzazione std :: vector.)
rwong

5

OOP è ovviamente una buona idea e, come ogni buona idea, può essere utilizzata in modo eccessivo. Nella mia esperienza è troppo abusato. Scarse prestazioni e scarso risultato di manutenibilità.

Non ha nulla a che fare con il sovraccarico di chiamare funzioni virtuali e non ha molto a che fare con l'ottimizzatore / jitter.

Ha tutto a che fare con le strutture dati che, pur avendo le migliori prestazioni di big-O, hanno fattori costanti pessimi. Questo viene fatto supponendo che se ci sono problemi che limitano le prestazioni nell'app, è altrove.

Un modo in cui questo si manifesta è il numero di volte al secondo in cui viene eseguita la nuova , che si presume abbia prestazioni O (1), ma può eseguire da centinaia a migliaia di istruzioni (incluso l' eliminazione corrispondente o il tempo GC). Ciò può essere mitigato salvando oggetti usati, ma ciò rende il codice meno "pulito".

Un altro modo in cui si manifesta è il modo in cui le persone sono incoraggiate a scrivere funzioni di proprietà, gestori di notifiche, chiamate a funzioni di classe di base, tutti i tipi di chiamate di funzioni sotterranee esistenti per cercare di mantenere la coerenza. Per mantenere la coerenza hanno un successo limitato, ma hanno un enorme successo nei cicli di spreco. I programmatori comprendono il concetto di dati normalizzati ma tendono ad applicarlo solo alla progettazione di database. Non lo applicano alla progettazione della struttura dei dati, almeno in parte perché OOP dice loro che non è necessario. Una cosa semplice come impostare un bit modificato in un oggetto può comportare uno tsunami di aggiornamenti in esecuzione attraverso la struttura dei dati, perché nessuna classe degna del suo codice accetta una chiamata modificata e la memorizza .

Forse le prestazioni di una determinata app vanno bene come scritte.

D'altra parte, se si verifica un problema di prestazioni, ecco un esempio di come procedere per ottimizzarlo. È un processo in più fasi. Ad ogni fase, alcune attività particolari rappresentano una frazione di tempo e potrebbero essere sostituite da qualcosa di più veloce. (Non ho detto "collo di bottiglia". Questi non sono i tipi di cose che i profiler sono bravi a trovare.) Questo processo spesso richiede, al fine di ottenere l'accelerazione, la sostituzione all'ingrosso della struttura dei dati. Spesso quella struttura di dati esiste solo perché è consigliata la pratica OOP.


3

In teoria, potrebbe portare alla lentezza, ma anche in questo caso, non sarebbe un algoritmo lento, sarebbe un'implementazione lenta. In pratica, l'orientamento agli oggetti ti consentirà di provare vari scenari what-if (o di rivisitare l'algoritmo in futuro) e quindi fornire miglioramenti algoritmici ad esso, che non avresti mai potuto sperare di ottenere se lo avessi scritto nel modo più semplice posto, perché il compito sarebbe scoraggiante. (Dovresti essenzialmente riscrivere il tutto.)

Ad esempio, dopo aver diviso i vari compiti ed entità in oggetti netti, potresti essere in grado di entrare facilmente in seguito e, per esempio, incorporare una funzione di memorizzazione nella cache tra alcuni oggetti (trasparente per loro) che potrebbe produrre un migliaio di miglioramento della piega.

In genere, i tipi di miglioramenti che è possibile ottenere utilizzando un linguaggio di basso livello (o trucchi intelligenti con un linguaggio di alto livello) forniscono miglioramenti di tempo (lineari) costanti, che non figurano in termini di notazione di grande oh. Con i miglioramenti algoritmici potresti essere in grado di ottenere miglioramenti non lineari. Non ha prezzo.


1
+1: la differenza tra spaghetti e codice orientato agli oggetti (o codice scritto in un paradigma ben definito) è: ogni versione di buon codice riscritta porta nuova comprensione nel problema. Ogni versione di spaghetti riscritta non porta mai alcuna intuizione.
rwong,

@rwong non poteva essere spiegato meglio ;-)
umlcat,

3

Ma questo OOP potrebbe essere uno svantaggio per il software basato sulle prestazioni, ovvero quanto velocemente esegue il programma?

Spesso sì !!! MA...

In altre parole, molti riferimenti tra molti oggetti diversi o l'utilizzo di molti metodi di molte classi potrebbero comportare un'implementazione "pesante"?

Non necessariamente. Questo dipende dalla lingua / dal compilatore. Ad esempio, un compilatore C ++ ottimizzante, a condizione che non si utilizzino funzioni virtuali, spesso riduce a zero il sovraccarico dell'oggetto. Puoi fare cose come scrivere un wrapper su un intlì o un puntatore intelligente con ambito su un semplice puntatore vecchio che si comporta altrettanto velocemente come usare direttamente questi semplici tipi di dati vecchi.

In altre lingue come Java, c'è un po 'di sovraccarico per un oggetto (spesso abbastanza piccolo in molti casi, ma astronomico in alcuni rari casi con oggetti davvero piccolissimi). Ad esempio, Integerè notevolmente meno efficiente di int(richiede 16 byte anziché 4 su 64 bit). Eppure questo non è solo uno spreco palese o qualcosa del genere. In cambio, Java offre elementi come la riflessione su ogni singolo tipo definito dall'utente in modo uniforme, nonché la possibilità di sovrascrivere qualsiasi funzione non contrassegnata come final.

Eppure prendiamo lo scenario migliore: il compilatore C ++ ottimizzante in grado di ottimizzare le interfacce degli oggetti fino a zero overhead. Anche in questo caso, OOP peggiorerà spesso le prestazioni e impedirà che raggiunga il picco. Potrebbe sembrare un paradosso completo: come potrebbe essere? Il problema sta nel:

Progettazione e incapsulamento dell'interfaccia

Il problema è che anche quando un compilatore può ridurre la struttura di un oggetto a zero overhead (che è almeno molto spesso vero per l'ottimizzazione dei compilatori C ++), l'incapsulamento e il design dell'interfaccia (e le dipendenze accumulate) di oggetti a grana fine spesso impediranno il rappresentazioni dei dati più ottimali per oggetti che sono destinati ad essere aggregati dalle masse (che è spesso il caso di software critico per le prestazioni).

Prendi questo esempio:

class Particle
{
public:
    ...

private:
    double birth;                // 8 bytes
    float x;                     // 4 bytes
    float y;                     // 4 bytes
    float z;                     // 4 bytes
    /*padding*/                  // 4 bytes of padding
};
Particle particles[1000000];     // 1mil particles (~24 megs)

Supponiamo che il nostro modello di accesso alla memoria sia semplicemente quello di scorrere ciclicamente queste particelle in sequenza e spostarle ripetutamente attorno a ciascun fotogramma, facendole rimbalzare dagli angoli dello schermo e quindi renderizzando il risultato.

Possiamo già vedere un evidente sovraccarico di padding di 4 byte necessario per allineare birthcorrettamente l' elemento quando le particelle vengono aggregate in modo contiguo. Già il ~ 16,7% della memoria viene sprecato con lo spazio morto utilizzato per l'allineamento.

Questo potrebbe sembrare discutibile perché oggi abbiamo gigabyte di DRAM. Eppure anche le macchine più bestiali che abbiamo oggi hanno spesso solo 8 megabyte quando si tratta della regione più lenta e più grande della cache della CPU (L3). Meno riusciamo a inserirci, più paghiamo per questo in termini di accesso ripetuto alla DRAM e più le cose diventano lente. Improvvisamente, sprecare il 16,7% della memoria non sembra più un affare banale.

Possiamo facilmente eliminare questo sovraccarico senza alcun impatto sull'allineamento del campo:

class Particle
{
public:
    ...

private:
    float x;                     // 4 bytes
    float y;                     // 4 bytes
    float z;                     // 4 bytes
};
Particle particles[1000000];     // 1mil particles (~12 megs)
double particle_birth[1000000];  // 1mil particle births (~8 bytes)

Ora abbiamo ridotto la memoria da 24 a 20 mega. Con un modello di accesso sequenziale, la macchina ora consumerà questi dati un po 'più velocemente.

Ma guardiamo questo birthcampo un po 'più da vicino. Diciamo che registra l'ora di inizio quando una particella nasce (creata). Immagina che il campo sia accessibile solo quando una particella viene creata per la prima volta e ogni 10 secondi per vedere se una particella dovrebbe morire e rinascere in una posizione casuale sullo schermo. In quel caso,birth è un campo freddo. Non è accessibile nei nostri loop critici per le prestazioni.

Di conseguenza, i dati effettivi critici per le prestazioni non sono 20 megabyte ma in realtà un blocco contiguo da 12 megabyte. La memoria reale effettiva a cui accediamo spesso si è ridotta della metà delle sue dimensioni! Aspettatevi accelerazioni significative rispetto alla nostra soluzione originale da 24 megabyte (non è necessario misurarla: ho già fatto questo tipo di cose mille volte, ma sentitevi liberi in caso di dubbi).

Tuttavia nota cosa abbiamo fatto qui. Abbiamo completamente rotto l'incapsulamento di questo oggetto particellare. Il suo stato è ora suddiviso tra Particlei campi privati ​​di un tipo e una matrice parallela separata. Ed è qui che si frappongono i design granulari orientati agli oggetti.

Non possiamo esprimere la rappresentazione ottimale dei dati se confinati alla progettazione dell'interfaccia di un singolo oggetto molto granulare come una singola particella, un singolo pixel, persino un singolo vettore a 4 componenti, forse anche un singolo oggetto "creatura" in un gioco , ecc. La velocità di un ghepardo sarà sprecata se si trova su un'isola più piccola di 2 metri quadrati, ed è ciò che spesso fa un design molto granulare orientato agli oggetti in termini di prestazioni. Limita la rappresentazione dei dati a una natura non ottimale.

Per fare ciò, supponiamo che dal momento che stiamo solo spostando le particelle, possiamo effettivamente accedere ai loro campi x / y / z in tre loop separati. In tal caso, possiamo trarre vantaggio dagli intrinseci SIMD in stile SoA con i registri AVX che possono vettorializzare 8 operazioni SPFP in parallelo. Ma per fare questo, ora dobbiamo usare questa rappresentazione:

float particle_x[1000000];       // 1mil particle X positions (~4 megs)
float particle_y[1000000];       // 1mil particle Y positions (~4 megs)
float particle_z[1000000];       // 1mil particle Z positions (~4 megs)
double particle_birth[1000000];  // 1mil particle births (~8 bytes)

Ora stiamo volando con la simulazione delle particelle, ma guarda cosa è successo al nostro design delle particelle. È stato completamente demolito e ora stiamo esaminando 4 array paralleli e nessun oggetto per aggregarli. Il nostro Particledesign orientato agli oggetti è diventato sayonara.

Questo mi è successo molte volte lavorando in settori critici per le prestazioni in cui gli utenti richiedono velocità, con la sola correttezza che è l'unica cosa che richiedono di più. Questi piccoli progetti orientati agli oggetti dovevano essere demoliti e le rotture a cascata spesso richiedevano che usassimo una strategia di deprecazione lenta verso un design più veloce.

Soluzione

Lo scenario sopra riportato presenta solo un problema con progetti granulari orientati agli oggetti. In questi casi, spesso finiamo per demolire la struttura per esprimere rappresentazioni più efficienti a seguito di ripetizioni SoA, divisione del campo caldo / freddo, riduzione dell'imbottitura per schemi di accesso sequenziale (l'imbottitura è talvolta utile per le prestazioni con accesso casuale modelli in casi AoS, ma quasi sempre un ostacolo per i modelli ad accesso sequenziale), ecc.

Eppure possiamo prendere quella rappresentazione finale su cui ci siamo basati e modellare ancora un'interfaccia orientata agli oggetti:

// Represents a collection of particles.
class ParticleSystem
{
public:
    ...

private:
    double particle_birth[1000000];  // 1mil particle births (~8 bytes)
    float particle_x[1000000];       // 1mil particle X positions (~4 megs)
    float particle_y[1000000];       // 1mil particle Y positions (~4 megs)
    float particle_z[1000000];       // 1mil particle Z positions (~4 megs)
};

Adesso stiamo bene. Possiamo ottenere tutti i gadget orientati agli oggetti che ci piacciono. Il ghepardo ha un intero paese da attraversare il più velocemente possibile. I nostri design di interfaccia non ci intrappolano più in un angolo di collo di bottiglia.

ParticleSystempuò anche essere astratto e utilizzare funzioni virtuali. Adesso è discutibile, stiamo pagando il sovraccarico a livello di raccolta di particelle anziché a livello di particella . Il sovraccarico è 1/1000.000 di quello che sarebbe altrimenti se modellassimo oggetti a livello di singola particella.

Quindi questa è la soluzione in aree critiche per le prestazioni che gestiscono un carico pesante e per tutti i tipi di linguaggi di programmazione (questa tecnica avvantaggia C, C ++, Python, Java, JavaScript, Lua, Swift, ecc.). E non può essere facilmente etichettato come "ottimizzazione prematura", poiché si riferisce al design dell'interfaccia e all'architettura . Non possiamo scrivere una base di codice che modella una singola particella come un oggetto con un carico di dipendenze del client in aParticle'sinterfaccia pubblica e poi cambiare idea in seguito. Ho fatto molto quando sono stato chiamato per ottimizzare basi di codice legacy, e questo può finire per richiedere mesi di riscrittura di decine di migliaia di righe di codice con attenzione per utilizzare il design più voluminoso. Ciò influenza idealmente il modo in cui progettiamo le cose in anticipo, a condizione che possiamo prevedere un carico pesante.

Continuo a fare eco a questa risposta in un modo o nell'altro in molte domande relative alle prestazioni, e in particolare quelle relative al design orientato agli oggetti. Il design orientato agli oggetti può ancora essere compatibile con le esigenze di prestazioni più elevate, ma dobbiamo cambiare un po 'il modo di pensarci. Dobbiamo dare a quel ghepardo un po 'di spazio per correre il più velocemente possibile, e questo è spesso impossibile se progettiamo piccoli oggetti per adolescenti che a malapena memorizzano qualsiasi stato.


Fantastico. Questo è ciò che stavo effettivamente cercando in termini di combinazione di OOP con richieste ad alte prestazioni. Non riesco davvero a capire perché non sia più votato.
pbx,

2

Sì, la mentalità orientata agli oggetti può essere sicuramente neutra o negativa quando si tratta di programmazione ad alte prestazioni, sia a livello algoritmico che di implementazione. Se OOP sostituisce l'analisi algoritmica, può portare all'implementazione prematura e, al livello più basso, le astrazioni OOP devono essere messe da parte.

Il problema deriva dall'enfasi di OOP nel pensare ai singoli casi. Penso che sia giusto dire che il modo OOP di pensare a un algoritmo è pensare a un insieme specifico di valori e implementarlo in quel modo. Se questo è il tuo percorso di massimo livello, è improbabile che tu realizzi una trasformazione o una ristrutturazione che porterebbe a grandi guadagni.

A livello algoritmico, sta spesso pensando al quadro più ampio e ai vincoli o alle relazioni tra valori che portano a guadagni di Big O. Un esempio potrebbe essere che non esiste nulla nella mentalità OOP che ti induca a trasformare "sommare un intervallo continuo di numeri interi" da un ciclo a(max + min) * n/2

A livello di implementazione, sebbene i computer siano "abbastanza veloci" per la maggior parte degli algoritmi a livello di applicazione, in un codice critico per le prestazioni di basso livello ci si preoccupa molto della località. Ancora una volta, l'enfasi di OOP sul pensare a una singola istanza e i valori di un passaggio attraverso il ciclo può essere un aspetto negativo. Nel codice ad alte prestazioni, invece di scrivere un ciclo semplice, potresti voler srotolare parzialmente il ciclo, raggruppare diverse istruzioni di caricamento in alto, quindi trasformarle in un gruppo, quindi scriverle in un gruppo. Nel frattempo presteresti attenzione ai calcoli intermedi e, enormemente, alla cache e all'accesso alla memoria; problemi in cui le astrazioni OOP non sono più valide. E, se seguito, può essere fuorviante: a questo livello, devi conoscere e pensare alle rappresentazioni a livello di macchina.

Quando osservi qualcosa come Intel Performance Primitives hai letteralmente migliaia di implementazioni della Fast Fourier Transform, ognuna ottimizzata per funzionare meglio per una specifica dimensione dei dati e un'architettura macchina. (Affascinante, si scopre che la maggior parte di queste implementazioni sono generate dalla macchina: Markus Püschel Automatic Performance Programming )

Ovviamente, come la maggior parte delle risposte ha affermato, per la maggior parte dello sviluppo, per la maggior parte degli algoritmi, OOP è irrilevante per le prestazioni. Finché non si "pessimizza prematuramente" e si aggiungono molte chiamate non locali, il thispuntatore non è né qui né lì.


0

È correlato e spesso trascurato.

Non è una risposta facile, dipende da cosa vuoi fare.

Alcuni algoritmi sono migliori nelle prestazioni usando una semplice programmazione strutturata, mentre altri utilizzano meglio l'orientamento agli oggetti.

Prima dell'orientamento agli oggetti, molte scuole insegnano (ed) la progettazione di algoritmi con una programmazione strutturata. Oggi molte scuole insegnano la programmazione orientata agli oggetti, ignorando la progettazione e le prestazioni degli algoritmi.

Certo, là dove scuole che insegnano la programmazione strutturata, a cui non importava affatto degli algoritmi.


0

Le prestazioni si riducono alla fine alla CPU e ai cicli di memoria. Ma la differenza percentuale tra il sovraccarico di messaggistica e incapsulamento OOP e una semantica di programmazione più ampia può o meno essere una percentuale abbastanza significativa da fare una notevole differenza nelle prestazioni dell'applicazione. Se un'app è legata al disco o alla mancanza della cache dei dati, qualsiasi sovraccarico di OOP potrebbe essere completamente perso nel rumore.

Ma, nei circuiti interni dell'elaborazione del segnale e delle immagini in tempo reale e di altre applicazioni legate al calcolo numerico, la differenza potrebbe benissimo essere una percentuale significativa di cicli di CPU e memoria, che può rendere qualsiasi overhead OOP molto più costoso da eseguire.

La semantica di un particolare linguaggio OOP può o meno esporre sufficienti opportunità per il compilatore di ottimizzare quei cicli, o per i circuiti di previsione dei rami della CPU di indovinare sempre correttamente e coprire quei cicli con pre-fetch e pipeline.


0

Un buon design orientato agli oggetti mi ha aiutato a velocizzare notevolmente un'applicazione. A ha dovuto generare una grafica complessa in modo algoritmico. L'ho fatto tramite l'automazione di Microsoft Visio. Ho lavorato, ma è stato incredibilmente lento. Fortunatamente, avevo inserito un ulteriore livello di astrazione tra la logica (l'algoritmo) e la roba di Visio. Il mio componente Visio ha rivelato la sua funzionalità attraverso un'interfaccia. Questo mi ha permesso di sostituire facilmente il componente lento con un altro creando file SVG, che era almeno 50 volte più veloce! Senza un approccio pulito orientato agli oggetti, i codici per l'algoritmo e il controllo Vision sarebbero stati intrappolati in un modo, il che avrebbe trasformato il cambiamento in un incubo.


intendevi OO Design applicato con un linguaggio procedurale o OO Design e linguaggio di programmazione OO?
Umlcat,

Sto parlando di un'applicazione C #. Sia il design che il linguaggio sono OO. Mentre OO-iness del linguaggio introdurrà alcune piccole prestazioni (chiamate al metodo virtuale, creazione di oggetti, accesso dei membri tramite interfaccia), il design OO mi ha aiutato a creare un'applicazione molto più veloce. Quello che voglio dire è: dimentica i risultati delle prestazioni a causa di OO (linguaggio e design). A meno che tu non stia facendo calcoli pesanti con milioni di iterazioni, OO non ti danneggerà. Dove di solito perdi molto tempo è l'I / O.
Olivier Jacot-Descombes il
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.