What is a cache?

As you surely know, a cache is a component that stores data as it were a little database. Original data are always stored in a file, typically a relational database, and accessing it every time you need them could negatively affect your overall application performance. On the other hand, accesses to the cache are very fast because caches are in-memory components.

So, how can we use a cache? Have a look at the following flowchart:

flowchart.jpg

Fig. 1

Using a cache, you need to access the database only when the cache doesn't contain the requested data, improving your application performance.

How BabyCache works

There are many cache libraries on the Internet, but I preferred to build my own library, my very simple library, I decided to call it "BabyCache" just for that reason. It's a cache of a fixed dimension and you can decide that dimension when you create a new instance. Let's see how a 10-element cache works:

cache1.jpg

Fig. 2

Fig. 2 shows a 10-element cache with 9 elements in it. Every cache element is composed by a key-value pair and the keys are represented in the figure. We can imagine the BabyCache as a set of elements ordered by the time the elements are stored (every element has been stored before than the elements on its left and after than the elements on its right; so the newest element is in the first position on the left and the oldest element is in the last position on the right). Two operations can be performed on our cache: you can insert a new element or you can ask the cache for a stored element. Let's have a look at these simple operations:

If you are a simple user, you can stop reading this article right now. The curious people can go ahead and have a look at the BabyCache's code.

The code

The BabyCache project is composed of three files:

This operation is a little heavier than the Add one. Comparing the specified key with the current object's key, adding the new element in the first position and returning the element's value are all O(1) operations, but removing the element from the cache is a O(n) operation (the Remove instruction will be executed at most one time, if the specified key is found). The foreach instruction iterates over the cache's elements so it is a O(n) operation. The overall operation is then O(2n): a O(n) iteration and a O(n) operation performed one time only during the iteration.

For educational purpose only, I created another method for fetching the desired object's value. That method just creates a new LinkedList and adds the old list's elements as it iterates over them. The desired element, if found, is not suddenly added to the new list but stored and added at the end of the iteration (the element has to be refreshed so it is moved to the first position)

BabyCache10.gif

All the operations in the foreach instruction are all O(1) operations and so the operations performed if the element is found are. The overall complexity is so O(n), better than the previous method. In this case, however, the AddLast method is called n times, differently from the previous method when an element was added to the collection at most one time. Here are the average execution times of the two methods above on two different computers (PC#1 and PC#2) and on two different types of caches (<string, string> and <int, string>) both of 1,000,000 elements (so many elements for a cache, I know, but I needed them to have not short execution times):

<string, string>
PC#1

<string, string>
PC#2

<int, string>
PC#1

<int, string>
PC#2

GetObject

193 ms

102 ms

155 ms

88 ms

GetObjectByDuplicating

623 ms

307 ms

1262 ms

464 ms

We can observe here than the GetObject method is always faster than the GetObjectByDuplicating one; so because in the first method an element is added to the collection at most one time.

Concurrency

What about concurrency? What would happen if you used BabyCache in a web application where multiple threads might access the cache at the same time? If you use a single BabyCache instance (for example if you use BabyCache as a static attribute of a class) what would happen if one thread is reading while another one is deleting an element from the cache? That was the situation I had while developing a new feature in my website about the Italian fantasy soccer: information about players are not related to a single user, so I decided to store the recently used players data into a shared cache. Two unwanted situations might happen while using a shared BabyCache instance in a web application:

ConcurrentBabyCache

I created another version of BabyCache to avoid the concurrency problems I've just explained in the previous paragraph. It's a simple subclass of BabyCacheManager and its code is very clear.

BabyCache13.gif

As you can see in the picture above, this class uses the BabyCacheManager's methods calling them inside a lock block, so one thread at a time can access the cache.

Conclusion

There are probably many ways to improve my BabyCache's performance. For example, a new collection type might be used in place of the LinkedList. So, any suggestion and correction are welcome! That's all, enjoy your BabyCache.