2013-05-21 8 views
9

Quindi, ho una domanda concettuale. Ho lavorato con JNI su Android allo scopo di fare "roba" audio di basso livello. Ho fatto un sacco di codifiche audio in C/C++, quindi ho pensato che questo non sarebbe stato un grosso problema. Ho deciso di usare C++ nel mio codice "nativo" (perché chi non ama OOP?). Il problema che ho riscontrato sembra (per me) strano: quando creo un oggetto per elaborare l'audio nel codice C++, e non ho mai passato questo oggetto a Java (né viceversa), chiamando i metodi su questo l'oggetto sembra invocare la garbage collection abbastanza spesso. Poiché questo accade callback all'interno audio, il risultato è audio balbuzie ed ottengo frequenti messaggi lungo le linee di:Gli oggetti C++ nel codice nativo JNI di Android invocano la garbage collection?

WAIT_FOR_CONCURRENT_GC blocked 23ms 

Tuttavia, quando eseguono le stesse operazioni creando funzioni statiche (anziché invocare metodi membri sulla un oggetto memeber), le prestazioni dell'app sembrano andare bene e non vedo più il messaggio di registro sopra.

Fondamentalmente, c'è qualche ragione per cui una funzione statica dovrebbe avere prestazioni migliori rispetto ai metodi dei membri chiamanti su un oggetto membro nel codice nativo? In particolare, sono oggetti membro o variabili di ambito limitato che vivono interamente all'interno del codice nativo di un progetto JNI coinvolto nella garbage collection? Lo stack di chiamate C++ è coinvolto in GC? C'è qualche idea su qualcuno che possa darmi come la gestione della memoria C++ incontra la gestione della memoria Java quando si tratta della programmazione JNI? Cioè, nel caso in cui non sto passando dati tra Java e C++, il modo in cui scrivo codice C++ influenza la gestione della memoria Java (GC o altro)?

Consentitemi di provare a dare un esempio. Resta con me, perché è spaventoso a lungo, e se pensi di avere intuizione, puoi smettere di leggere qui.

Ho un paio di oggetti. Uno che è responsabile per la creazione del motore audio, l'inizializzazione dell'output, ecc. Si chiama HelloAudioJNI (mi dispiace di non aver inserito esempi compilabili, ma c'è molto codice).

class CHelloAudioJNI { 

    ... omitted members ... 

    //member object pointers 
    COscillator *osc; 
    CWaveShaper *waveShaper; 

    ... etc ... 

public: 
    //some methods 
    void init(float fs, int bufferSize, int channels); 

    ... blah blah blah ... 

Ne consegue che ho ancora un paio di lezioni. La classe WaveShaper si presenta così:

class CWaveShaper : public CAudioFilter { 
protected: 
    double *coeffs; 
    unsigned int order;//order 
public: 
    CWaveShaper(const double sampleRate, const unsigned int numChannels, 
       double *coefficients, const unsigned int order); 

    double processSample(double input, unsigned int channel); 
    void reset(); 
}; 

Non preoccupiamoci sulla classe CAudioFilter per ora, dal momento che questo esempio è già piuttosto lungo. Il file .cpp di WaveShaper è simile al seguente:

CWaveShaper::CWaveShaper(const double sampleRate, 
         const unsigned int numChannels, 
         double *coefficients, 
         const unsigned int numCoeffs) : 
    CAudioFilter(sampleRate,numChannels), coeffs(coefficients), order(numCoeffs) 
{} 

double CWaveShaper::processSample(double input, unsigned int channel) 
{ 
    double output = 0; 
    double pow = input; 

    //zeroth order polynomial: 
    output = pow * coeffs[0]; 

    //each additional iteration 
    for(int iteration = 1; iteration < order; iteration++){ 
     pow *= input; 
     output += pow * coeffs[iteration]; 
    } 

    return output; 
} 

void CWaveShaper::reset() {} 

e quindi c'è HelloAudioJNI.cpp. È qui che entriamo nella carne del problema. Creo gli oggetti membro correttamente, usando nuovo all'interno della funzione init, così:

void CHelloAudioJNI::init(float samplerate, int bufferSize, int channels) 
{ 
    ... some omitted initialization code ... 

     //wave shaper numero uno 
    double coefficients[2] = {1.0/2.0, 3.0/2.0}; 
    waveShaper = new CWaveShaper(fs,outChannels,coefficients,2); 

    ... some more omitted code ... 
} 

Ok tutto sembra bene finora. Poi all'interno della richiamata audio che chiamiamo alcuni metodi membro in oggetto membro in questo modo:

void CHelloAudioJNI::processOutputBuffer() 
{ 
    //compute audio using COscillator object 
    for(int index = 0; index < outputBuffer.bufferLen; index++){ 
     for(int channel = 0; channel < outputBuffer.numChannels; channel++){ 
      double sample; 

      //synthesize 
      sample = osc->computeSample(channel); 
      //wave-shape 
      sample = waveShaper->processSample(sample,channel); 

      //convert to FXP and save to output buffer 
      short int outputSample = amplitude * sample * FLOAT_TO_SHORT; 
      outputBuffer.buffer[interleaveIndex(index,channel)] = outputSample; 
     } 
    } 
} 

Questo è ciò che produce frequenti interruzioni audio e un sacco di messaggi circa la raccolta dei rifiuti. Tuttavia, se copio la funzione CWaveShaper :: ProcessSample() per la HelloAudioJNI.cpp immediatamente sopra il callback e lo chiamo direttamente anziché la funzione membro:

sample = waveShape(sample, coeff, 2); 

tanto sono bello bello suoni emessi dalla mia Android dispositivo e non ricevo messaggi così frequenti sulla garbage collection.Ancora una volta le domande sono, sono oggetti membro o variabili di ambito limitato che vivono interamente all'interno del codice nativo di un progetto JNI coinvolto nella garbage collection? Lo stack di chiamate C++ è coinvolto in GC? C'è qualche idea su qualcuno che possa darmi come la gestione della memoria C++ incontra la gestione della memoria Java quando si tratta della programmazione JNI? Cioè, nel caso in cui non sto passando dati tra Java e C++, il modo in cui scrivo codice C++ influenza la gestione della memoria Java (GC o altro)?

risposta

3

CHelloAudioJNI::init(...) memorizza un puntatore a una variabile pila (double coefficients[2]) in waveshaper. Quando si accede a waveShaper->coeffs dopo che i coefficienti sono andati fuori campo, si verifica BadThings (tm).

Creare una copia dell'array nel costruttore CWaveShaper (e non dimenticare di eliminarlo nel distruttore). O utilizzare std::array.

+0

Questo può essere tangenziale, in quanto i coefficienti suonano come qualcosa che dovrebbe essere letto, ma non scritto. Pertanto, i valori al momento dell'accesso potrebbero essere indeterminati, ma non è immediatamente chiaro che si verificherà il danneggiamento dello stack. –

+0

@ChrisStratton, puoi spiegare il tuo commento? Non capisco cosa intendi. – xaviersjs

+0

Il problema sollevato qui è qualcosa che è necessario correggere poiché i valori dei coefficienti a cui si accede da una memoria non più allocata potrebbero non essere corretti. Ma se non proverai a modificare i coefficienti, questo non causerebbe un flusso di programma improprio, che sembra essere l'unica spiegazione per la tua garbage collection implicitamente offerta da questa risposta. Se si legge * solo * i coefficienti da utilizzare in un calcolo valido per tutti gli input, quindi "BadThings (tm)" è limitato a "risultati di calcolo errati" –

5

Non esiste alcuna relazione tra gli oggetti C++ e la garbage collection di Dalvik. Dalvik non ha interesse per il contenuto dell'heap nativo, se non per la sua memoria interna. Tutti gli oggetti creati da sorgenti Java vivono sull'heap "gestito", che è il luogo in cui avviene la garbage collection.

Il GC Dalvik non esamina lo stack nativo; ogni thread noto alla VM ha uno stack separato da utilizzare per l'interprete.

L'unico modo in cui C++ e oggetti gestiti sono correlati è se si sceglie di creare una relazione associando gli oggetti in qualche modo (ad esempio creando un nuovo oggetto gestito da un costruttore C++ o eliminando un oggetto nativo da un finalizzatore Java).

È possibile utilizzare la funzione "Tracciamento di allocazione" di DDMS/ADT per visualizzare gli oggetti creati più di recente nell'heap gestito e da dove vengono allocati. Se lo esegui durante il flurry del GC dovresti essere in grado di dire cosa lo sta causando.

Inoltre, i messaggi di logcat mostrano gli ID di processo e di thread (dalla riga di comando, adb logcat -v threadtime), che è necessario controllare per assicurarsi che i messaggi provengano dall'app e anche per vedere quale thread l'attività del GC sta succedendo. Puoi vedere i nomi dei thread nella scheda "Thread" in DDMS/ADT.