Fast Persistent Dictionary

November 16, 2024 · View on GitHub

Fast Persistent Dictionary, is an implementation of a persistent dictionary in C#. This dictionary-like data structure is designed to persist data to disk, serving as a reliable workhorse for cases where the application requires storing large amounts of data, but also needs to operate with high efficiency and minimize disk space usage.

The dictionary comes with a plethora of features and methods, giving the developer the flexibility to manipulate the data as per their requirements. Some of the key features include options to add or update a record, clear all records, load or save the dictionary, or perform common mathematical operations across the dataset. The dictionary also supports compression to help manage disk usage and provides a GROBUF Serializer for data serialization, facilitating efficient storage of complex objects.

This dictionary makes use of the .NET IEnumerable and ISerializable interfaces, allowing it to be used in various scenarios where these interfaces are required, for example, in .NET collections or serialization operations.

The project also provides exception handling and recovery features. A dispose mechanism is incorporated ensuring that all resources are released when the instance is no longer required, enhancing the project’s stability and resilience.

Fast Persistent Dictionary is an open-source project crafted with care, providing a robust and efficient persistent dictionary in C#. It's an invaluable tool when building applications requiring efficient, reliable, and persistent data storage.

Fast Persistent Dictionary Features

  • Single File Database: the In use database and the saved and loadable format is all compiled in a single file.
  • Performance: Fast Persistent Dictionary supports a high rate of updates and retrieves. Typically surpassing ESENT by a wide margin.
  • Simplicity: a FastPersistentDictionary looks and behaves like the .NET Dictionary class. No extra method calls are required.
  • Concurrency: each data structure can be accessed by multiple threads.
  • Scale: Values can be up to 2gb in size.
  • No Serialization Flags: Any key or value can be used as long as it is serializable by Grobuf.

Benchmarks


BenchmarkDotNet v0.14.0, Windows 11 (10.0.22631.4037/23H2/2023Update/SunValley3)
Intel Core i7-10875H CPU 2.30GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK 8.0.200
  [Host]     : .NET 8.0.2 (8.0.224.6711), X64 RyuJIT AVX2 [AttachedDebugger]
  Job-URLKUQ : .NET 8.0.2 (8.0.224.6711), X64 RyuJIT AVX2

InvocationCount=1  UnrollFactor=1  

MethodNMeanErrorStdDevMedian
FastPersistentDictionary_Add1035.712 μs1.0418 μs2.8869 μs34.700 μs
StandardDictionary_Add104.615 μs0.0963 μs0.1350 μs4.600 μs
EsentPersistentDictionary_Add10169.886 μs5.2269 μs14.5704 μs164.950 μs
FastPersistentDictionary_Get1029.895 μs0.9572 μs2.5550 μs30.300 μs
StandardDictionary_Get103.429 μs0.0810 μs0.2312 μs3.350 μs
EsentPersistentDictionary_Get1049.512 μs1.2037 μs3.2544 μs49.300 μs
FastPersistentDictionary_Remove104.667 μs0.0940 μs0.1742 μs4.700 μs
StandardDictionary_Remove103.397 μs0.1454 μs0.3981 μs3.300 μs
EsentPersistentDictionary_Remove1085.437 μs1.6982 μs3.5821 μs85.650 μs
FastPersistentDictionary_Add100246.110 μs4.8914 μs7.1697 μs245.000 μs
StandardDictionary_Add10020.167 μs0.3757 μs0.2934 μs20.200 μs
EsentPersistentDictionary_Add1001,387.178 μs24.7951 μs41.4270 μs1,383.100 μs
FastPersistentDictionary_Get100103.832 μs6.6457 μs19.1743 μs107.050 μs
StandardDictionary_Get10010.550 μs0.2117 μs0.3037 μs10.500 μs
EsentPersistentDictionary_Get100376.640 μs7.4826 μs20.4835 μs375.100 μs
FastPersistentDictionary_Remove10017.328 μs0.4785 μs1.2689 μs17.200 μs
StandardDictionary_Remove10010.529 μs0.2106 μs0.3217 μs10.600 μs
EsentPersistentDictionary_Remove100840.953 μs16.4776 μs30.5424 μs839.100 μs
FastPersistentDictionary_Add10002,308.243 μs44.5800 μs53.0693 μs2,306.800 μs
StandardDictionary_Add1000177.095 μs3.4542 μs3.9779 μs175.400 μs
EsentPersistentDictionary_Add100012,976.556 μs491.4843 μs1,441.4385 μs12,218.100 μs
FastPersistentDictionary_Get1000790.672 μs32.5535 μs94.4435 μs814.500 μs
StandardDictionary_Get100087.707 μs1.2218 μs1.0831 μs87.500 μs
EsentPersistentDictionary_Get10003,355.380 μs92.6984 μs264.4738 μs3,264.600 μs
FastPersistentDictionary_Remove1000200.779 μs5.6056 μs16.0834 μs196.950 μs
StandardDictionary_Remove1000106.619 μs5.9200 μs15.4915 μs106.600 μs
EsentPersistentDictionary_Remove10007,758.348 μs203.6357 μs580.9842 μs7,583.750 μs
FastPersistentDictionary_Add1000022,342.905 μs910.8584 μs2,671.3902 μs21,158.300 μs
StandardDictionary_Add100001,814.397 μs120.8519 μs356.3347 μs1,636.200 μs
EsentPersistentDictionary_Add10000122,277.346 μs2,166.8262 μs4,570.5723 μs122,271.200 μs
FastPersistentDictionary_Get100004,522.615 μs242.8226 μs668.8040 μs4,239.550 μs
StandardDictionary_Get10000710.010 μs50.2010 μs140.7690 μs662.100 μs
EsentPersistentDictionary_Get1000032,145.620 μs631.8258 μs591.0102 μs31,998.800 μs
FastPersistentDictionary_Remove10000876.048 μs73.5746 μs215.7816 μs742.500 μs
StandardDictionary_Remove10000869.499 μs62.9090 μs184.5012 μs835.700 μs
EsentPersistentDictionary_Remove1000076,566.850 μs1,515.7748 μs1,745.5685 μs76,654.200 μs
FastPersistentDictionary_Add100000229,172.487 μs4,581.7610 μs5,794.4611 μs228,866.700 μs
StandardDictionary_Add10000032,888.018 μs1,342.1441 μs3,696.6548 μs31,588.850 μs
EsentPersistentDictionary_Add1000001,249,948.586 μs19,460.0175 μs17,250.7987 μs1,249,580.750 μs
FastPersistentDictionary_Get10000047,154.111 μs884.0972 μs1,501.2643 μs47,125.200 μs
StandardDictionary_Get10000013,312.728 μs287.6890 μs830.0487 μs13,184.250 μs
EsentPersistentDictionary_Get100000328,134.381 μs5,873.8948 μs4,904.9682 μs328,413.050 μs
FastPersistentDictionary_Remove1000008,773.308 μs175.0641 μs227.6328 μs8,712.500 μs
StandardDictionary_Remove10000013,193.624 μs335.9221 μs979.9007 μs13,229.400 μs
EsentPersistentDictionary_Remove100000803,621.169 μs15,802.8693 μs15,520.5307 μs800,826.950 μs
FastPersistentDictionary_Add10000002,295,026.753 μs35,852.7067 μs33,536.6441 μs2,292,892.900 μs
StandardDictionary_Add1000000519,974.638 μs14,354.2653 μs41,872.0685 μs509,290.700 μs
EsentPersistentDictionary_Add100000013,038,880.720 μs82,744.1640 μs77,398.9424 μs13,047,758.000 μs
FastPersistentDictionary_Get1000000493,184.675 μs9,822.8597 μs11,312.0199 μs492,070.800 μs
StandardDictionary_Get1000000151,635.342 μs3,024.3169 μs3,361.5202 μs152,587.600 μs
EsentPersistentDictionary_Get10000003,279,782.257 μs55,003.8343 μs51,450.6208 μs3,301,896.650 μs
FastPersistentDictionary_Remove1000000112,024.419 μs2,202.4345 μs3,087.5058 μs112,387.200 μs
StandardDictionary_Remove1000000152,094.443 μs2,997.1979 μs4,486.0651 μs152,306.050 μs
EsentPersistentDictionary_Remove10000008,093,507.270 μs43,590.9941 μs40,775.0430 μs8,090,683.550 μs

Quick Start Demo

Setting up a FastPersistentDictionary is simple and straightforward and matches the implementation of a standard Dictionary in C# as much as possible with various additional extension methods.

int N = 100000;
private FastPersistentDictionary<string, string> fastPersistentDictionary= new FastPersistentDictionary<string, string>(path: fastPersistentDictionary);
//Strings for example but can work with any type.
 for (int i = 0; i < N; i++)
 {
     string key = $"Key{i}";
     string value = $"Value{i}";
     FastPersistentDictionary.Add(key, value); 
 }


 for (int i = 0; i < N; i++)
 {
     string key = $"Key{i}";
     string value;
     bool found = FastPersistentDictionary.TryGetValue(key, out value); 
 }

Table of Contents

Usage

(Back to top)

FastPersistentDictionary

The FastPersistentDictionary is a highly efficient and flexible persistent dictionary for C# that provides a range of dictionary functionalities while ensuring data persistence on disk. Developed by James Grice, this library is designed to be utilized just like any other dictionary, but with the added benefit of persistency and reduced disk space usage.

Installation

To install the FastPersistentDictionary library, you can download it from GitHub and include it in your project.

Quick Start Guide

1. Creating a FastPersistentDictionary

Here's a simple example to create a FastPersistentDictionary:

using FastPersistentDictionary;

var dictionary = new FastPersistentDictionary<string, int>("path/to/your/dictionary/file");

2. Adding Entries

To add entries to the dictionary:

dictionary.Add("key1", 100);
dictionary.Add("key2", 200);

3. Accessing Entries

The FastPersistentDictionary can be accessed like a regular dictionary:

int value = dictionary["key1"];
Console.WriteLine(value);  // Output: 100

4. Updating Entries

Updating existing entries is straightforward:

dictionary["key1"] = 150;

5. Checking Existence of Keys and Values

You can check if a key or a value exists in the dictionary:

bool hasKey = dictionary.ContainsKey("key1");    // true
bool hasValue = dictionary.ContainsValue(200);   // true

6. Removing Entries

Removing entries from the dictionary:

dictionary.Remove("key1");

7. Iterating Over Entries

You can easily iterate over the entries in the dictionary:

foreach (var kvp in dictionary)
{
    Console.WriteLine($"{kvp.Key} : {kvp.Value}");
}

8. Compacting the Database

To minimize the file size on the disk manually, you can compact the database:

dictionary.CompactDatabaseFile();

9. Saving and Loading the Dictionary

You can save the current state of the dictionary to a file:

dictionary.SaveDictionary("path/to/save/file");

And you can load an existing dictionary from a file:

var loadedDictionary = new FastPersistentDictionary<string, int>("path/to/save/file");
loadedDictionary.LoadDictionary("path/to/save/file");

10. Disposing the Dictionary

Make sure to dispose of the dictionary properly when it is no longer needed:

dictionary.Dispose();

Advanced Features

1. Bulk Operations

You can perform bulk-read operations:

var keys = new[] { "key1", "key2" };
var values = dictionary.GetBulk(keys);

2. Mathematical Operations

The FastPersistentDictionary supports mathematical operations:

int min = dictionary.Min();
int sum = dictionary.Sum();
int max = dictionary.Max();
double average = dictionary.Average(kvp => kvp.Value);

3. Query and Filter

Advanced querying, filtering, and extracting subsets of the dictionary:

var subset = dictionary.GetSubset((key, value) => value > 100);

bool any = dictionary.Any(kvp => kvp.Value > 150);
bool all = dictionary.All(kvp => kvp.Value > 50);

4. Importing Other Dictionaries

Import entries from another dictionary:

var anotherDictionary = new Dictionary<string, int>
{
    { "key3", 300 },
    { "key4", 400 }
};
dictionary.Merge(anotherDictionary);

Summary

The FastPersistentDictionary offers a wide range of functionalities for persistent data storage with minimal overhead on disk space. With the ease of use of any other dictionary and options for advanced operations, it is a powerful tool for large datasets and projects in C#.

Contribute

(Back to top)

Your support is invaluable and truly welcomed! Here's how you can contribute:

  • Write Tests and Benchmarks: Enhance code reliability and performance evaluation.
  • Enhance Documentation: Assist others in comprehending and effectively using FastPersistentDictionary.
  • Submit Feature Requests and Bug Reports: Suggest new ideas and report issues to help refine FastPersistentDictionary.
  • Optimize Performance: Offer optimizations and improvements to existing features..

License

(Back to top)

MIT license