Skip to content

Commit

Permalink
Update README with performance comparison
Browse files Browse the repository at this point in the history
  • Loading branch information
marpit19 committed Sep 8, 2024
1 parent 237114f commit 95414b7
Show file tree
Hide file tree
Showing 3 changed files with 236 additions and 88 deletions.
131 changes: 43 additions & 88 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,119 +28,74 @@ go get github.com/marpit19/goquickmap

## Usage

### QuickMap
Note: All keys in the current implementation must be strings.

### QuickMap
```go
import "github.com/marpit19/goquickmap/pkg/quickmap"

// Create a new QuickMap with default capacity
m := quickmap.New()

// Create a QuickMap with a specific initial capacity
m := quickmap.NewWithCapacity(1000)

// Insert a key-value pair
m.Insert("key", "value")

// Get a value
m.Insert("key", "value") // key must be a string
value, exists := m.Get("key")

// Delete a key-value pair
m.Delete("key")

// Batch insert
pairs := map[string]interface{}{
"key1": "value1",
"key2": "value2",
}
m.InsertMany(pairs)

// Batch delete
keys := []string{"key1", "key2"}
m.DeleteMany(keys)
```
## Performance

### QuickSet
GoQuickMap offers significant performance improvements over built-in Go maps and popular third-party set implementations. Here's a comparison based on 1,000,000 operations:
```go
import "github.com/marpit19/goquickmap/pkg/quickset"
### Map Operations
// Create a new QuickSet with default capacity
s := quickset.New()
| Operation | Built-in Map | QuickMap | Improvement |
|--------------|--------------|-------------|-------------|
| Insert | 271.17ms | 236.95ms | 12.6% faster |
| Get | 153.89ms | 83.68ms | 45.6% faster |
| Delete | 188.82ms | 70.47ms | 62.7% faster |
// Create a QuickSet with a specific initial capacity
s := quickset.NewWithCapacity(1000)
QuickMap also supports efficient batch operations:
- Batch Insert (10,000 items): 638.54µs
- Batch Delete (10,000 items): 106.5µs
// Add an element
s.Add("element")
### Set Operations
// Check if an element exists
exists := s.Contains("element")
| Operation | golang-set | QuickSet | Improvement |
|--------------|--------------|-------------|-------------|
| Add | 231.33ms | 211.49ms | 8.6% faster |
| Contains | 258.16ms | 88.20ms | 65.8% faster |
| Remove | 232.36ms | 78.90ms | 66.0% faster |
// Remove an element
s.Remove("element")
QuickSet also supports efficient batch operations:
- Batch Add (10,000 items): 1.26ms
- Batch Remove (10,000 items): 111.67µs
// Batch add
elements := []string{"elem1", "elem2"}
s.AddMany(elements)
### Analysis
// Batch remove
s.RemoveMany(elements)
```
1. **Superior Performance**: GoQuickMap consistently outperforms built-in maps and popular set implementations across all operations.
### QuickDict
2. **Significant Speedup for Lookups and Deletions**: QuickMap and QuickSet show dramatic improvements in Get/Contains (45-65% faster) and Delete/Remove operations (62-66% faster).
```go
import "github.com/marpit19/goquickmap/pkg/quickdict"
3. **Efficient Insertions**: Both QuickMap and QuickSet demonstrate faster insertion times compared to their counterparts.
// Create a new QuickDict with default capacity
d := quickdict.New()
4. **Batch Operations**: The library offers highly efficient batch operations, allowing for rapid insertion and deletion of multiple items simultaneously.
// Create a QuickDict with a specific initial capacity
d := quickdict.NewWithCapacity(1000)
5. **Consistent Advantage**: The performance advantage is maintained across different types of operations, indicating a well-optimized underlying structure.
// Set a key-value pair
d.Set("key", "value")
These results demonstrate that GoQuickMap is an excellent choice for applications requiring high-performance hash tables, maps, or sets, especially those dealing with large datasets or frequent lookup and deletion operations.
// Get a value
value, exists := d.Get("key")
## Current Limitations and Future Plans
// Delete a key-value pair
d.Delete("key")
### String Keys Only
The current implementation of GoQuickMap, including QuickMap, QuickSet, and QuickDict, only supports string keys. This design choice was made to optimize performance for string-based keys, which are common in many applications.
// Batch set
pairs := map[string]interface{}{
"key1": "value1",
"key2": "value2",
}
d.SetMany(pairs)
#### Implications:
1. **Use Case Focus**: The library is currently best suited for applications that primarily use string identifiers or textual data as keys.
2. **Performance Optimization**: The string-specific implementation allows for optimizations that may not be possible with a more generic approach.
3. **Benchmark Context**: The performance comparisons provided are specifically for string keys and may vary for other types of keys.
// Batch delete
keys := []string{"key1", "key2"}
d.DeleteMany(keys)
```
### Future Considerations
We acknowledge that supporting only string keys is a limitation. Potential future enhancements may include:
## Performance
1. **Generic Key Support**: Implementing support for generic types as keys, allowing for greater flexibility.
2. **Numeric Key Optimization**: Adding specialized implementations for common numeric types (int, int64, etc.) that could potentially offer even better performance for these types.
3. **Custom Hash Functions**: Allowing users to provide custom hash functions for their specific key types.
GoQuickMap is designed for high performance. Here are some benchmark results on an Apple M3 Pro:

- QuickMap Insert: ~383 ns/op
- QuickMap Get: ~26 ns/op
- QuickMap Delete: ~26 ns/op
- QuickMap InsertMany (1000 items): ~172 µs/op
- QuickMap DeleteMany (1000 items): ~4.2 µs/op

- QuickSet Add: ~316 ns/op
- QuickSet Contains: ~25 ns/op
- QuickSet Remove: ~26 ns/op
- QuickSet AddMany (1000 items): ~115 µs/op
- QuickSet RemoveMany (1000 items): ~4.2 µs/op

- QuickDict Set: ~360 ns/op
- QuickDict Get: ~26 ns/op
- QuickDict Delete: ~25 ns/op
- QuickDict SetMany (1000 items): ~171 µs/op
- QuickDict DeleteMany (1000 items): ~4.2 µs/op
We welcome feedback and contributions from the community regarding these potential improvements. If you have specific use cases that require non-string keys, please open an issue to discuss your needs.
## Contributing
Expand Down
193 changes: 193 additions & 0 deletions cmd/performance/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
/*
package main
import (
"fmt"
"math/rand"
"runtime"
"strconv"
"time"
mapset "github.com/deckarep/golang-set/v2"
"github.com/marpit19/goquickmap/pkg/quickmap"
"github.com/marpit19/goquickmap/pkg/quickset"
)
const (
numOperations = 1000000
numBatchOperations = 10000
)
func main() {
fmt.Println("Performance Comparison")
fmt.Printf("Number of operations: %d\n", numOperations)
fmt.Printf("Number of batch operations: %d\n", numBatchOperations)
compareMap()
compareSet()
}
func compareMap() {
fmt.Println("\n--- Map Comparison ---")
// Built-in map
start := time.Now()
m := make(map[string]int)
for i := 0; i < numOperations; i++ {
key := strconv.Itoa(i)
m[key] = i
}
builtinInsertTime := time.Since(start)
start = time.Now()
for i := 0; i < numOperations; i++ {
key := strconv.Itoa(i)
_ = m[key]
}
builtinGetTime := time.Since(start)
start = time.Now()
for i := 0; i < numOperations; i++ {
key := strconv.Itoa(i)
delete(m, key)
}
builtinDeleteTime := time.Since(start)
// QuickMap
start = time.Now()
qm := quickmap.New()
for i := 0; i < numOperations; i++ {
key := strconv.Itoa(i)
qm.Insert(key, i)
}
quickmapInsertTime := time.Since(start)
start = time.Now()
for i := 0; i < numOperations; i++ {
key := strconv.Itoa(i)
_, _ = qm.Get(key)
}
quickmapGetTime := time.Since(start)
start = time.Now()
for i := 0; i < numOperations; i++ {
key := strconv.Itoa(i)
qm.Delete(key)
}
quickmapDeleteTime := time.Since(start)
// Batch operations
batchKeys := make([]string, numBatchOperations)
batchMap := make(map[string]interface{}, numBatchOperations)
for i := 0; i < numBatchOperations; i++ {
key := strconv.Itoa(rand.Intn(numOperations))
batchKeys[i] = key
batchMap[key] = i
}
start = time.Now()
qm.InsertMany(batchMap)
quickmapBatchInsertTime := time.Since(start)
start = time.Now()
qm.DeleteMany(batchKeys)
quickmapBatchDeleteTime := time.Since(start)
// Print results
fmt.Println("Built-in map:")
fmt.Printf(" Insert: %v\n", builtinInsertTime)
fmt.Printf(" Get: %v\n", builtinGetTime)
fmt.Printf(" Delete: %v\n", builtinDeleteTime)
fmt.Println("QuickMap:")
fmt.Printf(" Insert: %v\n", quickmapInsertTime)
fmt.Printf(" Get: %v\n", quickmapGetTime)
fmt.Printf(" Delete: %v\n", quickmapDeleteTime)
fmt.Printf(" Batch Insert (%d items): %v\n", numBatchOperations, quickmapBatchInsertTime)
fmt.Printf(" Batch Delete (%d items): %v\n", numBatchOperations, quickmapBatchDeleteTime)
}
func compareSet() {
fmt.Println("\n--- Set Comparison ---")
// golang-set
start := time.Now()
s := mapset.NewSet[string]()
for i := 0; i < numOperations; i++ {
s.Add(strconv.Itoa(i))
}
mapsetAddTime := time.Since(start)
start = time.Now()
for i := 0; i < numOperations; i++ {
s.Contains(strconv.Itoa(i))
}
mapsetContainsTime := time.Since(start)
start = time.Now()
for i := 0; i < numOperations; i++ {
s.Remove(strconv.Itoa(i))
}
mapsetRemoveTime := time.Since(start)
// QuickSet
start = time.Now()
qs := quickset.New()
for i := 0; i < numOperations; i++ {
qs.Add(strconv.Itoa(i))
}
quicksetAddTime := time.Since(start)
start = time.Now()
for i := 0; i < numOperations; i++ {
qs.Contains(strconv.Itoa(i))
}
quicksetContainsTime := time.Since(start)
start = time.Now()
for i := 0; i < numOperations; i++ {
qs.Remove(strconv.Itoa(i))
}
quicksetRemoveTime := time.Since(start)
// Batch operations
batchElements := make([]string, numBatchOperations)
for i := 0; i < numBatchOperations; i++ {
batchElements[i] = strconv.Itoa(rand.Intn(numOperations))
}
start = time.Now()
qs.AddMany(batchElements)
quicksetBatchAddTime := time.Since(start)
start = time.Now()
qs.RemoveMany(batchElements)
quicksetBatchRemoveTime := time.Since(start)
// Print results
fmt.Println("golang-set:")
fmt.Printf(" Add: %v\n", mapsetAddTime)
fmt.Printf(" Contains: %v\n", mapsetContainsTime)
fmt.Printf(" Remove: %v\n", mapsetRemoveTime)
fmt.Println("QuickSet:")
fmt.Printf(" Add: %v\n", quicksetAddTime)
fmt.Printf(" Contains: %v\n", quicksetContainsTime)
fmt.Printf(" Remove: %v\n", quicksetRemoveTime)
fmt.Printf(" Batch Add (%d items): %v\n", numBatchOperations, quicksetBatchAddTime)
fmt.Printf(" Batch Remove (%d items): %v\n", numBatchOperations, quicksetBatchRemoveTime)
}
func printMemUsage() {
var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Printf("Alloc = %v MiB", bToMb(m.Alloc))
fmt.Printf("\tTotalAlloc = %v MiB", bToMb(m.TotalAlloc))
fmt.Printf("\tSys = %v MiB", bToMb(m.Sys))
fmt.Printf("\tNumGC = %v\n", m.NumGC)
}
func bToMb(b uint64) uint64 {
return b / 1024 / 1024
}
*/

Check failure on line 193 in cmd/performance/main.go

View workflow job for this annotation

GitHub Actions / build

expected 'package', found 'EOF'
Binary file added images/benchmark3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 95414b7

Please sign in to comment.