2012-01-13 11 views
6

Sto progettando un'API in Java per un insieme di algoritmi numerici che agiscono su array di doppi (per le statistiche finanziarie in tempo reale come accade). Per motivi di prestazioni, l'API deve funzionare con matrici primitive, quindi List<Double> e simili non sono un'opzione.Progettazione API per funzioni che agiscono sugli array

Un tipico caso d'uso può essere un oggetto algoritmo che accetta due matrici di input e restituisce un array di output che contiene un risultato calcolato dai due input.

mi piacerebbe stabilire convenzioni coerenti per come i parametri di matrice sono utilizzati nella API, in particolare:

  • Devo includere un offset con tutte le funzioni in modo gli utenti possono agire su parti di un array più grande es. someFunction(double[] input, int inputOffset, int length)
  • Se una funzione richiede sia i parametri di ingresso che di uscita, l'ingresso o l'uscita dovrebbe essere il primo nell'elenco dei parametri?
  • Se il chiamante assegna un array di output e lo passa come parametro (che potrebbe potenzialmente essere riutilizzato), o la funzione dovrebbe creare e restituire un array di output ogni volta che viene chiamato?

Gli obiettivi sono di raggiungere un equilibrio di efficienza, semplicità per gli utenti API e coerenza sia all'interno dell'API che con convenzioni consolidate.

Chiaramente ci sono molte opzioni, quindi qual è il miglior design generale dell'API?

risposta

2

In modo che suona davvero come tre domande, per cui qui sono le mie opinioni.

Naturalmente, questo è molto soggettivo - quindi - la vostra situazione potrebbe essere diversa:

  1. Sì. Includere sempre la lunghezza offset &. Se la maggior parte dei casi d'uso per una particolare funzione non ha bisogno di questi parametri, , sovraccaricare la funzione in modo che la lunghezza dell'ingresso & non sia richiesta.

  2. Per questo, vorrei seguire lo standard utilizzato da arraycopy:

    arraycopy (src Object, int srcPos, dest oggetto, int destPos, lunghezza int)

  3. La differenza di prestazioni qui sta per trascurabile a meno che il chiamante non chiami ripetutamente le funzioni di utilità. Se sono solo una cosa, non dovrebbe esserci differenza. Se vengono richiamati più volte del necessario, il chiamante ti invierà un array assegnato.

2
  1. Se lo si desidera, fornire anche un'opzione predefinita (inizia da 0, a lunghezza intera).
  2. Penso che la maggior parte degli utenti si aspetteranno in uscita 2a. Tuttavia, se potessi usare vararg, ciò potrebbe farti cambiare idea.
  3. Mi piace il chiamante che passa nell'array di output, ma con un'opzione per null, ovvero il metodo verrà assegnato.

Elaborazione sul commento vararg, consente di disporre di un metodo per aggiungere due array. Se si mette l'array di output arg come 1o arg e 2 array di input alla fine, è banale estendere il metodo per aggiungere N array.

Elaborazione su # 3, lasciando passare i chiamanti nell'array di output, a volte è più efficiente. E, anche se il guadagno è trascurabile, i tuoi utenti, che hanno a che fare con array primitivi, provengono probabilmente da uno sfondo C o FORTRAN, e pensano che il guadagno sarà grande e si lamenterà se non permetti loro di essere "efficienti". :-)

2

Supponendo che si stia lavorando con array abbastanza piccoli da essere allocati in pila o in Eden, l'allocazione è estremamente rapida. Pertanto, non vi è nulla di male nell'avere che le funzioni allocano i propri array per restituire risultati. Fare questo è una grande vittoria per la leggibilità.

Vorrei suggerire di iniziare a far funzionare le funzioni su interi array e introdurre un'opzione per chiamare una funzione con solo una porzione di un array solo se si scopre che è utile.

+0

Penso che stia scrivendo in Java, quindi il commento "in pila" non ha senso in quel contesto. – user949300

+1

No, HotSpot alloca piccoli oggetti che non sfuggono nello stack. –

+0

Ma poiché questi array vengono restituiti come risultati, sfuggono sicuramente. – user949300

0

userei List<Double> e hanno i metodi restituiscono l'output come nuovo List:

public List<Double> someFunction(List<Double> input) 
+0

generalmente un buon consiglio ma non è fattibile nel mio caso - per motivi di prestazioni l'API deve lavorare con gli array primitivi – mikera

1

L'aspetto principale del design dell'API che espone più funzioni è la sua coerenza interna. Tutto il resto arriva come un secondo lontano.

La decisione di passare o meno le coppie indice/lunghezza dipende dal modo in cui è prevista l'utilizzo dell'API. Se si prevede che gli utenti scrivano serie di chiamate al metodo che prendono o inseriscono dati in segmenti diversi della stessa matrice, come in System.arrayCopy, allora sono necessarie coppie indice/lunghezza. Altrimenti, è eccessivo.

L'input per primo o per primo è la tua decisione, ma una volta che lo hai fatto, seguilo con tutti i metodi con firme simili.

Il passaggio del buffer di output è un'opzione ragionevole solo se il buffer viene riutilizzato nel client. Altrimenti, è uno spreco di sforzi nella creazione e nella gestione di un set aggiuntivo di metodi API. Ovviamente questa decisione è strettamente correlata alla scelta di andare con coppie indice/lunghezza: se prendi l'indice e la lunghezza, dovresti anche prendere il buffer di output.

1

Penso che il design dell'API sia in gran parte soggettivo e/o debba essere pesantemente influenzato dai "casi d'uso" dell'API. D'altro canto, i casi d'uso per la tua API dipendono interamente dal codice cliente.

Detto questo, personalmente, vorrei approfittare di overloading dei metodi e vado per la seguente struttura:

Metodo con tutti i parametri:

void someFunction(int[] input1, int[] input2, int offset, int length, int[] output)

Questa è la funzione principale. Tutte le altre funzioni chiamano questo con parametri appropriati.

int[] someFunction(int[] input1, int[] input2, int offset, int length)

Questo chiama la prima funzione, ma alloca e restituisce la matrice di uscita a nome del chiamante.

void someFunction(int[] input1, int[] input2, int[] output)

int[] someFunction(int[] input1, int[] input2)

Si noti che la strategia generale è quello di rendere l'elenco dei parametri più breve eliminando parametri 'opzionali'.

In generale, tendo a evitare di cambiare il comportamento del metodo a seconda che un parametro (come l'array di output) sia null. Può rendere più difficile cogliere errori sottili in questo modo. Da qui la mia preferenza per due diversi stili di chiamata, uno in cui viene fornito il parametro di output (e richiesto) e uno in cui il metodo restituisce l'output.

Problemi correlati