2012-05-05 14 views
8

Quando si esegue questo e si osserva il consumo di memoria del mio processo di ruby ​​in OSX Activity Monitor, la memoria aumenta a circa 3 MB/s.Mancanza di memoria durante la creazione di molti nuovi oggetti

Se rimuovo la transazione, dimezza il consumo di memoria ma, comunque, il footprint di memoria continua a salire. Ho un problema nella mia app di produzione in cui Heroku uccide il processo a causa del suo consumo di memoria.

C'è un modo di fare il seguito, in un modo che non aumenterà la memoria? Se commento la riga .save allora va bene, ma ovviamente questa non è una soluzione.

ActiveRecord::Base.transaction do 
    10000000.times do |time| 
    puts "---- #{time} ----" 
    a = Activity.new(:name => "#{time} Activity") 
    a.save!(:validate => false) 
    a = nil 
    end 
end 

Sto utilizzando questo utilizzando delayed_job.

+0

Finetune il tuo garbage collector ma è un lavoro complicato ... – apneadiving

+1

@Morgz Quali versioni di Ruby e Rails stai utilizzando? – Matty

+0

@Matty Rails 3.1.0 – Morgz

risposta

4

La riga a = nil non è necessaria ed è possibile rimuoverla.

Stai creando molti oggetti ogni volta che esegui il ciclo: due stringhe, due hash e un oggetto Activity, quindi non mi sorprende che tu stia riscontrando un utilizzo elevato della memoria, specialmente mentre fai un ciclo di 10 milioni di volte ! Non sembra esserci un modo più efficiente di memoria per scrivere questo codice.

L'unico modo che posso pensare per ridurre l'utilizzo della memoria è avviare manualmente il garbage collector ogni x numero di iterazioni. È probabile che il GC di Ruby non sia abbastanza aggressivo. Tuttavia, non vuoi invocarlo ogni iterazione poiché questo rallenterà radicalmente il tuo codice. Forse potresti usare ogni 100 iterazioni come punto di partenza e andare da lì. Dovrai profilare e testare ciò che è più efficace.

La documentazione per GC is here.

+0

in base alle mie conoscenze, puoi solo "suggerire" Ruby per eseguire GC. ma potrebbe ancora funzionare – K2xL

+0

@ K2xL Non ne sono così sicuro. Puoi forzare GC chiamando 'GC.start'. Mi piacerebbe essere mostrato diversamente altrimenti. Non c'è nulla nella documentazione per indicare che chiamare 'GC.start' è un advisory. – Matty

+0

Hey Matt. È interessante notare che ... chiamando GC.start all'interno dei loop si evita che la memoria vada fuori controllo, ma sì, ciò influisce molto sulle prestazioni! Quindi ho pensato di provare a invocarlo ogni 1000 volte. GC.start se tempo% 1000 == 0 - Questo non funziona e le spirali di memoria aumentano e sono fuori controllo! Quindi al momento solo l'opzione è per GC ogni volta: -/ – Morgz

0

So che questo è un problema antico, ma devo suggerire un altro approccio radicale:

ActiveRecord::Base.transaction do 
    10000000.times do |time| 
    puts "---- #{time} ----" 
    sql = <<SQL 
INSERT INTO activities ("name") VALUES ("#{time}") 
SQL 
    Activity.connection.execute(sql) 
    end 
end 

Il punto è che se l'inserto è così semplice, e si sta già saltare alcuna convalida ActiveModel, c'è nessuna ragione per istanziare un oggetto activerecord in primo luogo. Normalmente non sarebbe male, ma visto che ti fa male in questo caso, penso che userai molto meno memoria in questo modo.

Problemi correlati