Compare commits

...

4 Commits

Author SHA1 Message Date
Richard Thier
6c1adb1655 perf tests and smaller perf tunes + some experiments 2024-10-11 00:54:13 +02:00
Richard Thier
c1b4b9e97b perf test architecture 2024-10-10 22:37:21 +02:00
Richard Thier
70f9b24669 added mapmap.hpp so maybe future benchmarks can be done 2024-10-10 17:16:41 +02:00
Richard Thier
c1f0e5f1a9 reorder operations and better docs for use case simplification 2024-10-10 17:16:19 +02:00
4 changed files with 156 additions and 17 deletions

View File

@ -1,9 +1,72 @@
#include <cstdio> #include <cstdio>
#include <cassert> #include <cassert>
#include <vector>
#include <string>
#include <chrono>
#include "amap.h" #include "amap.h"
#include "simap.h" #include "simap.h"
#include "mapmap.hpp"
/**
* Creates keys or returns the ith key. Used for performance tests.
*
* @param i When "create" is false, we return the ith key (does not check OOB)
* @param create When true, we initialize the keystore with keys generated from 0..i indices.
* @returns The ith key when create==false, otherwise undefined.
*/
inline const char *keystore(int i, bool create = false) noexcept {
static thread_local std::vector<std::string> keys;
if(!create) {
return keys[i].c_str();
} else {
keys.resize(0);
keys.reserve(0);
std::string key = "k";
for(int j = 0; j < i; ++j) {
keys.push_back(key + std::to_string(j));
}
return NULL;
}
}
/**
* Creates keys or returns the ith key. Used for performance tests.
*
* @param i When "create" is false, we return the ith data (does not check OOB)
* @param create When true, we initialize the datastore with datas generated from 0..i indices.
* @returns The ith data when create==false, otherwise undefined.
*/
inline int *datastore(int i, bool create = false) noexcept {
static thread_local std::vector<int> keys;
if(!create) {
return &(keys[i]);
} else {
keys.resize(0);
keys.reserve(0);
for(int j = 0; j < i; ++j) {
keys.push_back(j);
}
return NULL;
}
}
void test_perf(amap mapdo, void *map, int max_key, const char *what) {
auto begin = std::chrono::high_resolution_clock::now();
for(int i = 0; i < max_key; ++i) {
const char *key = keystore(i);
int *data = datastore(i);
mapdo(map, AMAP_SET, key, data);
}
auto end = std::chrono::high_resolution_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin);
printf("Insertion time for %d elements (%s): %.3f ms.\n", max_key, what, elapsed.count() * 1e-6);
}
void test_basics(amap mapdo, void *map) { void test_basics(amap mapdo, void *map) {
/* Most basics */
assert(NULL == mapdo(map, AMAP_GET, "asdf", NULL)); assert(NULL == mapdo(map, AMAP_GET, "asdf", NULL));
int i = 42; int i = 42;
int *iptr; int *iptr;
@ -30,7 +93,7 @@ void test_basics(amap mapdo, void *map) {
assert(NULL != mapdo(map, AMAP_SET, "meaningless1", &i)); assert(NULL != mapdo(map, AMAP_SET, "meaningless1", &i));
assert(NULL != mapdo(map, AMAP_SET, "meaning2", &i)); assert(NULL != mapdo(map, AMAP_SET, "meaning2", &i));
const char *helloworld = "Hello world!"; const char *helloworld = "Hello world!";
assert(NULL != mapdo(map, AMAP_SET, "hello", (char *)helloworld)); /* TODO: ugly cast... */ assert(NULL != mapdo(map, AMAP_SET, "hello", (char *)helloworld)); /* ugly cast... */
assert(NULL != (chptr = (const char *)mapdo(map, AMAP_GET, "hello", NULL))); assert(NULL != (chptr = (const char *)mapdo(map, AMAP_GET, "hello", NULL)));
assert(strlen(chptr) == strlen(helloworld)); assert(strlen(chptr) == strlen(helloworld));
@ -44,9 +107,20 @@ void test_basics(amap mapdo, void *map) {
} }
int main() { int main() {
/* test simap */ /* Basic tests */
simap_instance si = simap_create(); simap_instance si = simap_create();
test_basics(simap, &si); test_basics(simap, &si);
mapmap_instance mi = mapmap_create();
test_basics(mapmap, &mi);
/* Performance tests */
int i = 1000;
keystore(i, true);
datastore(i, true);
test_perf(simap, &si, i, "simap");
test_perf(mapmap, &mi, i, "std::map");
return 0; return 0;
} }

View File

@ -2,3 +2,5 @@ debug:
g++ main.cpp -g -Wall -o main g++ main.cpp -g -Wall -o main
release: release:
g++ main.cpp -O2 -Wall -o main g++ main.cpp -O2 -Wall -o main
release-native:
g++ main.cpp -march=native -O3 -Wall -o main

39
mapmap.hpp Normal file
View File

@ -0,0 +1,39 @@
#ifndef MAPMAP_HPP
#define MAPMAP_HPP
#include <map>
#include <cassert>
#include <memory>
struct mapmap_instance {
std::map<const char *, void *> m;
};
static inline mapmap_instance mapmap_create() {
mapmap_instance ret;
return ret;
}
static inline void* mapmap(void *amap_instance, AMAP_OP op, const char *key, void *ptr) {
mapmap_instance *map = (mapmap_instance *) amap_instance;
if(op == AMAP_GET) {
try {
return map->m[key];
} catch(...) {
return ptr;
}
} else if(op == AMAP_SET) {
try {
map->m[key] = ptr;
return map; // non-null
} catch(...) {
return NULL;
}
} else { // if(op == AMAP_ERASE) {
assert(op == AMAP_ERASE);
map->m = std::move(std::map<const char *, void *>());
return (void *)((uint8_t)(NULL) - 1L);
}
}
#endif // MAPMAP_HPP

54
simap.h
View File

@ -7,12 +7,24 @@
#include "amap.h" #include "amap.h"
#include "arena.h/arena.h" #include "arena.h/arena.h"
/** This is to ensure 8byte storage of pointers (with possible padding) */ /* Perf trickery */
union simap_ptr64 {
void *ptr; /* I have no idea what MSVC has instead... */
uint64_t u64; #ifdef _MSC_VER
}; #define SM_THREAD_LOCAL __declspec(thread)
typedef union simap_ptr64 simap_ptr64; #define SM_PREFETCH(x)
#define SM_LIKELY(x)
#define SM_UNLIKELY(x)
#define SM_NOINLINE __declspec(noinline)
#define SM_ALWAYS_INLINE __forceinline
#else
#define SM_THREAD_LOCAL __thread
#define SM_PREFETCH(x) __builtin_prefetch(x)
#define SM_LIKELY(x) __builtin_expect((x),1)
#define SM_UNLIKELY(x) __builtin_expect((x),0)
#define SM_NOINLINE __attribute__ ((noinline))
#define SM_ALWAYS_INLINE __attribute__ ((always_inline))
#endif
/** /**
* A "peasantly" map data structure backed by arena.h - basically a toy data structure... * A "peasantly" map data structure backed by arena.h - basically a toy data structure...
@ -64,12 +76,22 @@ static inline simap_instance simap_create() {
return ret; return ret;
} }
static inline void* simap(void *amap_instance, AMAP_OP op, const char *key, void *ptr);
/** The first 8 characters are stored as uint64_t for fast checks */
union simap_c64 { union simap_c64 {
char str8[8]; char str8[8];
uint64_t u64; uint64_t u64;
}; };
typedef union simap_char64 simap_char64; typedef union simap_char64 simap_char64;
/** This is to ensure 8byte storage of pointers (with possible padding) */
union simap_ptr64 {
void *ptr;
uint64_t u64;
};
typedef union simap_ptr64 simap_ptr64;
static inline simap_ptr64 *simap_search_internal(simap_instance *map, const char *key) { static inline simap_ptr64 *simap_search_internal(simap_instance *map, const char *key) {
/* Construct prefix (fast-key) */ /* Construct prefix (fast-key) */
size_t keylen = strlen(key); size_t keylen = strlen(key);
@ -82,13 +104,14 @@ static inline simap_ptr64 *simap_search_internal(simap_instance *map, const char
/* Construct keyremains - might point to the \0 terminator only if smallkey or 8 bytes exactly */ /* Construct keyremains - might point to the \0 terminator only if smallkey or 8 bytes exactly */
const char *keyremains = key + prefixlen; const char *keyremains = key + prefixlen;
/* TODO: Maybe this is buggy when we access behind our own data? */
/* TODO: Maybe I should create separate function for fast-lookup returning "next" pointer from a pointer to autovectorize? */ /* TODO: Maybe I should create separate function for fast-lookup returning "next" pointer from a pointer to autovectorize? */
/* Lookup prefix (fast-key) - hopefully this gets vectorized (should be)!!! */ /* Lookup prefix (fast-key) - hopefully this gets vectorized (should be)!!! */
uint64_t *base = map->base; uint64_t *base = map->base;
uint64_t *tipp = map->base; uint64_t *tipp = map->base;
for(uint32_t i = 0; i < map->usage_end / 8; ++i, ++tipp) { for(uint32_t i = 0; i < map->usage_end / 8; ++i, ++tipp) {
/* Fast lookup */ /* Fast lookup */
if(*tipp == prefix.u64) { if((*tipp == prefix.u64)) {
/* First check the remains of the string (only if needed) */ /* First check the remains of the string (only if needed) */
if(!is_smallkey) { if(!is_smallkey) {
char *tippremains = (char *)((uint8_t *)tipp + sizeof(uint64_t)); char *tippremains = (char *)((uint8_t *)tipp + sizeof(uint64_t));
@ -150,11 +173,11 @@ static inline uint32_t simap_elem_storage_size(const char *key) {
padding; padding;
} }
/** Force-add the (key,value) to the end of the map */ /** Force-add the (key,value) to the end of the map. Use this if you prefill your map one-by-one and need speed */
static inline void *simap_force_add_internal(simap_instance *map, const char *key, void *ptr) { static inline void *simap_force_add(simap_instance *map, const char *key, void *ptr) {
uint32_t storage_needed = simap_elem_storage_size(key); uint32_t storage_needed = simap_elem_storage_size(key);
assert((storage_needed % 8) == 0); assert((storage_needed % 8) == 0);
if(map->end - map->usage_end < storage_needed) { if(SM_UNLIKELY(map->end - map->usage_end < storage_needed)) {
/* Need storage */ /* Need storage */
aralloc(&(map->a), aralloc(&(map->a),
sizeof(uint8_t)/*esize*/, sizeof(uint8_t)/*esize*/,
@ -198,7 +221,7 @@ static inline void *simap_force_add_internal(simap_instance *map, const char *ke
/* XXX: The "padding" gets automagically added by the movement of the arena here(by junk bytes)! */ /* XXX: The "padding" gets automagically added by the movement of the arena here(by junk bytes)! */
/* Update previous with linkage */ /* Update previous with linkage */
if(previ != (uint32_t)-1) { if(SM_LIKELY(previ != (uint32_t)-1)) {
uint32_t *prevnex = (uint32_t *)((uint8_t *)(map->base) + previ + uint32_t *prevnex = (uint32_t *)((uint8_t *)(map->base) + previ +
sizeof(simap_ptr64) + sizeof(simap_ptr64) +
sizeof(uint32_t)); sizeof(uint32_t));
@ -232,7 +255,7 @@ static inline void *simap_force_add_internal(simap_instance *map, const char *ke
static inline void* simap(void *amap_instance, AMAP_OP op, const char *key, void *ptr) { static inline void* simap(void *amap_instance, AMAP_OP op, const char *key, void *ptr) {
simap_instance *map = (simap_instance *) amap_instance; simap_instance *map = (simap_instance *) amap_instance;
if(op == AMAP_ERASE) { if((op == AMAP_ERASE)) {
map->prev_usage_end = (uint32_t) -1; map->prev_usage_end = (uint32_t) -1;
map->usage_end = 0; map->usage_end = 0;
return (void *)((uint8_t)(NULL) - 1L); return (void *)((uint8_t)(NULL) - 1L);
@ -246,12 +269,13 @@ static inline void* simap(void *amap_instance, AMAP_OP op, const char *key, void
} else { } else {
assert(op == AMAP_SET); assert(op == AMAP_SET);
if(found) { if((!found)) {
/* Add as new */
return simap_force_add(map, key, ptr);
} else {
/* Just overwrite */ /* Just overwrite */
found->ptr = ptr; found->ptr = ptr;
return (void *) found; return (void *) found;
} else {
return simap_force_add_internal(map, key, ptr);
} }
} }