Skip to content

Commit

Permalink
WIP: crazy experiment
Browse files Browse the repository at this point in the history
  • Loading branch information
Ostrzyciel committed Jul 18, 2024
1 parent 5f75b05 commit 34d80ec
Show file tree
Hide file tree
Showing 2 changed files with 238 additions and 0 deletions.
110 changes: 110 additions & 0 deletions core/src/main/java/eu/ostrzyciel/jelly/core/NewEncoderLookup.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package eu.ostrzyciel.jelly.core;

import java.util.HashMap;

public class NewEncoderLookup {
public final static class LookupEntry {
public int getId;
public int setId;
public boolean newEntry;
public int serial = 1;

public LookupEntry(int getId, int setId) {
this.getId = getId;
this.setId = setId;
}

public LookupEntry(int getId, int setId, boolean newEntry) {
this.getId = getId;
this.setId = setId;
this.newEntry = newEntry;
}
}

HashMap<String, LookupEntry> map = new HashMap<>();
// Layout: [left, right, serial]
// Head: table[1]
int[] table;
int tail;
final int size;
int used;
int lastSetId;
String[] names;

LookupEntry entryForReturns = new LookupEntry(0, 0, true);

public NewEncoderLookup(int size) {
this.size = size;
table = new int[(size + 1) * 3];
names = new String[size + 1];
}

public void onAccess(int id) {
int base = id * 3;
if (base == tail) {
return;
}
int left = table[base];
int right = table[base + 1];
// Set left's right to our right
table[left + 1] = right;
// Set right's left to our left
table[right] = left;
// Set our left to the tail
table[base] = tail;
// Set the tail's right to us
table[tail + 1] = base;
// Update the tail
tail = base;
}

public LookupEntry addEntry(String key) {
var value = map.get(key);
if (value != null) {
onAccess(value.getId);
return value;
}

int id;
if (used < size) {
id = ++used;
int base = id * 3;
// Set the left to the tail
table[base] = tail;
// Right is already 0
// table[base + 1] = 0;
// Serial is zero, set it to 0+1 = 1
table[base + 2] = 1;
// Set the tail's right to us
table[tail + 1] = base;
tail = base;
names[id] = key;
map.put(key, new LookupEntry(id, id));
// setId is 0 because we are adding a new entry sequentially
entryForReturns.setId = 0;
// .serial is already 1 by default
// entryForReturns.serial = 1;
} else {
int base = table[1];
// Evict the least recently used
id = base / 3;
// Remove the entry from the map
LookupEntry oldEntry = map.remove(names[id]);
oldEntry.getId = id;
oldEntry.setId = id;
int serial = table[base + 2] + 1;
oldEntry.serial = serial;
table[base + 2] = serial;
// Insert the new entry
names[id] = key;
map.put(key, oldEntry);
// Update the table
onAccess(id);
entryForReturns.serial = serial;
entryForReturns.setId = lastSetId + 1 == id ? 0 : id;
}
entryForReturns.getId = id;
lastSetId = id;
return entryForReturns;
}
}
128 changes: 128 additions & 0 deletions core/src/test/scala/eu/ostrzyciel/jelly/core/EncoderLookupSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package eu.ostrzyciel.jelly.core

import org.scalatest.Inspectors
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec

import scala.util.Random

class EncoderLookupSpec extends AnyWordSpec, Matchers:
Random.setSeed(123)

"encoder lookup" should {
"add new entries up to capacity" in {
val lookup = NewEncoderLookup(4)
for i <- 1 to 4 do
val v = lookup.addEntry(s"v$i")
v.getId should be (i)
v.setId should be (0)
v.newEntry should be (true)
v.serial should be (1)
}

"retrieve entries" in {
val lookup = NewEncoderLookup(4)
for i <- 1 to 4 do
lookup.addEntry(s"v$i")
for i <- 1 to 4 do
val v = lookup.addEntry(s"v$i")
v.getId should be (i)
v.setId should be (i)
v.newEntry should be (false)
v.serial should be (1)
}

"retrieve entries many times, in random order" in {
val lookup = NewEncoderLookup(50)
for i <- 1 to 50 do
lookup.addEntry(s"v$i")
for _ <- 1 to 20 do
for i <- Random.shuffle(1 to 50) do
val v = lookup.addEntry(s"v$i")
v.getId should be (i)
v.setId should be (i)
v.newEntry should be (false)
v.serial should be (1)
}

"overwrite existing entries, from oldest to newest" in {
val lookup = NewEncoderLookup(4)
for i <- 1 to 4 do
lookup.addEntry(s"v$i")

val v = lookup.addEntry("v5")
v.getId should be (1)
v.setId should be (1)
v.newEntry should be (true)
v.serial should be (2)

for i <- 6 to 8 do
val v = lookup.addEntry(s"v$i")
v.getId should be (i - 4)
v.setId should be (0)
v.newEntry should be (true)
v.serial should be (2)
}

"overwrite existing entries in order, many times" in {
val lookup = NewEncoderLookup(17)
for i <- 1 to 17 do
lookup.addEntry(s"v$i")

for k <- 2 to 23 do
val v = lookup.addEntry(s"v1 $k")
v.getId should be (1)
v.setId should be (1)
v.newEntry should be (true)
v.serial should be (k)
for i <- 2 to 17 do
val v = lookup.addEntry(s"v$i $k")
v.getId should be (i)
v.setId should be (0)
v.newEntry should be (true)
v.serial should be (k)
}

"pass random stress test (1)" in {
val lookup = NewEncoderLookup(100)
val frequentSet = (1 to 10).map(i => s"v$i")
frequentSet.foreach(lookup.addEntry)

for i <- 1 to 50 do
for fIndex <- 1 to 10 do
val v = lookup.addEntry(frequentSet(fIndex - 1))
v.getId should be (fIndex)
v.setId should be (fIndex)
v.newEntry should be (false)
v.serial should be (1)

for _ <- 1 to 80 do
val v = lookup.addEntry(s"r${Random.nextInt(200) + 1}")
v.getId should be > 10
if v.setId != 0 then
v.setId should be > 10
}

"pass random stress test (2)" in {
val lookup = NewEncoderLookup(113)
for i <- 1 to 20 do
lookup.addEntry(s"v$i")
for _ <- 1 to 1000 do
val id = Random.nextInt(20) + 1
val v = lookup.addEntry(s"v$id")
v.getId should be (id)
if v.setId != 0 then
v.setId should be (id)
v.newEntry should be (false)
else
v.newEntry should be (true)
v.serial should be (1)
}

"pass random stress test (3)" in {
val lookup = NewEncoderLookup(1023)
for _ <- 1 to 100_000 do
val v = lookup.addEntry(s"v${Random.nextInt(10_000) + 1}")
v.getId should be > 0
}
}

0 comments on commit 34d80ec

Please sign in to comment.