From 38f54cdf7727d9c95f645d780a37a9ba55154985 Mon Sep 17 00:00:00 2001 From: Philippe Pittoli Date: Fri, 13 Dec 2024 23:31:25 +0100 Subject: [PATCH] Fix naming: FIFO -> LRU --- spec/benchmark-cars.cr | 62 ++++++++++++++++----------------- spec/benchmark-fifo.cr | 70 -------------------------------------- spec/benchmark-lru.cr | 70 ++++++++++++++++++++++++++++++++++++++ spec/test-cars.cr | 44 ++++++++++++------------ spec/test-common.cr | 4 +-- spec/test-fifo.cr | 54 ----------------------------- spec/test-lru.cr | 54 +++++++++++++++++++++++++++++ src/dodb.cr | 2 +- src/dodb/storage/common.cr | 22 ++++++------ src/{fifo.cr => lru.cr} | 62 ++++++++++++++++----------------- 10 files changed, 222 insertions(+), 222 deletions(-) delete mode 100644 spec/benchmark-fifo.cr create mode 100644 spec/benchmark-lru.cr delete mode 100644 spec/test-fifo.cr create mode 100644 spec/test-lru.cr rename src/{fifo.cr => lru.cr} (61%) diff --git a/spec/benchmark-cars.cr b/spec/benchmark-cars.cr index a3c06c9..10475ea 100644 --- a/spec/benchmark-cars.cr +++ b/spec/benchmark-cars.cr @@ -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 diff --git a/spec/benchmark-fifo.cr b/spec/benchmark-fifo.cr deleted file mode 100644 index 4effcbf..0000000 --- a/spec/benchmark-fifo.cr +++ /dev/null @@ -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= where to put the results" - puts "envvar: REPORT_EACH_ADD= to report the duration of each addition of a value in the structure" - puts "envvar: NBVAL= (default: 100_000) nb of values to add to the structure" - puts "envvar: FIFOSIZE= (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 diff --git a/spec/benchmark-lru.cr b/spec/benchmark-lru.cr new file mode 100644 index 0000000..a8d44c4 --- /dev/null +++ b/spec/benchmark-lru.cr @@ -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= where to put the results" + puts "envvar: REPORT_EACH_ADD= to report the duration of each addition of a value in the structure" + puts "envvar: NBVAL= (default: 100_000) nb of values to add to the structure" + puts "envvar: LRUSIZE= (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 diff --git a/spec/test-cars.cr b/spec/test-cars.cr index f93981e..6829faa 100644 --- a/spec/test-cars.cr +++ b/spec/test-cars.cr @@ -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 diff --git a/spec/test-common.cr b/spec/test-common.cr index bbd78fd..5a4f1bf 100644 --- a/spec/test-common.cr +++ b/spec/test-common.cr @@ -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 diff --git a/spec/test-fifo.cr b/spec/test-fifo.cr deleted file mode 100644 index 5e30118..0000000 --- a/spec/test-fifo.cr +++ /dev/null @@ -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 diff --git a/spec/test-lru.cr b/spec/test-lru.cr new file mode 100644 index 0000000..84b7c8e --- /dev/null +++ b/spec/test-lru.cr @@ -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 diff --git a/src/dodb.cr b/src/dodb.cr index a656200..98c9126 100644 --- a/src/dodb.cr +++ b/src/dodb.cr @@ -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.). diff --git a/src/dodb/storage/common.cr b/src/dodb/storage/common.cr index 36bb761..abd9446 100644 --- a/src/dodb/storage/common.cr +++ b/src/dodb/storage/common.cr @@ -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 diff --git a/src/fifo.cr b/src/lru.cr similarity index 61% rename from src/fifo.cr rename to src/lru.cr index 77801a0..39333f1 100644 --- a/src/fifo.cr +++ b/src/lru.cr @@ -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]?