Hoje fiz vários testes de gerência de memória e achei interessante como cada ganho de byte pode ser interessante conforme a quantidade de dados que se tem para manusear. Apesar da memória ser barata, tratando-se de performance e escalabilidade o barato sai caro muitas vezes.

Então olhando para os gargalos do sistema observei um hash muito utilizado em um determinado sistema e então resolvi montar esta comparação.

A brincadeira começa com uma simples array de 1000_000 registros e populamos ela das seguintes formas:

Hash - memory_a.rb

a = []
sum = 0
100000.times do |i|
   sum += 1
   a << {:i => i,
         :sum => sum}

end

Struct - memory_b.rb

a = []
B = Struct.new :i, :sum
sum = 0
100000.times do |i|
    sum += 1
    a << B.new(i,sum)
end

Array - memory_c.rb

i = []
sums = []
sum = 0
100000.times do |i|
    sum += 1
    i << i
    sums << sum
end

Class com métodos - memory_d.rb

a = []
class B
  def initialize i, sum
    @i = i
    @sum = sum
  end
  def i
    @i
  end
  def sum
    @sum
  end
end
sum = 0
100000.times do |i|
  sum += 1
   a << B.new(i,sum)
end

Class com accessors - memory_e.rb

a = []
sum = 0
class B
  attr_accessor :i, :sum
  def initialize i, sum
    @i = i
    @sum = sum
  end
end
100000.times do |i|
  sum += 1
  a << B.new(i,sum)
end

Class sem métodos - memory_f.rb

class B
  instance_methods.each { |m| undef_method m unless m =~ /^__|object_id/ }
  attr_accessor :i, :sum
  def initialize i, sum
    @i = i
    @sum = sum
  end
end

a = []
sum = 0
100000.times do |i|
  sum += 1
  a << B.new(i,sum)
end

Observando os resultados com jruby-1.7.3 temos os seguintes tamanhos de memória em bytes:

arquivo memória
memory_a.rb 106409984
memory_b.rb 94859264
memory_c.rb 95125504
memory_d.rb 95064064
memory_e.rb 94691328
memory_f.rb 94236672

No meu caso o modelo memory_f parece ser o mais adequado em vista do consumo de memória e a implementação também é relativamente simples. O interessante destes objetos em branco é por que de certa forma eles não podem ser clonados, mas para casos específicos podem ajudar muito, neste caso têm uma significância de aproximadamente 12%.

Baixe e teste o code você mesmo!

Assim que sobrar um tempo vou evoluir os modelos para comparar entre tipos de númericos. Hoje estamos usando o java.math.BigDecimal para cálculos precisos mas este é um dos mais lentos e grandes para trabalhar com números.

Jônatas Davi Paganini

Jônatas Davi Paganini

Developer and writer passionate about PostgreSQL, TimescaleDB, and building better systems. Currently sharing knowledge about time series databases and system architecture.