Fix naming: FIFO -> LRU

This commit is contained in:
Philippe Pittoli 2024-12-13 23:31:25 +01:00
parent 19d878e21c
commit 38f54cdf77
10 changed files with 222 additions and 222 deletions

View File

@ -12,7 +12,7 @@ require "./db-cars.cr"
# ENV["REPORT_DIR"] rescue "results"
# ENV["NBRUN"] rescue 100
# ENV["MAXINDEXES"] rescue 5_000
# ENV["FIFO_SIZE"] rescue 10_000
# ENV["LRU_SIZE"] rescue 10_000
class Context
class_property report_dir = "results"
@ -21,7 +21,7 @@ class Context
class_property from = 1_000
class_property to = 50_000
class_property incr = 1_000
class_property fifo_size : UInt32 = 10_000
class_property lru_size : UInt32 = 10_000
end
# To simplify the creation of graphs, it's better to have fake data for
@ -76,9 +76,9 @@ end
def search_benchmark(storage : DODB::Storage(Car),
current_db_size : Int32,
name : String,
search_name : DODB::Trigger::Index(Car),
search_color : DODB::Trigger::Partition(Car),
name : String,
search_name : DODB::Trigger::Index(Car),
search_color : DODB::Trigger::Partition(Car),
search_keywords : DODB::Trigger::Tags(Car))
name_to_search = ENV["CARNAME"] rescue "Corvet-#{(current_db_size/2).to_i}"
color_to_search = ENV["CARCOLOR"] rescue "red"
@ -103,13 +103,13 @@ end
def bench_searches()
cars_ram = SPECDB::RAMOnly(Car).new
cars_cached = SPECDB::Cached(Car).new
cars_fifo = SPECDB::Common(Car).new "-#{Context.fifo_size}", Context.fifo_size
cars_lru = SPECDB::Common(Car).new "-#{Context.lru_size}", Context.lru_size
cars_semi = SPECDB::Uncached(Car).new "-semi"
cars_uncached = SPECDB::Uncached(Car).new
ram_Sby_name, ram_Sby_color, ram_Sby_keywords = ram_indexes cars_ram
cached_Sby_name, cached_Sby_color, cached_Sby_keywords = cached_indexes cars_cached
fifo_Sby_name, fifo_Sby_color, fifo_Sby_keywords = cached_indexes cars_fifo
lru_Sby_name, lru_Sby_color, lru_Sby_keywords = cached_indexes cars_lru
semi_Sby_name, semi_Sby_color, semi_Sby_keywords = cached_indexes cars_semi
uncached_Sby_name, uncached_Sby_color, uncached_Sby_keywords = uncached_indexes cars_uncached
@ -117,7 +117,7 @@ def bench_searches()
prepare_env cars_ram, "ram", ram_Sby_name, ram_Sby_color, ram_Sby_keywords, &fn
prepare_env cars_cached, "cached", cached_Sby_name, cached_Sby_color, cached_Sby_keywords, &fn
prepare_env cars_fifo, "fifo", fifo_Sby_name, fifo_Sby_color, fifo_Sby_keywords, &fn
prepare_env cars_lru, "lru", lru_Sby_name, lru_Sby_color, lru_Sby_keywords, &fn
prepare_env cars_semi, "semi", semi_Sby_name, semi_Sby_color, semi_Sby_keywords, &fn
prepare_env cars_uncached, "uncached", uncached_Sby_name, uncached_Sby_color, uncached_Sby_keywords, &fn
end
@ -136,13 +136,13 @@ end
def bench_add()
cars_ram = SPECDB::RAMOnly(Car).new
cars_cached = SPECDB::Cached(Car).new
cars_fifo = SPECDB::Common(Car).new "-#{Context.fifo_size}", Context.fifo_size
cars_lru = SPECDB::Common(Car).new "-#{Context.lru_size}", Context.lru_size
cars_semi = SPECDB::Uncached(Car).new "-semi"
cars_uncached = SPECDB::Uncached(Car).new
ram_indexes cars_ram
cached_indexes cars_cached
cached_indexes cars_fifo
cached_indexes cars_lru
cached_indexes cars_semi
uncached_indexes cars_uncached
@ -152,8 +152,8 @@ def bench_add()
avr = perform_add(cars_cached)
puts "(cached db and indexes) add a value (average on #{Context.nb_run} tries): #{avr}"
avr = perform_add(cars_fifo)
puts "(fifo db and cached indexes) add a value (average on #{Context.nb_run} tries): #{avr}"
avr = perform_add(cars_lru)
puts "(lru db and cached indexes) add a value (average on #{Context.nb_run} tries): #{avr}"
avr = perform_add(cars_semi)
puts "(uncached db but cached indexes) add a value (average on #{Context.nb_run} tries): #{avr}"
@ -167,23 +167,23 @@ def bench_add()
cars_uncached.rm_storage_dir
end
def bench_50_shades_of_fifo()
cars_fifo1 = SPECDB::Common(Car).new "-1k", 1_000
cars_fifo5 = SPECDB::Common(Car).new "-5k", 5_000
cars_fifo10 = SPECDB::Common(Car).new "-10k", 10_000
cars_fifo20 = SPECDB::Common(Car).new "-20k", 20_000
def bench_50_shades_of_lru()
cars_lru1 = SPECDB::Common(Car).new "-1k", 1_000
cars_lru5 = SPECDB::Common(Car).new "-5k", 5_000
cars_lru10 = SPECDB::Common(Car).new "-10k", 10_000
cars_lru20 = SPECDB::Common(Car).new "-20k", 20_000
fifo_Sby_name1, fifo_Sby_color1, fifo_Sby_keywords1 = cached_indexes cars_fifo1
fifo_Sby_name5, fifo_Sby_color5, fifo_Sby_keywords5 = cached_indexes cars_fifo5
fifo_Sby_name10, fifo_Sby_color10, fifo_Sby_keywords10 = cached_indexes cars_fifo10
fifo_Sby_name20, fifo_Sby_color20, fifo_Sby_keywords20 = cached_indexes cars_fifo20
lru_Sby_name1, lru_Sby_color1, lru_Sby_keywords1 = cached_indexes cars_lru1
lru_Sby_name5, lru_Sby_color5, lru_Sby_keywords5 = cached_indexes cars_lru5
lru_Sby_name10, lru_Sby_color10, lru_Sby_keywords10 = cached_indexes cars_lru10
lru_Sby_name20, lru_Sby_color20, lru_Sby_keywords20 = cached_indexes cars_lru20
fn = ->search_benchmark(DODB::Storage(Car), Int32, String, DODB::Trigger::Index(Car), DODB::Trigger::Partition(Car), DODB::Trigger::Tags(Car))
prepare_env cars_fifo1, "fifo1", fifo_Sby_name1, fifo_Sby_color1, fifo_Sby_keywords1, &fn
prepare_env cars_fifo5, "fifo5", fifo_Sby_name5, fifo_Sby_color5, fifo_Sby_keywords5, &fn
prepare_env cars_fifo10, "fifo10", fifo_Sby_name10, fifo_Sby_color10, fifo_Sby_keywords10, &fn
prepare_env cars_fifo20, "fifo20", fifo_Sby_name20, fifo_Sby_color20, fifo_Sby_keywords20, &fn
prepare_env cars_lru1, "lru1", lru_Sby_name1, lru_Sby_color1, lru_Sby_keywords1, &fn
prepare_env cars_lru5, "lru5", lru_Sby_name5, lru_Sby_color5, lru_Sby_keywords5, &fn
prepare_env cars_lru10, "lru10", lru_Sby_name10, lru_Sby_color10, lru_Sby_keywords10, &fn
prepare_env cars_lru20, "lru20", lru_Sby_name20, lru_Sby_color20, lru_Sby_keywords20, &fn
end
ENV["REPORT_DIR"]?.try { |report_dir| Context.report_dir = report_dir }
@ -194,7 +194,7 @@ ENV["NBRUN"]?.try { |it| Context.nb_run = it.to_i }
ENV["DBSIZE"]?.try { |it| Context.to = it.to_i }
ENV["DBSIZE_START"]?.try { |it| Context.from = it.to_i }
ENV["DBSIZE_INCREMENT"]?.try { |it| Context.incr = it.to_i }
ENV["FIFO_SIZE"]?.try { |it| Context.fifo_size = it.to_u32 }
ENV["LRU_SIZE"]?.try { |it| Context.lru_size = it.to_u32 }
puts "REPORT_DIR: #{Context.report_dir}"
puts "MAXINDEXES: #{Context.max_indexes}"
@ -202,20 +202,20 @@ puts "NBRUN: #{Context.nb_run}"
puts "DBSIZE: #{Context.to}"
puts "DBSIZE_START: #{Context.from}"
puts "DBSIZE_INCREMENT: #{Context.incr}"
puts "FIFO_SIZE: #{Context.fifo_size}"
puts "LRU_SIZE: #{Context.lru_size}"
if ARGV.size == 0
puts "Usage: benchmark-cars (fifo|search|add)"
puts "Usage: benchmark-cars (lru|search|add)"
exit 0
end
case ARGV[0]
when /fifo/
bench_50_shades_of_fifo
when /lru/
bench_50_shades_of_lru
when /search/
bench_searches
when /add/
bench_add
else
puts "Usage: benchmark-cars (fifo|search|add)"
puts "Usage: benchmark-cars (lru|search|add)"
end

View File

@ -1,70 +0,0 @@
require "benchmark"
require "./utilities.cr"
require "../src/fifo.cr"
def add(fifo : FIFO(Int32) | EfficientFIFO(Int32), nb : UInt32)
i = 0
while i < nb
fifo << i
i += 1
end
end
def report_add(fifo : FIFO(Int32) | EfficientFIFO(Int32), nb : UInt32, fname : String)
File.open("#{Context.report_dir}/#{fname}.raw", "w") do |file|
i = 0
while i < nb
elapsed_time = perform_something { fifo << i }
i += 1
file.puts "#{i} #{elapsed_time.total_nanoseconds}"
end
end
end
class Context
class_property nb_values : UInt32 = 100_000
class_property fifo_size : UInt32 = 10_000
class_property report_dir = "results"
end
if nb_values = ENV["NBVAL"]?
Context.nb_values = nb_values.to_u32
end
if fifo_size = ENV["FIFOSIZE"]?
Context.fifo_size = fifo_size.to_u32
end
if ARGV.size > 0
puts "Usage: benchmark-fifo"
puts ""
puts "envvar: REPORT_DIR=<directory> where to put the results"
puts "envvar: REPORT_EACH_ADD=<any> to report the duration of each addition of a value in the structure"
puts "envvar: NBVAL=<nb> (default: 100_000) nb of values to add to the structure"
puts "envvar: FIFOSIZE=<nb> (default: 10_000) max number of values in the structure"
exit 0
end
ENV["REPORT_DIR"]?.try { |report_dir| Context.report_dir = report_dir }
Dir.mkdir_p Context.report_dir
if ENV["REPORT_EACH_ADD"]?
FIFO(Int32).new(Context.fifo_size).tap do |fifo|
report_add fifo, Context.nb_values, "fifo_#{Context.fifo_size}_#{Context.nb_values}"
end
EfficientFIFO(Int32).new(Context.fifo_size).tap do |fifo|
report_add fifo, Context.nb_values, "efficientfifo_#{Context.fifo_size}_#{Context.nb_values}"
end
else
Benchmark.ips do |x|
x.report("adding #{Context.nb_values} values, FIFO limited to #{Context.fifo_size}") do
fifo = FIFO(Int32).new Context.fifo_size
add fifo, Context.nb_values
end
x.report("adding #{Context.nb_values} values, EfficientFIFO limited to #{Context.fifo_size}") do
fifo = EfficientFIFO(Int32).new Context.fifo_size
add fifo, Context.nb_values
end
end
end

70
spec/benchmark-lru.cr Normal file
View File

@ -0,0 +1,70 @@
require "benchmark"
require "./utilities.cr"
require "../src/lru.cr"
def add(lru : LRU(Int32) | EfficientLRU(Int32), nb : UInt32)
i = 0
while i < nb
lru << i
i += 1
end
end
def report_add(lru : LRU(Int32) | EfficientLRU(Int32), nb : UInt32, fname : String)
File.open("#{Context.report_dir}/#{fname}.raw", "w") do |file|
i = 0
while i < nb
elapsed_time = perform_something { lru << i }
i += 1
file.puts "#{i} #{elapsed_time.total_nanoseconds}"
end
end
end
class Context
class_property nb_values : UInt32 = 100_000
class_property lru_size : UInt32 = 10_000
class_property report_dir = "results"
end
if nb_values = ENV["NBVAL"]?
Context.nb_values = nb_values.to_u32
end
if lru_size = ENV["LRUSIZE"]?
Context.lru_size = lru_size.to_u32
end
if ARGV.size > 0
puts "Usage: benchmark-lru"
puts ""
puts "envvar: REPORT_DIR=<directory> where to put the results"
puts "envvar: REPORT_EACH_ADD=<any> to report the duration of each addition of a value in the structure"
puts "envvar: NBVAL=<nb> (default: 100_000) nb of values to add to the structure"
puts "envvar: LRUSIZE=<nb> (default: 10_000) max number of values in the structure"
exit 0
end
ENV["REPORT_DIR"]?.try { |report_dir| Context.report_dir = report_dir }
Dir.mkdir_p Context.report_dir
if ENV["REPORT_EACH_ADD"]?
LRU(Int32).new(Context.lru_size).tap do |lru|
report_add lru, Context.nb_values, "lru_#{Context.lru_size}_#{Context.nb_values}"
end
EfficientLRU(Int32).new(Context.lru_size).tap do |lru|
report_add lru, Context.nb_values, "efficientlru_#{Context.lru_size}_#{Context.nb_values}"
end
else
Benchmark.ips do |x|
x.report("adding #{Context.nb_values} values, LRU limited to #{Context.lru_size}") do
lru = LRU(Int32).new Context.lru_size
add lru, Context.nb_values
end
x.report("adding #{Context.nb_values} values, EfficientLRU limited to #{Context.lru_size}") do
lru = EfficientLRU(Int32).new Context.lru_size
add lru, Context.nb_values
end
end
end

View File

@ -45,60 +45,60 @@ describe "tracking inconsistencies between implementations" do
cars_ram0 = SPECDB::RAMOnly(Car).new "-0"
cars_ram1 = SPECDB::RAMOnly(Car).new "-1"
cars_ram2 = SPECDB::RAMOnly(Car).new "-2"
cars_fifo = SPECDB::Common(Car).new "-3", 5
cars_lru = SPECDB::Common(Car).new "-3", 5
uncached_searchby_name, uncached_searchby_color, uncached_searchby_keywords = uncached_indexes cars_ram0
cached_searchby_name, cached_searchby_color, cached_searchby_keywords = cached_indexes cars_ram1
ram_searchby_name, ram_searchby_color, ram_searchby_keywords = ram_indexes cars_ram2
fifo_cached_searchby_name, fifo_cached_searchby_color, fifo_cached_searchby_keywords = cached_indexes cars_fifo
uncached_searchby_name, uncached_searchby_color, uncached_searchby_keywords = uncached_indexes cars_ram0
cached_searchby_name, cached_searchby_color, cached_searchby_keywords = cached_indexes cars_ram1
ram_searchby_name, ram_searchby_color, ram_searchby_keywords = ram_indexes cars_ram2
lru_cached_searchby_name, lru_cached_searchby_color, lru_cached_searchby_keywords = cached_indexes cars_lru
add_cars cars_ram0, 1
add_cars cars_ram1, 1
add_cars cars_ram2, 1
add_cars cars_fifo, 1
add_cars cars_lru, 1
# Searches should be consistent between all implementations of basic indexes, partitions and tags.
# Basic index.
uncached_corvet_car = uncached_searchby_name.get? "Corvet-0"
cached_corvet_car = cached_searchby_name.get? "Corvet-0"
ram_corvet_car = ram_searchby_name.get? "Corvet-0"
fifo_cached_corvet_car = fifo_cached_searchby_name.get? "Corvet-0"
uncached_corvet_car = uncached_searchby_name.get? "Corvet-0"
cached_corvet_car = cached_searchby_name.get? "Corvet-0"
ram_corvet_car = ram_searchby_name.get? "Corvet-0"
lru_cached_corvet_car = lru_cached_searchby_name.get? "Corvet-0"
uncached_corvet_car.should eq cached_corvet_car
uncached_corvet_car.should eq ram_corvet_car
uncached_corvet_car.should eq fifo_cached_corvet_car
uncached_corvet_car.should eq lru_cached_corvet_car
uncached_corvet_car.should eq corvet0
# Partitions.
red_cars = [ Car.new("Corvet-0", "red", [ "shiny", "impressive", "fast", "elegant" ]),
Car.new("Ford-5-0", "red", [ "unknown" ])
]
uncached_red_cars = uncached_searchby_color.get? "red"
cached_red_cars = cached_searchby_color.get? "red"
ram_red_cars = ram_searchby_color.get? "red"
fifo_cached_red_cars = fifo_cached_searchby_color.get? "red"
uncached_red_cars = uncached_searchby_color.get? "red"
cached_red_cars = cached_searchby_color.get? "red"
ram_red_cars = ram_searchby_color.get? "red"
lru_cached_red_cars = lru_cached_searchby_color.get? "red"
uncached_red_cars.sort.should eq cached_red_cars.sort
uncached_red_cars.sort.should eq ram_red_cars.sort
uncached_red_cars.sort.should eq fifo_cached_red_cars.sort
uncached_red_cars.sort.should eq lru_cached_red_cars.sort
uncached_red_cars.sort.should eq red_cars.sort
# Tags.
fast_cars = [ Car.new("Corvet-0", "red", [ "shiny", "impressive", "fast", "elegant" ]),
Car.new("Bullet-GT-0", "blue", [ "shiny", "fast", "expensive" ])
]
uncached_fast_cars = uncached_searchby_keywords.get? "fast"
cached_fast_cars = cached_searchby_keywords.get? "fast"
ram_fast_cars = ram_searchby_keywords.get? "fast"
fifo_cached_fast_cars = fifo_cached_searchby_keywords.get? "fast"
uncached_fast_cars = uncached_searchby_keywords.get? "fast"
cached_fast_cars = cached_searchby_keywords.get? "fast"
ram_fast_cars = ram_searchby_keywords.get? "fast"
lru_cached_fast_cars = lru_cached_searchby_keywords.get? "fast"
uncached_fast_cars.sort.should eq cached_fast_cars.sort
uncached_fast_cars.sort.should eq ram_fast_cars.sort
uncached_fast_cars.sort.should eq fifo_cached_fast_cars.sort
uncached_fast_cars.sort.should eq lru_cached_fast_cars.sort
uncached_fast_cars.sort.should eq fast_cars.sort
cars_ram0.rm_storage_dir
cars_ram1.rm_storage_dir
cars_ram2.rm_storage_dir
cars_fifo.rm_storage_dir
cars_lru.rm_storage_dir
end
end

View File

@ -24,10 +24,10 @@ describe "SPECDB::Common" do
db << car3
db.data.keys.sort.should eq([0, 2, 3] of Int32)
db.fifo.to_s.should eq "[ 3, 0, 2 ]"
db.lru.to_s.should eq "[ 3, 0, 2 ]"
db.delete 2
db.data.keys.sort.should eq([0, 3] of Int32)
db.fifo.to_s.should eq "[ 3, 0 ]"
db.lru.to_s.should eq "[ 3, 0 ]"
end
end

View File

@ -1,54 +0,0 @@
require "spec"
require "../src/fifo.cr"
describe "FIFO" do
it "add and remove values" do
fifo = FIFO(Int32).new 3 # Only 3 allowed entries.
(fifo << 1).should be_nil # there is still room in the fifo
(fifo << 2).should be_nil # there is still room in the fifo
(fifo << 3).should be_nil # last entry without exceeding the allowed size
(fifo << 4).should eq 1 # -> 1 (least recently used data)
(fifo << 4).should be_nil # -> nil (already in the fifo)
(fifo << 2).should be_nil # -> nil (already in the fifo)
(fifo << 5).should eq 3 # -> 3 (least recently used data)
fifo.data.should eq([5, 2, 4] of Int32)
fifo.delete 2
fifo.data.should eq([5, 4] of Int32)
end
end
describe "EfficientFIFO" do
it "add and remove values" do
fifo = EfficientFIFO(Int32).new 3 # Only 3 allowed entries.
(fifo << 1).should be_nil # there is still room in the fifo
(fifo << 2).should be_nil # there is still room in the fifo
(fifo << 3).should be_nil # last entry without exceeding the allowed size
(fifo << 4).should eq 1 # -> 1 (least recently used data)
(fifo << 4).should be_nil # -> nil (already in the fifo)
(fifo << 2).should be_nil # -> nil (already in the fifo)
(fifo << 5).should eq 3 # -> 3 (least recently used data)
fifo.list.to_s.should eq "[ 5, 2, 4 ]"
fifo.delete 2
fifo.list.to_s.should eq "[ 5, 4 ]"
(fifo << 4).should be_nil # -> nil (just a re-order)
fifo.list.to_s.should eq "[ 4, 5 ]"
fifo.delete 5
(fifo << 0).should be_nil
fifo.list.to_s.should eq "[ 0, 4 ]"
(fifo << 1).should be_nil
fifo.list.to_s.should eq "[ 1, 0, 4 ]"
fifo.delete 4
fifo.list.to_s.should eq "[ 1, 0 ]"
fifo.delete 4
fifo.list.to_s.should eq "[ 1, 0 ]"
fifo.list.size.should eq 2
fifo.hash.size.should eq 2
end
end

54
spec/test-lru.cr Normal file
View File

@ -0,0 +1,54 @@
require "spec"
require "../src/lru.cr"
describe "LRU" do
it "add and remove values" do
lru = LRU(Int32).new 3 # Only 3 allowed entries.
(lru << 1).should be_nil # there is still room in the lru
(lru << 2).should be_nil # there is still room in the lru
(lru << 3).should be_nil # last entry without exceeding the allowed size
(lru << 4).should eq 1 # -> 1 (least recently used data)
(lru << 4).should be_nil # -> nil (already in the lru)
(lru << 2).should be_nil # -> nil (already in the lru)
(lru << 5).should eq 3 # -> 3 (least recently used data)
lru.data.should eq([5, 2, 4] of Int32)
lru.delete 2
lru.data.should eq([5, 4] of Int32)
end
end
describe "EfficientLRU" do
it "add and remove values" do
lru = EfficientLRU(Int32).new 3 # Only 3 allowed entries.
(lru << 1).should be_nil # there is still room in the lru
(lru << 2).should be_nil # there is still room in the lru
(lru << 3).should be_nil # last entry without exceeding the allowed size
(lru << 4).should eq 1 # -> 1 (least recently used data)
(lru << 4).should be_nil # -> nil (already in the lru)
(lru << 2).should be_nil # -> nil (already in the lru)
(lru << 5).should eq 3 # -> 3 (least recently used data)
lru.list.to_s.should eq "[ 5, 2, 4 ]"
lru.delete 2
lru.list.to_s.should eq "[ 5, 4 ]"
(lru << 4).should be_nil # -> nil (just a re-order)
lru.list.to_s.should eq "[ 4, 5 ]"
lru.delete 5
(lru << 0).should be_nil
lru.list.to_s.should eq "[ 0, 4 ]"
(lru << 1).should be_nil
lru.list.to_s.should eq "[ 1, 0, 4 ]"
lru.delete 4
lru.list.to_s.should eq "[ 1, 0 ]"
lru.delete 4
lru.list.to_s.should eq "[ 1, 0 ]"
lru.list.size.should eq 2
lru.hash.size.should eq 2
end
end

View File

@ -1,6 +1,6 @@
require "file_utils"
require "json"
require "./fifo.cr" # FIFO class to implement a cache policy.
require "./lru.cr" # LRU class to implement a cache policy.
require "./list.cr" # Double Linked List.
require "./dodb/*" # Databases and indexes (basic indexes, partitions, tags, etc.).

View File

@ -33,13 +33,13 @@
#
# NOTE: fast for frequently requested data and requires a stable (and configurable) amount of memory.
class DODB::Storage::Common(V) < DODB::Storage::Cached(V)
# The *fifo* is an instance of `EfficientFIFO` where the key of the requested data is pushed.
# The *lru* is an instance of `EfficientLRU` where the key of the requested data is pushed.
# In case the number of stored entries exceeds what is allowed, the least recently used entry is removed.
property fifo : EfficientFIFO(Int32)
property lru : EfficientLRU(Int32)
# Initializes the `DODB::Storage::Common` database with a maximum number of entries in the cache.
def initialize(@directory_name : String, max_entries : UInt32)
@fifo = EfficientFIFO(Int32).new max_entries
@lru = EfficientLRU(Int32).new max_entries
Dir.mkdir_p data_path
Dir.mkdir_p locks_directory
@ -47,7 +47,7 @@ class DODB::Storage::Common(V) < DODB::Storage::Cached(V)
end
# Verifies that the value is in cache, or read it on disk.
# Pushes the key in the fifo.
# Pushes the key in the lru.
def [](key : Int32) : V
val = @data[key]?
if val.nil?
@ -55,14 +55,14 @@ class DODB::Storage::Common(V) < DODB::Storage::Cached(V)
val = read file_path key
@data[key] = val
end
push_fifo key
push_lru key
val
end
# Assumes new entries are more requested than old ones.
def []=(key : Int32, value : V)
super key, value
push_fifo key
push_lru key
end
# :inherit:
@ -70,19 +70,19 @@ class DODB::Storage::Common(V) < DODB::Storage::Cached(V)
# Assumes new entries are more requested than old ones.
def <<(item : V)
key = super item
push_fifo key
push_lru key
end
def unsafe_delete(key : Int32)
@fifo.delete key if super key
@lru.delete key if super key
end
def delete(key : Int32)
@fifo.delete key if super key
@lru.delete key if super key
end
private def push_fifo(key : Int32)
if entry_to_remove = @fifo << key
private def push_lru(key : Int32)
if entry_to_remove = @lru << key
@data.delete entry_to_remove
end
end

View File

@ -1,31 +1,31 @@
require "./list.cr"
# This class is a simpler implementation of `EfficientFIFO`, used to implement an eviction policy for data cache
# This class is a simpler implementation of `EfficientLRU`, used to implement an eviction policy for data cache
# for `DODB::Storage::Common`.
# It enables to keep track of recently used data.
#
# **How this works**.
# Each time a value is added in the database, its key is put in this "FIFO" structure.
# Each time a value is added in the database, its key is put in this "LRU" structure.
# In this structure, **values are unique** and adding a value several times is considered as "using the value",
# so it is pushed back at the entry of the FIFO structure, as a new value.
# so it is pushed back at the entry of the LRU structure, as a new value.
# In case the number of entries exceeds what is allowed, the least recently used value is removed.
# ```
# fifo = FIFO(Int32).new 3 # Only 3 allowed entries.
# lru = LRU(Int32).new 3 # Only 3 allowed entries.
#
# pp! fifo << 1 # -> nil (there is still room in the FIFO structure)
# pp! fifo << 2 # -> nil (there is still room in the FIFO structure)
# pp! fifo << 3 # -> nil (last entry without exceeding the allowed size)
# pp! fifo << 4 # -> 1 (least recently used data)
# pp! fifo << 4 # -> nil (already in the structure)
# pp! fifo << 2 # -> nil (already in the structure)
# pp! fifo << 5 # -> 3 (least recently used data)
# pp! lru << 1 # -> nil (there is still room in the LRU structure)
# pp! lru << 2 # -> nil (there is still room in the LRU structure)
# pp! lru << 3 # -> nil (last entry without exceeding the allowed size)
# pp! lru << 4 # -> 1 (least recently used data)
# pp! lru << 4 # -> nil (already in the structure)
# pp! lru << 2 # -> nil (already in the structure)
# pp! lru << 5 # -> 3 (least recently used data)
# ```
#
# The number of entries in the FIFO structure is configurable.
# The number of entries in the LRU structure is configurable.
# WARNING: this implementation becomes slow very fast (0(n) complexity), but doesn't cost much memory.
# WARNING: this *FIFO* class doesn't allow the same value multiple times.
class FIFO(V)
# This array is used as the *fifo structure*.
# WARNING: this *LRU* class doesn't allow the same value multiple times.
class LRU(V)
# This array is used as the *lru structure*.
property data : Array(V)
# Maximum allowed entries in the structure.
@ -35,7 +35,7 @@ class FIFO(V)
@data = Array(V).new
end
# Pushes a value in the FIFO and gets the oldest value whether it exceeds the allowed number of entries.
# Pushes a value in the LRU and gets the oldest value whether it exceeds the allowed number of entries.
# NOTE: `#<<(v : V)` is (almost) the only function since it's enough for the intended use, feel free to improve this.
# WARNING: implementation is extremely simple (3 lines) and not designed to be highly efficient.
def <<(v : V) : V?
@ -58,32 +58,32 @@ end
# It enables to keep track of recently used data.
#
# **How this works**.
# Each time a value is added in the database, its key is put in this "FIFO" structure.
# Each time a value is added in the database, its key is put in this "LRU" structure.
# In this structure, **values are unique** and adding a value several times is considered as "using the value",
# so it is pushed back at the entry of the FIFO structure, as a new value.
# so it is pushed back at the entry of the LRU structure, as a new value.
# In case the number of entries exceeds what is allowed, the least recently used value is removed.
# ```
# fifo = EfficientFIFO(Int32).new 3 # Only 3 allowed entries.
# lru = EfficientLRU(Int32).new 3 # Only 3 allowed entries.
#
# pp! fifo << 1 # -> nil (there is still room in the FIFO structure)
# pp! fifo << 2 # -> nil (there is still room in the FIFO structure)
# pp! fifo << 3 # -> nil (last entry without exceeding the allowed size)
# pp! fifo << 4 # -> 1 (least recently used data)
# pp! fifo << 4 # -> nil (already in the structure)
# pp! fifo << 2 # -> nil (already in the structure)
# pp! fifo << 5 # -> 3 (least recently used data)
# pp! lru << 1 # -> nil (there is still room in the LRU structure)
# pp! lru << 2 # -> nil (there is still room in the LRU structure)
# pp! lru << 3 # -> nil (last entry without exceeding the allowed size)
# pp! lru << 4 # -> 1 (least recently used data)
# pp! lru << 4 # -> nil (already in the structure)
# pp! lru << 2 # -> nil (already in the structure)
# pp! lru << 5 # -> 3 (least recently used data)
# ```
#
# **Implementation details.**
# Contrary to the `FIFO` class, this implementation is time-efficient.
# Contrary to the `LRU` class, this implementation is time-efficient.
# However, this efficiency is a memory tradeoff: all the entries are added to a double-linked list to keep
# track of the order **and** to a hash to perform efficient searches of the values in the double-linked list.
# Thus, all the nodes are added twice, once in the list, once in the hash.
#
# The number of entries in the FIFO structure is configurable.
# The number of entries in the LRU structure is configurable.
# NOTE: this implementation is time-efficient, but costs some memory.
class EfficientFIFO(V)
# Both this list and the hash are used as the *fifo structures*.
class EfficientLRU(V)
# Both this list and the hash are used as the *lru structures*.
# The list preserves the *order* of the entries while the *hash* enables fast retrieval of entries in the list.
property list : DoubleLinkedList(V)
property hash : Hash(V, DoubleLinkedList::Node(V))
@ -93,7 +93,7 @@ class EfficientFIFO(V)
@hash = Hash(V, DoubleLinkedList::Node(V)).new
end
# Pushes a value in the FIFO and gets the oldest value whether it exceeds the allowed number of entries.
# Pushes a value in the LRU and gets the oldest value whether it exceeds the allowed number of entries.
# NOTE: `#<<(v : V)` is (almost) the only function since it's enough for the intended use, feel free to improve this.
def <<(v : V) : V?
if node = hash[v]?