Ruby Numbers and the Memory: come mi sono confuso.

Feb 22 2023
All'improvviso stavo facendo un semplice compito per generare un gruppo di numeri interi casuali. Non c'erano requisiti rigidi, quindi stavo pensando a un limite superiore che potevo mettere.

All'improvviso stavo facendo un semplice compito per generare un gruppo di numeri interi casuali. Non c'erano requisiti rigidi, quindi stavo pensando a un limite superiore che potevo mettere. Il mio istinto mi ha consigliato di inventare un numero che potesse rappresentare vari numeri a un costo ragionevole. Per costo qui intendo memoria e prestazioni.

Logo ufficiale Rubino

Il mio primo tentativo è stato 2⁶³-1 come numero intero con segno di 8 byte (1 bit per un segno e 63 bit per il numero stesso). Quindi, ho controllato .sizeper assicurarmi che richiedano solo 8 byte.

(2**63-1).size
=> 8

Per un motivo che non ricordo, ho deciso di scoprire quando Ruby avrebbe iniziato a restituire 9 byte e, sorprendentemente, per me non era 2⁶³.

(2**63).size
=> 8

(2**64).size
=> 9

(-1*(2**64-1)).size
=> 8

La risposta breve: non può .

E ora va la lunga risposta.

Prima di tutto, il sizemetodo dipende dalla macchina .

Ma non è tutto. Come sappiamo, tutto in Ruby è un oggetto: compresi i numeri. Alcuni oggetti possono avere un comportamento diverso rispetto ad altri a causa dell'implementazione interna. Ad esempio, Symbolse Fixnums(ora con la Bignumparte della Integerclasse) sono speciali.

Avendo questo in mente, Integer#sizenon restituisce l'intera dimensione dell'oggetto ma solo la sua parte numerica.

Per ottenere la dimensione effettiva dell'oggetto, dovremmo usare ObjectSpace.memsize_of invece.

ObjectSpace.memsize_of(2**63)
=> 40

Puoi leggere di più sulla memoria interna di Ruby qui .

Quale sarebbe la dimensione di 2⁶³-1 nello spazio oggetti di Ruby? Scopriamolo.

ObjectSpace.memsize_of(2**63-1)
=> 40

ObjectSpace.memsize_of(2**62-1)
=> 0

Ciò significa che Ruby VM non alloca memoria extra per ogni numero (da -2⁶² a 2⁶²-1). Memorizza il numero nel suo puntatore di riferimento, che richiede solo 8 byte.

Perché è importante sapere?

Ci sono due ragioni principali: prestazioni e consumo di memoria .

Iniziamo prima con le prestazioni.

Operare con Bignum(dietro le quinte, tali Integernumeri sono ancora Bignum) è più lento.

Possiamo verificarlo con il seguente semplice benchmark.


require 'benchmark/ips'

Benchmark.ips do |x|
  x.report('Fixnum') { 2**60 + 2**60 }
  x.report('Bignum') { 2**64 + 2**64 }
end

Warming up --------------------------------------
              Fixnum     1.160M i/100ms
              Bignum   323.084k i/100ms
Calculating -------------------------------------
              Fixnum     11.542M (± 1.0%) i/s -     58.021M in   5.027510s
              Bignum      3.214M (± 0.9%) i/s -     16.154M in   5.026065s

Volentieri o no, gli sviluppatori Ruby raramente hanno bisogno di pensare a problemi di memoria a causa del lavoro di Garbage Collector (GC) . Sfortunatamente, a volte questa libertà dal controllo della memoria può colpirci.

Ad esempio, Ruby raramente può liberare la memoria nel sistema, il che significa che preferiremmo evitare i rigonfiamenti della memoria . Per lo più, i rigonfiamenti della memoria sono difficili da gestire, quindi perché non eliminare potenziali problemi quando possiamo farlo gratuitamente?

Nel mio caso, la creazione di milioni di numeri casuali in pochi secondi che avrebbero avuto luogo nello spazio degli oggetti non era perfetta. Quindi avere limiti tra -2⁶² e 2⁶²-1 per un numero casuale renderebbe il consumo di memoria più fluido.

Grazie per il tuo tempo e seguimi per altri argomenti su #ruby e #rails.

  • medio
  • Linkedin
  • Cinguettio
  • Git Hub