Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d5de1b9ea | ||
|
|
e23691c743 | ||
|
|
aaac236db6 | ||
|
|
ccccedc2d5 | ||
|
|
446afb04c7 | ||
|
|
87cd964329 | ||
|
|
80a344e46d | ||
|
|
f2eb5014ac | ||
|
|
d6c72061ce | ||
|
|
7c26fd25c9 | ||
|
|
10e12d2b5c | ||
|
|
b9070f89d9 | ||
|
|
46d41a8882 | ||
|
|
b5dc3481d4 | ||
|
|
c18cd81dde | ||
|
|
eaf5a26157 | ||
|
|
547c67b676 | ||
|
|
c89d6ef0ce | ||
|
|
a54041c340 | ||
|
|
352e0972cd | ||
|
|
91ebd7cc9f | ||
|
|
d8854c276b | ||
|
|
cc61840ae8 | ||
|
|
67a7a4762a | ||
|
|
c28597014f | ||
|
|
d00f29bf26 | ||
|
|
1432fbabca | ||
|
|
f3b6ee0190 | ||
|
|
229d21ef72 | ||
|
|
00f1b1fc4f | ||
|
|
277d8b37af | ||
|
|
7b83761ef4 | ||
|
|
a96e9c7e11 | ||
|
|
866721d067 | ||
|
|
b356c29c89 | ||
|
|
a2f7cb7670 | ||
|
|
08887c1df0 | ||
|
|
8b0a9f2da9 | ||
|
|
e6c958c0df | ||
|
|
01ad221972 | ||
|
|
c269a36e8b | ||
|
|
95fcd054a3 | ||
|
|
7760f16260 | ||
|
|
6d016d732b | ||
|
|
b9aebf875e | ||
|
|
1eb0e8730d | ||
|
|
7eb26a29f1 | ||
|
|
0ef051e6d2 | ||
|
|
0d641f0d17 | ||
|
|
ce6a7f6cb3 | ||
|
|
07b4c1a9f4 | ||
|
|
4d3a82583d | ||
|
|
2835d53ef1 | ||
|
|
620ecadec1 | ||
|
|
dc803f23bb | ||
|
|
a6d11fe897 | ||
|
|
78b05690e7 | ||
|
|
958a79ad73 | ||
|
|
ead25c905a | ||
|
|
97f80450ca | ||
|
|
8e62537604 | ||
|
|
7813182e2b | ||
|
|
b199fef674 | ||
|
|
e27c06c227 | ||
|
|
41b04f34e2 | ||
|
|
b091f84f90 | ||
|
|
05ae9f382b | ||
|
|
a36efcd888 | ||
|
|
af2fe9ccf9 | ||
|
|
c4a5727c2a | ||
|
|
31ca762c62 | ||
|
|
30bda70319 | ||
|
|
7fa9d375b8 | ||
|
|
91ae3fd4f2 | ||
|
|
b00d6a9655 | ||
|
|
013aa58524 | ||
|
|
8ce7e0483f | ||
|
|
582c413cd2 | ||
|
|
22e16e983b | ||
|
|
7c0c16260a | ||
|
|
e496fbd152 | ||
|
|
e025de91ab | ||
|
|
69e2fc432f | ||
|
|
78ba83ce42 | ||
|
|
6d9b61c119 | ||
|
|
61cfa5cedd | ||
|
|
0507d94306 | ||
|
|
7e8919080b | ||
|
|
8406efcaab | ||
|
|
2d87eb1e6a | ||
|
|
4685aac454 | ||
|
|
9863a8325c | ||
|
|
b9514cbe72 | ||
|
|
7b085d18fc | ||
|
|
91e73520f3 | ||
|
|
aebf5c83ba | ||
|
|
42ff47f511 | ||
|
|
746bdf144b | ||
|
|
4e8f5f7b51 | ||
|
|
50d89f71cb | ||
|
|
d70ef53283 | ||
|
|
1b6182c1cd | ||
|
|
73b50c852a | ||
|
|
063011d8e9 | ||
|
|
0f3bf6ab82 | ||
|
|
475836e851 | ||
|
|
325ca30c61 | ||
|
|
72e872c768 | ||
|
|
c2a837b30b | ||
|
|
08822ac7d5 | ||
|
|
7f9007ca1a | ||
|
|
f067cb334c | ||
|
|
60ee253fa7 | ||
|
|
4642e0b506 | ||
|
|
bd29680b62 | ||
|
|
c337462af6 | ||
|
|
c990277855 | ||
|
|
8870a1c2f9 | ||
|
|
60f1454550 | ||
|
|
38eb1c26c6 | ||
|
|
af050d0100 | ||
|
|
14f3ae5cea | ||
|
|
f4ed750d39 | ||
|
|
4b3adc8d35 | ||
|
|
8b3a4a802c | ||
|
|
8b9f605ffa | ||
|
|
3c56f24db2 | ||
|
|
82dbf60a4c | ||
|
|
b596e81e88 | ||
|
|
b7bbe15547 | ||
|
|
cb5f8c20c8 | ||
|
|
61a1e15103 | ||
|
|
2416c2e466 | ||
|
|
b898ce3c56 | ||
|
|
061e83b9e1 | ||
|
|
0a8f727908 | ||
|
|
ba584ab096 | ||
|
|
1fb1fa2e0a | ||
|
|
8cf780a60f | ||
|
|
ae171115bc | ||
|
|
a8620c3aff | ||
|
|
c154cdf46c | ||
|
|
77c59e2b85 | ||
|
|
f07656757f | ||
|
|
a1c22fb679 | ||
|
|
3640041c47 | ||
|
|
d3934dccb9 | ||
|
|
10cf2f2270 | ||
|
|
c180b2b96d |
No files matched your search
@@ -1,6 +1,6 @@
|
||||
BSD 2-Clause License
|
||||
|
||||
Copyright (c) 2017-2018, Patrick Pelissier
|
||||
Copyright (c) 2017-2019, Patrick Pelissier
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
||||
@@ -10,11 +10,11 @@ PREFIX=/usr/local
|
||||
|
||||
# Package name & version.
|
||||
PACKAGE=m*lib-$(VERSION)
|
||||
VERSION=0.2.0
|
||||
VERSION=0.2.2
|
||||
|
||||
# Define the contain of the distribution tarball
|
||||
# TODO: Get theses lists from GIT itself.
|
||||
HEADER=m-algo.h m-array.h m-atomic.h m-bitset.h m-bptree.h m-buffer.h m-core.h m-deque.h m-dict.h m-genint.h m-i-list.h m-i-shared.h m-list.h m-mempool.h m-mutex.h m-prioqueue.h m-rbtree.h m-shared.h m-snapshot.h m-string.h m-tuple.h m-variant.h m-worker.h
|
||||
HEADER=m-algo.h m-array.h m-atomic.h m-bitset.h m-bptree.h m-buffer.h m-concurrent.h m-core.h m-deque.h m-dict.h m-genint.h m-i-list.h m-i-shared.h m-list.h m-mempool.h m-mutex.h m-prioqueue.h m-rbtree.h m-shared.h m-snapshot.h m-string.h m-tuple.h m-variant.h m-worker.h
|
||||
DOC1=LICENSE README.md
|
||||
DOC2=doc/API.txt doc/Container.html doc/Container.ods doc/DEV.md doc/ISSUES.org doc/depend.png doc/oplist.png
|
||||
EXAMPLE=example/ex-array01.c example/ex-array04.c example/ex-dict02.c example/ex-grep01.c example/ex-multi01.c example/ex-rbtree01.c example/ex-array02.c example/ex-buffer01.c example/ex-dict03.c example/ex-list01.c example/ex-multi02.c example/Makefile example/ex-array03.c example/ex-dict01.c example/ex-dict04.c example/ex-mph.c example/ex-multi03.c
|
||||
|
||||
+55
-34
@@ -1,14 +1,20 @@
|
||||
CC=cc -std=c11
|
||||
CXX=c++ -std=c++11
|
||||
CFLAGS=-O2 -march=native -Wall
|
||||
XCFLAGS=
|
||||
CPPFLAGS=-I..
|
||||
N=4
|
||||
BENCH_DEF=
|
||||
RM=rm -rf
|
||||
|
||||
.PHONY: all container queue mempool string plain bench-mlib bench-mlib-mempool bench-stl bench-qt bench-glib bench-klib bench-libdynamic bench-sparsepp bench-collectionc bench-tommyds bench-flathashmap bench-hopscotchmap bench-mlib-thread bench-liblfds bench-concurrentqueue bench-boost bench-mempool bench-string bench-plain
|
||||
.PHONY: all pgo container queue mempool string plain bench-mlib bench-mlib-mempool bench-stl bench-qt bench-glib bench-klib bench-libdynamic bench-sparsepp bench-collectionc bench-tommyds bench-flathashmap bench-hopscotchmap bench-mlib-thread bench-liblfds bench-concurrentqueue bench-boost bench-mempool bench-string bench-plain
|
||||
|
||||
all: container queue mempool string plain
|
||||
|
||||
pgo:
|
||||
$(MAKE) all XCFLAGS="-fprofile-generate=pgo"
|
||||
$(MAKE) all XCFLAGS="-fprofile-use=pgo"
|
||||
|
||||
#########################################################################
|
||||
|
||||
container: bench-mlib bench-mlib-mempool bench-stl bench-qt bench-glib bench-klib bench-libdynamic bench-sparsepp bench-collectionc bench-tommyds bench-flathashmap bench-hopscotchmap
|
||||
@@ -32,7 +38,7 @@ plain: bench-plain
|
||||
|
||||
|
||||
bench-mlib:
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -pthread bench-mlib.c -o bench-mlib.exe
|
||||
$(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) -pthread bench-mlib.c -o bench-mlib.exe
|
||||
@./bench-mlib.exe 10
|
||||
@./bench-mlib.exe 11
|
||||
@./bench-mlib.exe 20
|
||||
@@ -67,9 +73,10 @@ bench-mlib-thread:
|
||||
@./bench-mlib-thread.exe 63
|
||||
@./bench-mlib-thread.exe 64
|
||||
@./bench-mlib-thread.exe 65
|
||||
@./bench-mlib-thread.exe 66
|
||||
|
||||
bench-stl:
|
||||
$(CXX) $(CFLAGS) $(CPPFLAGS) bench-stl.cpp -o bench-stl.exe
|
||||
$(CXX) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) bench-stl.cpp -o bench-stl.exe
|
||||
@./bench-stl.exe 10
|
||||
@./bench-stl.exe 20
|
||||
@./bench-stl.exe 30
|
||||
@@ -84,7 +91,7 @@ bench-stl:
|
||||
bench-qt:
|
||||
@if test -n "$${QT}" ; then $(MAKE) bench-qt0 ; else echo "Nothing to be done for QT." ; fi
|
||||
bench-qt0:
|
||||
$(CXX) $(CFLAGS) $(CPPFLAGS) -fPIC `PKG_CONFIG_PATH="$${QT}" pkg-config --libs --cflags Qt5Core` bench-qt.cpp -o bench-qt.exe
|
||||
$(CXX) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) -fPIC `PKG_CONFIG_PATH="$${QT}" pkg-config --libs --cflags Qt5Core` bench-qt.cpp -o bench-qt.exe
|
||||
@./bench-qt.exe 10
|
||||
@./bench-qt.exe 20
|
||||
@./bench-qt.exe 30
|
||||
@@ -97,7 +104,7 @@ bench-qt0:
|
||||
bench-glib:
|
||||
@if test -n "$${GLIB}" ; then $(MAKE) bench-glib0 ; else echo "Nothing to be done for GLIB." ; fi
|
||||
bench-glib0:
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) bench-glib.c -o bench-glib.exe `PKG_CONFIG_PATH="$${GLIB}" pkg-config --libs --cflags glib-2.0`
|
||||
$(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) bench-glib.c -o bench-glib.exe `PKG_CONFIG_PATH="$${GLIB}" pkg-config --libs --cflags glib-2.0`
|
||||
@./bench-glib.exe 10
|
||||
@./bench-glib.exe 20
|
||||
@./bench-glib.exe 30
|
||||
@@ -110,7 +117,7 @@ bench-klib:
|
||||
@if test -n "$${KLIB}" ; then $(MAKE) bench-klib0 ; else echo "Nothing to be done for KLIB." ; fi
|
||||
|
||||
bench-klib0:
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -I $${KLIB} bench-klib.c -o bench-klib.exe
|
||||
$(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) -I $${KLIB} bench-klib.c -o bench-klib.exe
|
||||
@./bench-klib.exe 10
|
||||
@./bench-klib.exe 20
|
||||
@./bench-klib.exe 30
|
||||
@@ -123,7 +130,7 @@ bench-libdynamic:
|
||||
@if test -n "$${LIBDYNAMIC}" ; then $(MAKE) bench-libdynamic0 ; else echo "Nothing to be done for LIBDYNAMIC." ; fi
|
||||
|
||||
bench-libdynamic0:
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) bench-libdynamic.c -o bench-libdynamic.exe `PKG_CONFIG_PATH="$${LIBDYNAMIC}" pkg-config --libs --cflags libdynamic`
|
||||
$(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) bench-libdynamic.c -o bench-libdynamic.exe `PKG_CONFIG_PATH="$${LIBDYNAMIC}" pkg-config --libs --cflags libdynamic`
|
||||
@./bench-libdynamic.exe 10
|
||||
@./bench-libdynamic.exe 20
|
||||
@./bench-libdynamic.exe 30
|
||||
@@ -136,7 +143,7 @@ bench-sparsepp:
|
||||
@if test -n "$${SPARSEPP}" ; then $(MAKE) bench-sparsepp0 ; else echo "Nothing to be done for SPARSEPP." ; fi
|
||||
|
||||
bench-sparsepp0:
|
||||
$(CXX) $(CFLAGS) $(CPPFLAGS) -I "$${SPARSEPP}" -I "$${SPARSEPP}/.." bench-sparsepp.cpp -o bench-sparsepp.exe
|
||||
$(CXX) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) -I "$${SPARSEPP}" -I "$${SPARSEPP}/.." bench-sparsepp.cpp -o bench-sparsepp.exe
|
||||
@./bench-sparsepp.exe 41
|
||||
@./bench-sparsepp.exe 42
|
||||
@./bench-sparsepp.exe 43
|
||||
@@ -146,7 +153,7 @@ bench-densehashmap:
|
||||
@if test -n "$${DENSEHASHMAP}" ; then $(MAKE) bench-densehashmap0 ; else echo "Nothing to be done Google DENSEHASHMAP." ; fi
|
||||
|
||||
bench-densehashmap0:
|
||||
$(CXX) $(CFLAGS) $(CPPFLAGS) -I "$${DENSEHASHMAP}" -I "$${DENSEHASHMAP}/.." bench-densehashmap.cpp -o bench-densehashmap.exe
|
||||
$(CXX) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) -I "$${DENSEHASHMAP}" -I "$${DENSEHASHMAP}/.." bench-densehashmap.cpp -o bench-densehashmap.exe
|
||||
@./bench-densehashmap.exe 41
|
||||
@./bench-densehashmap.exe 42
|
||||
@./bench-densehashmap.exe 43
|
||||
@@ -156,7 +163,7 @@ bench-collectionc:
|
||||
@if test -n "$${COLLECTIONC}" ; then $(MAKE) bench-collectionc0 ; else echo "Nothing to be done for COLLECTIONC." ; fi
|
||||
|
||||
bench-collectionc0:
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -I "$${COLLECTIONC}/include" -L "$${COLLECTIONC}" -static bench-collectionc.c -lcollectc -o bench-collectionc.exe
|
||||
$(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) -I "$${COLLECTIONC}/include" -L "$${COLLECTIONC}" -static bench-collectionc.c -lcollectc -o bench-collectionc.exe
|
||||
@./bench-collectionc.exe 10
|
||||
@./bench-collectionc.exe 20
|
||||
@./bench-collectionc.exe 30
|
||||
@@ -169,7 +176,7 @@ bench-tommyds:
|
||||
@if test -n "$${TOMMYDS}" ; then $(MAKE) bench-tommyds0 ; else echo "Nothing to be done for TOMMYDS." ; fi
|
||||
|
||||
bench-tommyds0:
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -I "$${TOMMYDS}" bench-tommyds.c $${TOMMYDS}/tommyds/tommyarray.c $${TOMMYDS}/tommyds/tommylist.c $${TOMMYDS}/tommyds/tommytree.c $${TOMMYDS}/tommyds/tommyhashlin.c -o bench-tommyds.exe
|
||||
$(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) -I "$${TOMMYDS}" bench-tommyds.c $${TOMMYDS}/tommyds/tommyarray.c $${TOMMYDS}/tommyds/tommylist.c $${TOMMYDS}/tommyds/tommytree.c $${TOMMYDS}/tommyds/tommyhashlin.c -o bench-tommyds.exe
|
||||
@./bench-tommyds.exe 10
|
||||
@./bench-tommyds.exe 20
|
||||
@./bench-tommyds.exe 30
|
||||
@@ -181,7 +188,7 @@ bench-flathashmap:
|
||||
@if test -n "$${FLATHASHMAP}" ; then $(MAKE) bench-flathashmap0 ; else echo "Nothing to be done for FLATHASHMAP." ; fi
|
||||
|
||||
bench-flathashmap0:
|
||||
$(CXX) -std=c++14 $(CFLAGS) $(CPPFLAGS) -I "$${FLATHASHMAP}" bench-flathashmap.cpp -o bench-flathashmap.exe
|
||||
$(CXX) -std=c++14 $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) -I "$${FLATHASHMAP}" bench-flathashmap.cpp -o bench-flathashmap.exe
|
||||
@./bench-flathashmap.exe 41
|
||||
@./bench-flathashmap.exe 42
|
||||
@./bench-flathashmap.exe 43
|
||||
@@ -191,7 +198,7 @@ bench-hopscotchmap:
|
||||
@if test -n "$${HOPSCOTCHMAP}" ; then $(MAKE) bench-hopscotchmap0 ; else echo "Nothing to be done for HOPSCOTCHMAP." ; fi
|
||||
|
||||
bench-hopscotchmap0:
|
||||
$(CXX) $(CFLAGS) $(CPPFLAGS) -I "$${HOPSCOTCHMAP}" bench-hopscotchmap.cpp -o bench-hopscotchmap.exe
|
||||
$(CXX) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) -I "$${HOPSCOTCHMAP}" bench-hopscotchmap.cpp -o bench-hopscotchmap.exe
|
||||
@./bench-hopscotchmap.exe 41
|
||||
@./bench-hopscotchmap.exe 42
|
||||
@./bench-hopscotchmap.exe 43
|
||||
@@ -201,7 +208,7 @@ bench-uthash:
|
||||
@if test -n "$${UTHASH}" ; then $(MAKE) bench-uthash0 ; else echo "Nothing to be done for UTHASH." ; fi
|
||||
|
||||
bench-uthash0:
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -I "$${UTHASH}" bench-uthash.c -o bench-uthash.exe
|
||||
$(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) -I "$${UTHASH}" bench-uthash.c -o bench-uthash.exe
|
||||
@./bench-uthash.exe 10
|
||||
@./bench-uthash.exe 20
|
||||
@./bench-uthash.exe 41
|
||||
@@ -214,7 +221,7 @@ bench-qlibc:
|
||||
@if test -n "$${QLIBC}" ; then $(MAKE) bench-qlibc0 ; else echo "Nothing to be done for QLIBC." ; fi
|
||||
|
||||
bench-qlibc0:
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -I "$${QLIBC}/include" -L "$${QLIBC}/lib" -lqlibc -pthread bench-qlibc.c -o bench-qlibc.exe
|
||||
$(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) -I "$${QLIBC}/include" -L "$${QLIBC}/lib" -lqlibc -pthread bench-qlibc.c -o bench-qlibc.exe
|
||||
@LD_LIBRARY_PATH="$${QLIBC}/lib:$${LD_LIBRARY_PATH}" ./bench-qlibc.exe 10
|
||||
@LD_LIBRARY_PATH="$${QLIBC}/lib:$${LD_LIBRARY_PATH}" ./bench-qlibc.exe 20
|
||||
@LD_LIBRARY_PATH="$${QLIBC}/lib:$${LD_LIBRARY_PATH}" ./bench-qlibc.exe 30
|
||||
@@ -222,11 +229,12 @@ bench-qlibc0:
|
||||
@LD_LIBRARY_PATH="$${QLIBC}/lib:$${LD_LIBRARY_PATH}" ./bench-qlibc.exe 41
|
||||
|
||||
# LIBSRT shall point to the directory where the source are
|
||||
# The library shall be built.
|
||||
bench-libsrt:
|
||||
@if test -n "$${LIBSRT}" ; then $(MAKE) bench-libsrt0 ; else echo "Nothing to be done for LIBSRT." ; fi
|
||||
|
||||
bench-libsrt0:
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -I "$${LIBSRT}/src" bench-libsrt.c -o bench-libsrt.exe $${LIBSRT}/libsrt.a
|
||||
$(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) -I "$${LIBSRT}/src" bench-libsrt.c -o bench-libsrt.exe $${LIBSRT}/libsrt.a
|
||||
@./bench-libsrt.exe 10
|
||||
@./bench-libsrt.exe 20
|
||||
@./bench-libsrt.exe 41
|
||||
@@ -240,7 +248,7 @@ bench-libsrt0:
|
||||
bench-liblfds:
|
||||
@if test -n "$${LIBLFDS}" ; then $(MAKE) bench-liblfds0 ; else echo "Nothing to be done for LIBLFDS." ; fi
|
||||
bench-liblfds0:
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -I "$${LIBLFDS}/inc" -Wno-unknown-pragmas bench-liblfds.c -o bench-liblfds.exe $${LIBLFDS}/bin/liblfds711.a -lpthread
|
||||
$(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) -I "$${LIBLFDS}/inc" -Wno-unknown-pragmas bench-liblfds.c -o bench-liblfds.exe $${LIBLFDS}/bin/liblfds711.a -lpthread
|
||||
@LD_LIBRARY_PATH="$${LIBLFDS}/bin:$${LD_LIBRARY_PATH}" ./bench-liblfds.exe 60
|
||||
|
||||
|
||||
@@ -249,36 +257,37 @@ bench-liblfds0:
|
||||
bench-concurrentqueue:
|
||||
@if test -n "$${CONCURRENTQUEUE}" ; then $(MAKE) bench-concurrentqueue0 ; else echo "Nothing to be done for CONCURRENTQUEUE." ; fi
|
||||
bench-concurrentqueue0:
|
||||
$(CXX) $(CFLAGS) $(CPPFLAGS) -I "$${CONCURRENTQUEUE}/" bench-concurrentqueue.cpp -o bench-concurrentqueue.exe -lpthread
|
||||
$(CXX) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) -I "$${CONCURRENTQUEUE}/" bench-concurrentqueue.cpp -o bench-concurrentqueue.exe -lpthread
|
||||
@./bench-concurrentqueue.exe 60
|
||||
|
||||
|
||||
# XXHASH shall point to the source directory of xxhash
|
||||
# The library shall be built.
|
||||
bench-xxhash:
|
||||
@if test -n "$${XXHASH}" ; then $(MAKE) bench-xxhash0 ; else echo "Nothing to be done for XXHASH." ; fi
|
||||
bench-xxhash0:
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -I "$${XXHASH}/" bench-xxhash.c "$${XXHASH}/libxxhash.a" -o bench-xxhash.exe
|
||||
$(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) -I "$${XXHASH}/" bench-xxhash.c "$${XXHASH}/libxxhash.a" -o bench-xxhash.exe
|
||||
@./bench-xxhash.exe 70
|
||||
|
||||
# BOOST shall point to the header location.
|
||||
bench-boost:
|
||||
@if test -n "$${BOOST}" ; then $(MAKE) bench-boost0 ; else echo "Nothing to be done for BOOST." ; fi
|
||||
bench-boost0:
|
||||
$(CXX) $(CFLAGS) $(CPPFLAGS) -I "$${BOOST}" bench-boost.cpp -o bench-boost.exe -lpthread
|
||||
$(CXX) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) -I "$${BOOST}" bench-boost.cpp -o bench-boost.exe -lpthread
|
||||
@./bench-boost.exe 60
|
||||
|
||||
# NEDTRIES shall point to the header location.
|
||||
bench-nedtries:
|
||||
@if test -n "$${NEDTRIES}" ; then $(MAKE) bench-nedtries0 ; else echo "Nothing to be done for NEDTRIES." ; fi
|
||||
bench-nedtries0:
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -I "$${NEDTRIES}" bench-nedtries.c -o bench-nedtries.exe
|
||||
$(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) -I "$${NEDTRIES}" bench-nedtries.c -o bench-nedtries.exe
|
||||
@./bench-nedtries.exe 40
|
||||
@./bench-nedtries.exe 45
|
||||
|
||||
############################################################################
|
||||
|
||||
bench-plain:
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) bench-plain-c.c -o bench-plain-c.exe
|
||||
$(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) bench-plain-c.c -o bench-plain-c.exe
|
||||
@./bench-plain-c.exe
|
||||
|
||||
############################################################################
|
||||
@@ -286,13 +295,13 @@ bench-plain:
|
||||
bench-mempool:
|
||||
@if test -n "$${BOOST}" ; then if test -n "$${GLIB}" ; then make bench-mempool0 BENCH_DEF="-DUSE_BOOST -DUSE_GLIB `pkg-config --libs --cflags glib-2.0` -lboost_system" ; else make bench-mempool0 BENCH_DEF="-DUSE_BOOST -lboost_system" ; fi ; else if test -n "$${GLIB}" ; then make bench-mempool0 BENCH_DEF="-DUSE_GLIB `pkg-config --libs --cflags glib-2.0`" ; else make bench-mempool0 ; fi ; fi
|
||||
bench-mempool0:
|
||||
$(CXX) $(CFLAGS) $(CPPFLAGS) pool.cc $(BENCH_DEF) -o bench-mempool.exe
|
||||
$(CXX) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) pool.cc $(BENCH_DEF) -o bench-mempool.exe
|
||||
@./bench-mempool.exe
|
||||
|
||||
############################################################################
|
||||
|
||||
bench-maxdict:
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) max-dict.c -o bench-max-dict.exe
|
||||
$(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) max-dict.c -o bench-max-dict.exe
|
||||
@./bench-max-dict.exe
|
||||
|
||||
############################################################################
|
||||
@@ -300,27 +309,39 @@ bench-maxdict:
|
||||
# BSTRLIB shall point to the BSTRLIB sources
|
||||
# LIBSRT shall point to the LIBSRT sources
|
||||
bench-string:
|
||||
@if test -n "$${BSTRLIB}" ; then $(MAKE) bench-string-with BENCH_DEF="-DBENCH_CAN_USE_BSTRLIB -I ${BSTRLIB} $${BSTRLIB}/obj/bstrlib.o $${BSTRLIB}/obj/bstraux.o $${BSTRLIB}/obj/bstrwrap.o" ; else $(MAKE) bench-string-with ; fi
|
||||
@if test -n "$${BSTRLIB}" ; then $(MAKE) bench-string-step1 BENCH_DEF="-DBENCH_CAN_USE_BSTRLIB -I ${BSTRLIB} $${BSTRLIB}/obj/bstrlib.o $${BSTRLIB}/obj/bstraux.o $${BSTRLIB}/obj/bstrwrap.o" ; else $(MAKE) bench-string-step1 ; fi
|
||||
|
||||
bench-string-with:
|
||||
@if test -n "$${LIBSRT}" ; then $(MAKE) bench-string-with2 BENCH_DEF="$(BENCH_DEF) -DBENCH_CAN_USE_LIBSRT -I$${LIBSRT}/src $${LIBSRT}/libsrt.a" ; else $(MAKE) bench-string-with2 BENCH_DEF="$(BENCH_DEF)"; fi
|
||||
bench-string-step1:
|
||||
@if test -n "$${LIBSRT}" ; then $(MAKE) bench-string-step2 BENCH_DEF="$(BENCH_DEF) -DBENCH_CAN_USE_LIBSRT -I$${LIBSRT}/src $${LIBSRT}/libsrt.a" ; else $(MAKE) bench-string-step2 BENCH_DEF="$(BENCH_DEF)"; fi
|
||||
|
||||
#NOTE: Need to build by hand sds.o since it is not build by default and sds.c is not compatible with C++
|
||||
bench-string-with2:
|
||||
@if test -n "$${SDS}" ; then $(MAKE) bench-string-with3 BENCH_DEF="$(BENCH_DEF) -fpermissive -DBENCH_CAN_USE_SDS -I$${SDS}/ $${SDS}/sds.o" ; else $(MAKE) bench-string-with3 BENCH_DEF="$(BENCH_DEF)"; fi
|
||||
bench-string-step2:
|
||||
@if test -n "$${SDS}" ; then $(MAKE) bench-string-step3 BENCH_DEF="$(BENCH_DEF) -fpermissive -DBENCH_CAN_USE_SDS -I$${SDS}/ $${SDS}/sds.o" ; else $(MAKE) bench-string-step3 BENCH_DEF="$(BENCH_DEF)"; fi
|
||||
|
||||
bench-string-with3:
|
||||
@if test -n "$${RAPIDSTRING}" ; then $(MAKE) bench-string-exe BENCH_DEF="$(BENCH_DEF) -DBENCH_CAN_USE_RAPIDSTRING -I$${RAPIDSTRING}/ " ; else $(MAKE) bench-string-exe BENCH_DEF="$(BENCH_DEF)"; fi
|
||||
bench-string-step3:
|
||||
@if test -n "$${RAPIDSTRING}" ; then $(MAKE) bench-string-step4 BENCH_DEF="$(BENCH_DEF) -DBENCH_CAN_USE_RAPIDSTRING -I$${RAPIDSTRING}/ " ; else $(MAKE) bench-string-step4 BENCH_DEF="$(BENCH_DEF)"; fi
|
||||
|
||||
bench-string-exe:
|
||||
$(CXX) $(CFLAGS) $(CPPFLAGS) -I "$${BSTRLIB}" -DBENCH_CAN_USE_STL -DBENCH_CAN_USE_MSTARLIB bench-string.cpp -o bench-string.exe $(BENCH_DEF)
|
||||
bench-string-step4:
|
||||
@if test -n "$${GLIB}" ; then $(MAKE) bench-string-step5 BENCH_DEF="$(BENCH_DEF) -DBENCH_CAN_USE_GLIB $$(pkg-config --cflags --libs glib-2.0)" ; else $(MAKE) bench-string-step5 BENCH_DEF="$(BENCH_DEF)"; fi
|
||||
|
||||
bench-string-step5:
|
||||
$(CXX) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) -DBENCH_CAN_USE_STL -DBENCH_CAN_USE_MSTARLIB bench-string.cpp common.c $(BENCH_DEF) -o bench-string.exe
|
||||
$(CXX) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) -DBENCH_CAN_USE_STL -DBENCH_CAN_USE_MSTARLIB bench-string-2.cpp common.c $(BENCH_DEF) -o bench-string-2.exe
|
||||
@./bench-string.exe
|
||||
@./bench-string-2.exe 2000000 0
|
||||
@./bench-string-2.exe 2000000 1
|
||||
@./bench-string-2.exe 2000000 2
|
||||
@./bench-string-2.exe 2000000 3
|
||||
|
||||
bench-string-pgo:
|
||||
$(MAKE) bench-string XCFLAGS="-fprofile-generate=pgo"
|
||||
$(MAKE) bench-string XCFLAGS="-fprofile-use=pgo"
|
||||
|
||||
############################################################################
|
||||
|
||||
clean:
|
||||
rm -f *.o bench-*.exe bench-*.s *~ massif.out.*
|
||||
$(RM) *.o bench-*.exe bench-*.s *~ massif.out.*
|
||||
$(RM) pgo
|
||||
cd nucleotide && $(MAKE) clean
|
||||
|
||||
############################################################################
|
||||
|
||||
+96
-4
@@ -441,7 +441,7 @@ static void conso_mpmc(void *arg)
|
||||
size_t n = *p_n;
|
||||
unsigned long long s = 0;
|
||||
for(int i = 0; i < n;i++) {
|
||||
while (!queue_uint_pop(&j, g_buff_mpmc));
|
||||
while (!queue_uint_pop(&j, g_buff_mpmc)) m_thread_yield();
|
||||
s += j;
|
||||
}
|
||||
while (!queue_ull_push(g_final_mpmc, s));
|
||||
@@ -453,7 +453,7 @@ static void prod_mpmc(void *arg)
|
||||
size_t n = *p_n;
|
||||
size_t r = n;
|
||||
for(unsigned int i = 0; i < n;i++) {
|
||||
while (!queue_uint_push(g_buff_mpmc, r ));
|
||||
while (!queue_uint_push(g_buff_mpmc, r )) m_thread_yield();
|
||||
r = r * 31421U + 6927U;
|
||||
}
|
||||
}
|
||||
@@ -526,7 +526,7 @@ static void conso_spsc(void *arg)
|
||||
size_t n = *p_n;
|
||||
unsigned long long s = 0;
|
||||
for(int i = 0; i < n;i++) {
|
||||
while (!queue_single_uint_pop(&j, g_buff_spsc));
|
||||
while (!queue_single_uint_pop(&j, g_buff_spsc)) m_thread_yield();
|
||||
s += j;
|
||||
}
|
||||
while (!queue_single_ull_push(g_final_spsc, s));
|
||||
@@ -538,7 +538,7 @@ static void prod_spsc(void *arg)
|
||||
size_t n = *p_n;
|
||||
size_t r = n;
|
||||
for(unsigned int i = 0; i < n;i++) {
|
||||
while (!queue_single_uint_push(g_buff_spsc, r ));
|
||||
while (!queue_single_uint_push(g_buff_spsc, r )) m_thread_yield();
|
||||
r = r * 31421U + 6927U;
|
||||
}
|
||||
}
|
||||
@@ -581,6 +581,96 @@ static void test_queue_single(size_t n)
|
||||
|
||||
/********************************************************************************************/
|
||||
|
||||
#define BULK_SIZE 20
|
||||
|
||||
static void final_spsc_bulk(void *arg)
|
||||
{
|
||||
size_t *p_n = arg;
|
||||
size_t n = *p_n;
|
||||
unsigned long long j, s = 0;
|
||||
for(int i = 0; i < n;i++) {
|
||||
while (!queue_single_ull_pop(&j, g_final_spsc)) m_thread_yield();
|
||||
s += j;
|
||||
}
|
||||
g_result = s;
|
||||
}
|
||||
|
||||
static void conso_spsc_bulk(void *arg)
|
||||
{
|
||||
size_t *p_n = arg;
|
||||
size_t n = *p_n;
|
||||
unsigned long long s = 0;
|
||||
unsigned tab[BULK_SIZE];
|
||||
for(int i = 0; i < n;i+= BULK_SIZE) {
|
||||
unsigned k = queue_single_uint_pop_bulk(BULK_SIZE, tab, g_buff_spsc);
|
||||
while (k != BULK_SIZE) {
|
||||
m_thread_yield();
|
||||
k += queue_single_uint_pop_bulk(BULK_SIZE-k, tab+k, g_buff_spsc);
|
||||
}
|
||||
for(k = 0; k < BULK_SIZE;k++)
|
||||
s += tab[k];
|
||||
}
|
||||
while (!queue_single_ull_push(g_final_spsc, s));
|
||||
}
|
||||
|
||||
static void prod_spsc_bulk(void *arg)
|
||||
{
|
||||
size_t *p_n = arg;
|
||||
size_t n = *p_n;
|
||||
if ((n % BULK_SIZE) != 0) abort();
|
||||
size_t r = n;
|
||||
unsigned tab[BULK_SIZE];
|
||||
for(unsigned int i = 0; i < n;i+= BULK_SIZE) {
|
||||
for(unsigned k = 0; k < BULK_SIZE; k++) {
|
||||
tab[k] = r;
|
||||
r = r * 31421U + 6927U;
|
||||
}
|
||||
unsigned k = queue_single_uint_push_bulk(g_buff_spsc, BULK_SIZE, tab);
|
||||
while (k != BULK_SIZE) {
|
||||
m_thread_yield();
|
||||
k += queue_single_uint_push_bulk(g_buff_spsc, BULK_SIZE-k, tab+k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void test_queue_single_bulk(size_t n)
|
||||
{
|
||||
const int cpu_count = 2;
|
||||
const int prod_count = cpu_count/2;
|
||||
const int conso_count = cpu_count - prod_count;
|
||||
// Init
|
||||
queue_single_uint_init(g_buff_spsc, 64*cpu_count);
|
||||
queue_single_ull_init (g_final_spsc, 64*cpu_count);
|
||||
|
||||
// Create thread
|
||||
m_thread_t idx_p[prod_count];
|
||||
m_thread_t idx_c[conso_count];
|
||||
m_thread_t idx_final;
|
||||
for(int i = 0; i < prod_count; i++) {
|
||||
m_thread_create (idx_p[i], prod_spsc_bulk, &n);
|
||||
}
|
||||
for(int i = 0; i < conso_count; i++) {
|
||||
m_thread_create (idx_c[i], conso_spsc_bulk, &n);
|
||||
}
|
||||
size_t n2 = conso_count;
|
||||
m_thread_create(idx_final, final_spsc_bulk, &n2);
|
||||
|
||||
// Wait for jobs to be done.
|
||||
for(int i = 0; i < prod_count; i++) {
|
||||
m_thread_join(idx_p[i]);
|
||||
}
|
||||
for(int i = 0; i < conso_count; i++) {
|
||||
m_thread_join(idx_c[i]);
|
||||
}
|
||||
m_thread_join(idx_final);
|
||||
|
||||
// Clear & quit
|
||||
queue_single_ull_clear(g_final_spsc);
|
||||
queue_single_uint_clear(g_buff_spsc);
|
||||
}
|
||||
|
||||
/********************************************************************************************/
|
||||
|
||||
DEQUE_DEF(deque_uint, unsigned int)
|
||||
CONCURRENT_DEF(cdeque_uint, deque_uint_t, M_OPEXTEND(DEQUE_OPLIST(deque_uint, M_DEFAULT_OPLIST), PUSH(deque_uint_push_front)))
|
||||
|
||||
@@ -739,6 +829,8 @@ int main(int argc, const char *argv[])
|
||||
test_function("Queue SPSC time", 1000000, test_queue_single);
|
||||
if (n == 65)
|
||||
test_function("Queue CONCURRENT time", 1000000, test_queue_concurrent);
|
||||
if (n == 66)
|
||||
test_function("Queue SPSC BULK time", 1000000, test_queue_single_bulk);
|
||||
if (n == 70) {
|
||||
n = (argc > 2) ? atoi(argv[2]) : 100000000;
|
||||
test_hash_prepare(n);
|
||||
|
||||
@@ -0,0 +1,288 @@
|
||||
/*
|
||||
* M*LIB - String Bench
|
||||
*
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* + Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* + Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "m-string.h"
|
||||
#include "common.h"
|
||||
|
||||
#ifdef BENCH_CAN_USE_SDS
|
||||
extern "C" {
|
||||
#include "sds.h"
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef BENCH_CAN_USE_RAPIDSTRING
|
||||
#include "rapidstring.h"
|
||||
#endif
|
||||
|
||||
/*
|
||||
* BENCH
|
||||
* 2000000
|
||||
* 1. Generate N strings of random numbers 32 bits.
|
||||
* 2. Concat all strings in a string usigned a random permutation of N.
|
||||
* 3. Replace "1234" in the string into "WELL" until nothing remains.
|
||||
* 4. Replace "56789" in the string into "DONE" until nothing remains.
|
||||
* Return the final string length.
|
||||
*/
|
||||
|
||||
unsigned *permutation_tab = NULL;
|
||||
|
||||
void random_permutation(unsigned n)
|
||||
{
|
||||
if (permutation_tab) free(permutation_tab);
|
||||
permutation_tab = (unsigned *) malloc(n * sizeof(unsigned));
|
||||
if (permutation_tab == NULL) abort();
|
||||
for(unsigned i = 0; i < n; i++)
|
||||
permutation_tab[i] = i;
|
||||
for(unsigned i = 0; i < n; i++) {
|
||||
unsigned j = rand_get() % n;
|
||||
unsigned k = rand_get() % n;
|
||||
unsigned l = permutation_tab[j];
|
||||
permutation_tab[j] = permutation_tab[k];
|
||||
permutation_tab[k] = l;
|
||||
}
|
||||
}
|
||||
|
||||
using namespace std;
|
||||
|
||||
size_t bench_stl(unsigned n)
|
||||
{
|
||||
vector<string> tab;
|
||||
tab.resize(n);
|
||||
for(unsigned i = 0; i < n; i++)
|
||||
tab[i] = to_string(rand_get());
|
||||
|
||||
string str;
|
||||
for(unsigned i = 0; i < n; i++) {
|
||||
str += tab[permutation_tab[i]];
|
||||
}
|
||||
|
||||
size_t pos = 0;
|
||||
do {
|
||||
pos = str.find("1234", pos);
|
||||
if (pos != string::npos) {
|
||||
str.replace (pos, 4, "WELL");
|
||||
}
|
||||
} while (pos != string::npos);
|
||||
|
||||
pos = 0;
|
||||
do {
|
||||
pos = str.find("56789", pos);
|
||||
if (pos != string::npos) {
|
||||
str.replace (pos, 5, "DONE");
|
||||
}
|
||||
} while (pos != string::npos);
|
||||
|
||||
return str.length();
|
||||
}
|
||||
|
||||
size_t bench_mlib(unsigned n)
|
||||
{
|
||||
string_t *tab = (string_t*) malloc (n * sizeof (string_t));
|
||||
assert (tab != 0);
|
||||
// P1
|
||||
for(unsigned i = 0; i < n; i++) {
|
||||
string_init(tab[i]);
|
||||
string_printf(tab[i], "%u", rand_get());
|
||||
}
|
||||
// P2
|
||||
string_t str;
|
||||
string_init(str);
|
||||
for(unsigned i = 0; i < n; i++) {
|
||||
string_cat(str, tab[permutation_tab[i]]);
|
||||
}
|
||||
// P3
|
||||
size_t i = 0;
|
||||
do {
|
||||
i = string_replace_str(str, "1234", "WELL", i);
|
||||
} while (i != STRING_FAILURE);
|
||||
// P4
|
||||
i = 0;
|
||||
do {
|
||||
i = string_replace_str(str, "56789", "DONE", i);
|
||||
} while (i != STRING_FAILURE);
|
||||
size_t length = string_size(str);
|
||||
// Clean
|
||||
string_clear(str);
|
||||
for(unsigned i = 0; i < n; i++) {
|
||||
string_clear(tab[i]);
|
||||
}
|
||||
free(tab);
|
||||
return length;
|
||||
}
|
||||
|
||||
#ifdef BENCH_CAN_USE_SDS
|
||||
sds SDS_replace_at(sds str, size_t pos, size_t len, const char str2[])
|
||||
{
|
||||
// simple implementation as replace is not available
|
||||
sds a = sdsnewlen((const void*) str, pos);
|
||||
a = sdscat(a, str2);
|
||||
a = sdscat(a, &str[pos+len]);
|
||||
sdsfree(str);
|
||||
return a;
|
||||
}
|
||||
|
||||
size_t bench_sds(unsigned n)
|
||||
{
|
||||
sds *tab = (sds*) malloc (n * sizeof (sds));
|
||||
assert (tab != 0);
|
||||
// P1
|
||||
for(unsigned i = 0; i < n; i++) {
|
||||
tab[i] = sdsfromlonglong(rand_get());
|
||||
}
|
||||
// P2
|
||||
sds str = sdsempty();
|
||||
for(unsigned i = 0; i < n; i++) {
|
||||
str = sdscat(str, tab[permutation_tab[i]]);
|
||||
}
|
||||
// P3
|
||||
size_t pos = 0;
|
||||
do {
|
||||
char *p = strstr(&str[pos], "1234");
|
||||
pos = p == NULL ? -1U : (p - str);
|
||||
if (pos != -1U) {
|
||||
str = SDS_replace_at(str, pos, 4, "WELL");
|
||||
}
|
||||
} while (pos != -1U);
|
||||
// P4
|
||||
pos = 0;
|
||||
do {
|
||||
char *p = strstr(&str[pos], "56789");
|
||||
pos = p == NULL ? -1U : (p - str);
|
||||
if (pos != -1U) {
|
||||
str = SDS_replace_at(str, pos, 5, "DONE");
|
||||
}
|
||||
} while (pos != -1U);
|
||||
size_t length = sdslen(str);
|
||||
// Clean
|
||||
sdsfree(str);
|
||||
for(unsigned i = 0; i < n; i++) {
|
||||
sdsfree(tab[i]);
|
||||
}
|
||||
free(tab);
|
||||
return length;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef BENCH_CAN_USE_RAPIDSTRING
|
||||
void RAPIDSTRING_replace_at(rapidstring *str, size_t pos, size_t len, const char str2[])
|
||||
{
|
||||
// simple implementation as replace is not available
|
||||
rapidstring a;
|
||||
|
||||
rs_init_w_n(&a, rs_data(str), pos);
|
||||
rs_cat(&a, str2);
|
||||
rs_cat(&a, &rs_data(str)[pos+len] );
|
||||
rs_free(str);
|
||||
*str = a;
|
||||
}
|
||||
|
||||
size_t bench_rapidstring(unsigned n)
|
||||
{
|
||||
rapidstring *tab = (rapidstring*) malloc (n * sizeof (rapidstring));
|
||||
assert (tab != 0);
|
||||
// P1
|
||||
for(unsigned i = 0; i < n; i++) {
|
||||
char tmp[10];
|
||||
sprintf(tmp, "%u", rand_get());
|
||||
rs_init_w(&tab[i], tmp);
|
||||
}
|
||||
// P2
|
||||
rapidstring str;
|
||||
rs_init(&str);
|
||||
for(unsigned i = 0; i < n; i++) {
|
||||
rs_cat_rs(&str, &tab[permutation_tab[i]]);
|
||||
}
|
||||
// P3
|
||||
size_t pos = 0;
|
||||
do {
|
||||
char *p = strstr(&rs_data(&str)[pos], "1234");
|
||||
pos = p == NULL ? -1U : (p - rs_data(&str));
|
||||
if (pos != -1U) {
|
||||
RAPIDSTRING_replace_at(&str, pos, 4, "WELL");
|
||||
}
|
||||
} while (pos != -1U);
|
||||
// P4
|
||||
pos = 0;
|
||||
do {
|
||||
char *p = strstr(&rs_data(&str)[pos], "56789");
|
||||
pos = p == NULL ? -1U : (p - rs_data(&str));
|
||||
if (pos != -1U) {
|
||||
RAPIDSTRING_replace_at(&str, pos, 5, "DONE");
|
||||
}
|
||||
} while (pos != -1U);
|
||||
size_t length = rs_len(&str);
|
||||
// Clean
|
||||
rs_free(&str);
|
||||
for(unsigned i = 0; i < n; i++) {
|
||||
rs_free(&tab[i]);
|
||||
}
|
||||
free(tab);
|
||||
return length;
|
||||
}
|
||||
#endif
|
||||
|
||||
int main(int argc, const char *argv[])
|
||||
{
|
||||
unsigned length, n = atoi(argv[1]);
|
||||
unsigned select = atoi(argv[2]);
|
||||
const char *name;
|
||||
random_permutation(n);
|
||||
rand_init();
|
||||
unsigned long long t0 = cputime();
|
||||
|
||||
switch (select) {
|
||||
case 0:
|
||||
length = bench_mlib(n);
|
||||
name = "MLIB";
|
||||
break;
|
||||
case 1:
|
||||
length = bench_stl(n);
|
||||
name = "STL";
|
||||
break;
|
||||
#ifdef BENCH_CAN_USE_SDS
|
||||
case 2:
|
||||
length = bench_sds(n);
|
||||
name = "SDS";
|
||||
break;
|
||||
#endif
|
||||
#ifdef BENCH_CAN_USE_RAPIDSTRING
|
||||
case 3:
|
||||
length = bench_rapidstring(n);
|
||||
name = "RAPID";
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
exit(0);
|
||||
}
|
||||
unsigned long long t1 = cputime();
|
||||
printf("%5.5s LENGTH=%u T= %f s\n", name, length, (t1-t0) / 1000000.0 );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
+522
-169
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* M*LIB - common functions for bench module
|
||||
*
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* + Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* + Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
|
||||
void compiler_barrier(void *p)
|
||||
{
|
||||
// Do nothing, but let the compiler thinks that we did.
|
||||
(void) p;
|
||||
// Can be used for FLTO building
|
||||
// asm volatile("": "+r" (p) : :"memory");
|
||||
}
|
||||
+20
-7
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* M*LIB - common functions for bench module
|
||||
*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -25,11 +25,14 @@
|
||||
#ifndef __COMMON_BENCH__H
|
||||
#define __COMMON_BENCH__H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#if defined(_WIN32)
|
||||
/* WINDOWS variant */
|
||||
#include <windows.h>
|
||||
|
||||
static unsigned long long
|
||||
static inline unsigned long long
|
||||
cputime (void)
|
||||
{
|
||||
LARGE_INTEGER freq, val;
|
||||
@@ -44,7 +47,7 @@ cputime (void)
|
||||
#include <sys/resource.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
static unsigned long long
|
||||
static inline unsigned long long
|
||||
cputime (void)
|
||||
{
|
||||
#if defined(MULTI_THREAD_MEASURE)
|
||||
@@ -62,20 +65,25 @@ cputime (void)
|
||||
|
||||
#endif
|
||||
|
||||
unsigned long g_result;
|
||||
static unsigned long g_result;
|
||||
static
|
||||
#if defined(MULTI_THREAD_MEASURE)
|
||||
M_THREAD_ATTR
|
||||
#endif
|
||||
unsigned int randValue = 0;
|
||||
|
||||
static void rand_init(void)
|
||||
static inline void rand_init(void)
|
||||
{
|
||||
randValue = 0;
|
||||
}
|
||||
static unsigned int rand_get(void)
|
||||
|
||||
static inline unsigned int rand_get(void)
|
||||
{
|
||||
randValue = randValue * 31421U + 6927U;
|
||||
return randValue;
|
||||
}
|
||||
|
||||
static void test_function(const char str[], size_t n, void (*func)(size_t))
|
||||
static inline void test_function(const char str[], size_t n, void (*func)(size_t))
|
||||
{
|
||||
unsigned long long start, end;
|
||||
rand_init();
|
||||
@@ -120,4 +128,9 @@ static inline int get_cpu_count(void)
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
#endif
|
||||
void compiler_barrier(void *p);
|
||||
|
||||
#endif
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* MLIB - TEST module
|
||||
*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
Binary file not shown.
+92
-15
@@ -1,6 +1,58 @@
|
||||
*Long term issues*
|
||||
------------------
|
||||
|
||||
* TODO #24 : Add MIN-MAX-HEAP container :ENHANCEMENT:
|
||||
|
||||
See https://en.wikipedia.org/wiki/Min-max_heap
|
||||
as DPRIORITY_QUEUE_DEF ?
|
||||
|
||||
|
||||
* TODO #23: Force MOVE semantic :ENHANCEMENT:
|
||||
|
||||
Some type may need to have a force MOVE semantic (for example, they can store
|
||||
pointer to themselves). Currently the INIT_MOVE & MOVE operators are more
|
||||
a help for performance than a strict semantic usage.
|
||||
|
||||
** ARRAY container
|
||||
|
||||
The ARRAY container doesn't support strict MOVE semantic for example.
|
||||
It is not a simple matter as it performs a realloc of the table, thus
|
||||
moving the data before they can be moved using MOVE. Two solutions:
|
||||
|
||||
- New operator MOVE_SELF to fix a type after it has been moved.
|
||||
- If MOVE defined, force another table and then copy by hand the type. This will be slower and consumme more memory.
|
||||
|
||||
** MOVE in OPLIST :ENHANCEMENT:
|
||||
|
||||
Proposal: do not export INIT_MOVE / MOVE operator in OPLIST if the MOVE operator is
|
||||
compatible with a pure COPY semantic. An exported MOVE operator will tell
|
||||
other containers than the type shall be carefully moved using the provided
|
||||
MOVE operator.
|
||||
|
||||
For example for tuple, it shall
|
||||
|
||||
- create an init_move operator if no one has disabled INIT_MOVE,
|
||||
- export the init_move operator if at least one has exported a INIT_MOVE and no one has disabled INIT_MOVE.
|
||||
|
||||
|
||||
* DONE #22: Enhanced services for SPSC Queue :ENHANCEMENT:
|
||||
|
||||
Add services:
|
||||
|
||||
** DONE _push_bulk
|
||||
Test the capacity of the queue and push as much as possible in the queue
|
||||
with one check of the atomic structure.
|
||||
|
||||
** DONE _pop_bulk
|
||||
Test the capacity of the queue and push as much as possible in the queue
|
||||
with one check of the atomic structure.
|
||||
|
||||
** DONE _push_force:
|
||||
|
||||
If the capacity of the queue is full, pop one element and push it:
|
||||
push always succeed and the queue always keep the youngest element.
|
||||
|
||||
|
||||
* TODO #21: Generic Binary serialization :ENHANCEMENT:
|
||||
|
||||
Based on issue #26 of https://github.com/P-p-H-d/mlib/issues/26
|
||||
@@ -59,16 +111,17 @@ http://htmlpreview.github.io/?https://github.com/P-p-H-d/mlib/blob/master/doc/Co
|
||||
|
||||
A ressouce handler which shall associated a unique handle to a ressource.
|
||||
The handle shall be unique for the ressource and shall not be reused.
|
||||
It is typically a 64 bits integers always incremented (even the program
|
||||
It is typically a 64 bits integers always incremented (even if the program
|
||||
creates one billion ressources per second, the counter won't overflow
|
||||
until 585 years).
|
||||
|
||||
The ressource handler shall also keep count on:
|
||||
The ressource handler shall make an association between a HANDLE 64 bits and:
|
||||
|
||||
- how much real owners claim to own the ressource
|
||||
(the ressource is only owned by the ressource handler, however
|
||||
it acts as delegate of the real owner),
|
||||
it acts as a delegate of the real owner),
|
||||
- how much users keep a pointer to the ressource.
|
||||
- pointer to the resource itself.
|
||||
|
||||
This may be a better alternative than shared_ptr & weak_ptr:
|
||||
|
||||
@@ -77,7 +130,12 @@ http://htmlpreview.github.io/?https://github.com/P-p-H-d/mlib/blob/master/doc/Co
|
||||
- shared_ref & weak_ref becomes only HANDLE,
|
||||
- all ressources can be freed in one pass.
|
||||
|
||||
Needs lock free dictionnary.
|
||||
Needs lock free dictionnary or at least concurrent dictionnary.
|
||||
|
||||
How to handle multiple resource ?
|
||||
|
||||
* variant: works fine. Memory usage can be (much) higher than needed if there is a lot of dissimilarity between the size of the objects.
|
||||
* embedded the type in the ressource handler: more work, API more complex. Memory usage seems better.
|
||||
|
||||
|
||||
* TODO #16: Lock Free List :ENHANCEMENT:
|
||||
@@ -85,22 +143,23 @@ http://htmlpreview.github.io/?https://github.com/P-p-H-d/mlib/blob/master/doc/Co
|
||||
Implement a lock free list. Most of the difficulty is the memory reclamation part.
|
||||
Typically this lock free list shall be compatible with RCU method.
|
||||
|
||||
A node can be in the following state:
|
||||
** First step: backoff methods :DONE:
|
||||
** Second step: lock free node pool : :DONE:
|
||||
Done as m-c-mempool header.
|
||||
|
||||
- FREE: Can be used for allocation.
|
||||
- IN_USE: The node is currently used in the list.
|
||||
- TO_BE_RECLAIMED: The node is semantically removed, but some others may still be used it.
|
||||
- CAN_BE_RECLAIMED: The node is semantically removed and no one still use it.
|
||||
** Third step: Implement generic lock free list on top of it.
|
||||
|
||||
So separate lists shall be used for the memory handler.
|
||||
It shall also be compatible with NUMA and avoid putting all nodes in the same lists if possible:
|
||||
otherwise the head will become a hot spot and slow down everything.
|
||||
|
||||
The ABA problem shall also be taken into account. Normaly with the provided state of the nodes,
|
||||
it should not be a problem.
|
||||
The ABA problem is already taken into account by the memory alloctor
|
||||
provided that the lock free list doesn't try to be smart.
|
||||
|
||||
backoff has be used when using CAS.
|
||||
|
||||
Concurrent insertion / insertion and insertion / deletion and deletion / deletion shall be taken into account.
|
||||
|
||||
Questions:
|
||||
- singly or doubly or dual push?
|
||||
- needs to be logically deleted : needs a previous field
|
||||
(NULL if not logically deleted) ? TBC
|
||||
|
||||
|
||||
* TODO #15: Constructor / Destructor Prologue / Epilogue for Stack Exception Handling :ENHANCEMENT:
|
||||
@@ -144,6 +203,16 @@ Update:
|
||||
API transformation support enables "instance-based allocator" to be made easily.
|
||||
Needs some formal operator in the oplist to support it fully and an example.
|
||||
|
||||
Can be supported using another API extension, some more operators and forcing some names:
|
||||
|
||||
* API_N: call like FUNC(obj->extra_data, type)
|
||||
|
||||
'obj' is a forced named corresponding to an alias to an object in the function.
|
||||
Operator needed:
|
||||
|
||||
- EXTRA_DATA: Add an extra-data field wihtin the container. Defines the type of data.
|
||||
|
||||
It is a kind of object inheritance where the container inherits some extra data from its base.
|
||||
|
||||
* TODO #12: Atomic shared pointer :ENHANCEMENT:
|
||||
|
||||
@@ -261,3 +330,11 @@ Another solution is to create a huge list of items which is:
|
||||
|
||||
Hash table will only give quick access to items to this list. Expanding the table won't change the order of the list (so the items will remain at the same place and always accessible by other threads) but just add sentinel in the sentinel at the right place (there won't be any reallocation of the list). Inserting will need to insert an item at the right place.
|
||||
Not sure it will really help. To analyse.
|
||||
|
||||
Concurrent dictionary is possible now with CONCURRENT_DEF + DICT_DEF.
|
||||
But it uses a global lock for all dictionary access.
|
||||
|
||||
** Open Addressing Indirect Table with a lock free node pool used as transaction pool.
|
||||
|
||||
A good way maybe Open Addessing table used only for indirection and a freelist memory reclamation container for handle the entries
|
||||
(like a transaction).
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 91 KiB |
@@ -0,0 +1,40 @@
|
||||
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "m-array.h"
|
||||
#include "m-tuple.h"
|
||||
#include "m-dict.h"
|
||||
#include "m-string.h"
|
||||
|
||||
TUPLE_DEF2(symbol, (offset, long), (value, long))
|
||||
#define M_OPL_symbol_t() TUPLE_OPLIST(symbol, M_DEFAULT_OPLIST, M_DEFAULT_OPLIST)
|
||||
|
||||
ARRAY_DEF(array_symbol, symbol_t)
|
||||
#define M_OPL_array_symbol_t() ARRAY_OPLIST(array_symbol, M_OPL_symbol_t())
|
||||
|
||||
DICT_DEF2(sections, string_t, array_symbol_t)
|
||||
#define M_OPL_sections_t() DICT_OPLIST(sections, STRING_OPLIST, M_OPL_array_symbol_t())
|
||||
|
||||
int main(int argc, const char *argv[])
|
||||
{
|
||||
if (argc < 2) abort();
|
||||
FILE *f = fopen(argv[1], "rt");
|
||||
if (!f) abort();
|
||||
M_LET(sc, sections_t) {
|
||||
sections_in_str(sc, f);
|
||||
array_symbol_t *a = sections_get(sc, STRING_CTE(".text"));
|
||||
if (a == NULL) {
|
||||
printf("There is no .text section.");
|
||||
} else {
|
||||
printf("Section .text is :");
|
||||
array_symbol_out_str(stdout, *a);
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
int main(void) { return 0; }
|
||||
#endif
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* M*LIB - ALGO module
|
||||
*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -105,6 +105,8 @@
|
||||
|
||||
#define ALGOI_DEF_P2(name, container_t, cont_oplist, type_t, type_oplist, it_t) \
|
||||
\
|
||||
M_IF_METHOD(EQUAL, type_oplist)( \
|
||||
\
|
||||
/* It supposes that the container is not sorted */ \
|
||||
static inline void \
|
||||
M_C(name, _find_again) (it_t it, type_t const data) \
|
||||
@@ -117,20 +119,90 @@
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _find) (it_t it, container_t l, type_t const data) \
|
||||
M_C(name, _find) (it_t it, container_t const l, type_t const data) \
|
||||
{ \
|
||||
M_CALL_IT_FIRST(cont_oplist, it, l); \
|
||||
M_C(name, _find_again)(it, data); \
|
||||
} \
|
||||
\
|
||||
static inline bool \
|
||||
M_C(name, _contain_p) (container_t l, type_t const data) \
|
||||
M_C(name, _contain_p) (container_t const l, type_t const data) \
|
||||
{ \
|
||||
it_t it; \
|
||||
M_C(name,_find)(it, l, data); \
|
||||
return !M_CALL_IT_END_P(cont_oplist, it); \
|
||||
} \
|
||||
\
|
||||
/* For the definition of _find_last, if the methods \
|
||||
PREVIOUS & IT_LAST are defined, then search backwards */ \
|
||||
M_IF_METHOD2(PREVIOUS, IT_LAST, cont_oplist) \
|
||||
( \
|
||||
static inline void \
|
||||
M_C(name, _find_last) (it_t it, container_t const l, type_t const data) \
|
||||
{ \
|
||||
for (M_CALL_IT_LAST(cont_oplist, it, l); \
|
||||
!M_CALL_IT_END_P(cont_oplist, it) ; \
|
||||
M_CALL_IT_PREVIOUS(cont_oplist, it)) { \
|
||||
if (M_CALL_EQUAL(type_oplist, *M_CALL_IT_CREF(cont_oplist, it), data)) \
|
||||
/* We can stop as soon as a match is found */ \
|
||||
return; \
|
||||
} \
|
||||
} \
|
||||
, \
|
||||
/* Otherwise search forward, but don't stop on the first occurence */ \
|
||||
static inline void \
|
||||
M_C(name, _find_last) (it_t it, container_t const l, type_t const data) \
|
||||
{ \
|
||||
M_CALL_IT_END(cont_oplist, it, l); \
|
||||
it_t it2; \
|
||||
for (M_CALL_IT_FIRST(cont_oplist, it2, l); \
|
||||
!M_CALL_IT_END_P(cont_oplist, it2) ; \
|
||||
M_CALL_IT_NEXT(cont_oplist, it2)) { \
|
||||
if (M_CALL_EQUAL(type_oplist, *M_CALL_IT_CREF(cont_oplist, it2), data)) \
|
||||
/* We cannot stop as soon as a match is found */ \
|
||||
M_CALL_IT_SET(cont_oplist, it, it2) ; \
|
||||
} \
|
||||
} \
|
||||
) /* End of alternative of _find_last */ \
|
||||
\
|
||||
static inline size_t \
|
||||
M_C(name, _count) (container_t const l, type_t const data) \
|
||||
{ \
|
||||
it_t it; \
|
||||
size_t count = 0; \
|
||||
for (M_CALL_IT_FIRST(cont_oplist, it, l); \
|
||||
!M_CALL_IT_END_P(cont_oplist, it) ; \
|
||||
M_CALL_IT_NEXT(cont_oplist, it)) { \
|
||||
if (M_CALL_EQUAL(type_oplist, *M_CALL_IT_CREF(cont_oplist, it), data)) \
|
||||
count++ ; \
|
||||
} \
|
||||
return count; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _mismatch_again) (it_t it1, it_t it2) \
|
||||
{ \
|
||||
for (/* nothing */ ; !M_CALL_IT_END_P(cont_oplist, it1) && \
|
||||
!M_CALL_IT_END_P(cont_oplist, it2); \
|
||||
M_CALL_IT_NEXT(cont_oplist, it1), \
|
||||
M_CALL_IT_NEXT(cont_oplist, it2)) { \
|
||||
if (!M_CALL_EQUAL(type_oplist, *M_CALL_IT_CREF(cont_oplist, it1), \
|
||||
*M_CALL_IT_CREF(cont_oplist, it2))) \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _mismatch) (it_t it1, it_t it2, container_t const l1, container_t const l2 ) \
|
||||
{ \
|
||||
M_CALL_IT_FIRST(cont_oplist, it1, l1); \
|
||||
M_CALL_IT_FIRST(cont_oplist, it2, l2); \
|
||||
M_C(name, _mismatch_again)(it1, it2); \
|
||||
} \
|
||||
\
|
||||
, /* NO EQUAL */) \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _find_again_if) (it_t it, bool (*func)(type_t const)) \
|
||||
{ \
|
||||
@@ -148,54 +220,9 @@
|
||||
M_C(name, _find_again_if)(it, func); \
|
||||
} \
|
||||
\
|
||||
/* For the definition of _find_last, if the methods \
|
||||
PREVIOUS & IT_LAST are defined, then search backwards */ \
|
||||
M_IF_METHOD2(PREVIOUS, IT_LAST, cont_oplist) \
|
||||
( \
|
||||
static inline void \
|
||||
M_C(name, _find_last) (it_t it, container_t l, type_t const data) \
|
||||
{ \
|
||||
for (M_CALL_IT_LAST(cont_oplist, it, l); \
|
||||
!M_CALL_IT_END_P(cont_oplist, it) ; \
|
||||
M_CALL_IT_PREVIOUS(cont_oplist, it)) { \
|
||||
if (M_CALL_EQUAL(type_oplist, *M_CALL_IT_CREF(cont_oplist, it), data)) \
|
||||
/* We can stop as soon as a match is found */ \
|
||||
return; \
|
||||
} \
|
||||
} \
|
||||
, \
|
||||
/* Otherwise search forward, but don't stop on the first occurence */ \
|
||||
static inline void \
|
||||
M_C(name, _find_last) (it_t it, container_t l, type_t const data) \
|
||||
{ \
|
||||
M_CALL_IT_END(cont_oplist, it, l); \
|
||||
it_t it2; \
|
||||
for (M_CALL_IT_FIRST(cont_oplist, it2, l); \
|
||||
!M_CALL_IT_END_P(cont_oplist, it2) ; \
|
||||
M_CALL_IT_NEXT(cont_oplist, it2)) { \
|
||||
if (M_CALL_EQUAL(type_oplist, *M_CALL_IT_CREF(cont_oplist, it2), data)) \
|
||||
/* We cannot stop as soon as a match is found */ \
|
||||
M_CALL_IT_SET(cont_oplist, it, it2) ; \
|
||||
} \
|
||||
} \
|
||||
) /* End of alternative of _find_last */ \
|
||||
\
|
||||
static inline size_t \
|
||||
M_C(name, _count) (container_t l, type_t const data) \
|
||||
{ \
|
||||
it_t it; \
|
||||
size_t count = 0; \
|
||||
for (M_CALL_IT_FIRST(cont_oplist, it, l); \
|
||||
!M_CALL_IT_END_P(cont_oplist, it) ; \
|
||||
M_CALL_IT_NEXT(cont_oplist, it)) { \
|
||||
if (M_CALL_EQUAL(type_oplist, *M_CALL_IT_CREF(cont_oplist, it), data)) \
|
||||
count++ ; \
|
||||
} \
|
||||
return count; \
|
||||
} \
|
||||
\
|
||||
static inline size_t \
|
||||
M_C(name, _count_if) (container_t l, bool (*func)(type_t const data)) \
|
||||
M_C(name, _count_if) (container_t const l, bool (*func)(type_t const data)) \
|
||||
{ \
|
||||
it_t it; \
|
||||
size_t count = 0; \
|
||||
@@ -209,27 +236,6 @@
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _mismatch_again) (it_t it1, it_t it2) \
|
||||
{ \
|
||||
for (/* nothing */ ; !M_CALL_IT_END_P(cont_oplist, it1) && \
|
||||
!M_CALL_IT_END_P(cont_oplist, it2); \
|
||||
M_CALL_IT_NEXT(cont_oplist, it1), \
|
||||
M_CALL_IT_NEXT(cont_oplist, it2)) { \
|
||||
if (!M_CALL_EQUAL(type_oplist, *M_CALL_IT_CREF(cont_oplist, it1), \
|
||||
*M_CALL_IT_CREF(cont_oplist, it2))) \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _mismatch) (it_t it1, it_t it2, container_t l1, container_t l2 ) \
|
||||
{ \
|
||||
M_CALL_IT_FIRST(cont_oplist, it1, l1); \
|
||||
M_CALL_IT_FIRST(cont_oplist, it2, l2); \
|
||||
M_C(name, _mismatch_again)(it1, it2); \
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _mismatch_again_if) (it_t it1, it_t it2, bool (*func)(type_t const, type_t const) ) \
|
||||
{ \
|
||||
for (/*nothing */ ; !M_CALL_IT_END_P(cont_oplist, it1) && \
|
||||
@@ -243,7 +249,7 @@
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _mismatch_if) (it_t it1, it_t it2, container_t l1, \
|
||||
M_C(name, _mismatch_if) (it_t it1, it_t it2, container_t const l1, \
|
||||
container_t l2, bool (*func)(type_t const, type_t const) ) \
|
||||
{ \
|
||||
M_CALL_IT_FIRST(cont_oplist, it1, l1); \
|
||||
@@ -260,7 +266,8 @@
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _reduce) (type_t *dest, container_t l, void (*f)(type_t *, type_t const) ) \
|
||||
M_C(name, _reduce) (type_t *dest, container_t const l, \
|
||||
void (*f)(type_t *, type_t const) ) \
|
||||
{ \
|
||||
bool initDone = false; \
|
||||
for M_EACH(item, l, cont_oplist) { \
|
||||
@@ -273,9 +280,10 @@
|
||||
} \
|
||||
} \
|
||||
\
|
||||
M_IF_METHOD(INIT, type_oplist)( \
|
||||
static inline \
|
||||
void M_C(name, _map_reduce) (type_t *dest, \
|
||||
container_t l, \
|
||||
const container_t l, \
|
||||
void (*redFunc)(type_t*, type_t const), \
|
||||
void (*mapFunc)(type_t*, type_t const) ) \
|
||||
{ \
|
||||
@@ -293,9 +301,10 @@
|
||||
} \
|
||||
M_CALL_CLEAR(type_oplist, tmp); \
|
||||
} \
|
||||
, ) \
|
||||
\
|
||||
static inline bool \
|
||||
M_C(name, _any_of_p) (container_t l, bool (*f)(type_t const) ) \
|
||||
M_C(name, _any_of_p) (container_t const l, bool (*f)(type_t const) ) \
|
||||
{ \
|
||||
for M_EACH(item, l, cont_oplist) { \
|
||||
if (f(*item)) \
|
||||
@@ -305,7 +314,7 @@
|
||||
} \
|
||||
\
|
||||
static inline bool \
|
||||
M_C(name, _all_of_p) (container_t l, bool (*f)(type_t const) ) \
|
||||
M_C(name, _all_of_p) (container_t const l, bool (*f)(type_t const) ) \
|
||||
{ \
|
||||
for M_EACH(item, l, cont_oplist) { \
|
||||
if (!f(*item)) \
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* M*LIB - dynamic ARRAY module
|
||||
*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -32,7 +32,7 @@
|
||||
#define ARRAY_DEF(name, ...) \
|
||||
ARRAYI_DEF(M_IF_NARGS_EQ1(__VA_ARGS__) \
|
||||
((name, __VA_ARGS__, M_GLOBAL_OPLIST_OR_DEF(__VA_ARGS__)(), M_C(name,_t), M_C(name,_it_t) ), \
|
||||
(name, __VA_ARGS__, M_C(name,_t), M_C(name,_it_t))))
|
||||
(name, __VA_ARGS__, M_C(name,_t), M_C(name,_it_t))))
|
||||
|
||||
/* Define the oplist of a dynamic array given its name and its oplist.
|
||||
USAGE: ARRAY_OPLIST(name[, oplist of the type]) */
|
||||
@@ -49,7 +49,7 @@
|
||||
|
||||
/* OPLIST definition of a dynamic array */
|
||||
/* FIXME: Do we want to export some methods as they are slow and
|
||||
are not fit to be used for building other methods (like _remove)? */
|
||||
are not fit to be used for building other methods (like _it_remove)? */
|
||||
#define ARRAYI_OPLIST2(name, oplist) \
|
||||
(INIT(M_C(name, _init)) \
|
||||
,INIT_SET(M_C(name, _init_set)) \
|
||||
@@ -85,6 +85,7 @@
|
||||
,GET_KEY(M_C(name, _get)) \
|
||||
,M_IF_METHOD(INIT, oplist)(GET_SET_KEY(M_C(name, _get_at)) ,) \
|
||||
,ERASE_KEY(M_C(name, _erase)) \
|
||||
,GET_SIZE(M_C(name, _size)) \
|
||||
,PUSH(M_C(name,_push_back)) \
|
||||
,POP(M_C(name,_pop_back)) \
|
||||
,PUSH_MOVE(M_C(name,_push_move)) \
|
||||
@@ -102,7 +103,7 @@
|
||||
,M_IF_METHOD(DEL, oplist)(DEL(M_GET_DEL oplist),) \
|
||||
)
|
||||
|
||||
/* Define the contract of an array */
|
||||
/* Define the internal contract of an array */
|
||||
#define ARRAYI_CONTRACT(a) do { \
|
||||
assert (a != NULL); \
|
||||
assert (a->size <= a->alloc); \
|
||||
@@ -118,7 +119,7 @@
|
||||
typedef struct M_C(name, _s) { \
|
||||
size_t size; /* Number of elements in the array */ \
|
||||
size_t alloc; /* Allocated size for the array */ \
|
||||
type *ptr; /* Pointer to the array */ \
|
||||
type *ptr; /* Pointer to the array base */ \
|
||||
} array_t[1]; \
|
||||
typedef struct M_C(name, _s) *M_C(name, _ptr); \
|
||||
typedef const struct M_C(name, _s) *M_C(name, _srcptr); \
|
||||
@@ -126,8 +127,8 @@
|
||||
typedef type M_C(name, _type_t); \
|
||||
\
|
||||
typedef struct M_C(name, _it_s) { \
|
||||
size_t index; \
|
||||
const struct M_C(name, _s) *array; \
|
||||
size_t index; /* Index of the element */ \
|
||||
const struct M_C(name, _s) *array; /* Reference of the array */ \
|
||||
} array_it_t[1]; \
|
||||
\
|
||||
static inline void \
|
||||
@@ -218,7 +219,7 @@
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _set_at)(array_t v, size_t i, type x) \
|
||||
M_C(name, _set_at)(array_t v, size_t i, type const x) \
|
||||
{ \
|
||||
ARRAYI_CONTRACT(v); \
|
||||
assert(v->size > 0 && v->ptr != NULL && i < v->size); \
|
||||
@@ -495,10 +496,17 @@
|
||||
ARRAYI_CONTRACT(v); \
|
||||
assert(i <= v->size); \
|
||||
size_t size = v->size + num; \
|
||||
/* Avoid overflow of v->size + num */ \
|
||||
if (v->size > v->alloc-num) { \
|
||||
/* Test for overflow of variable size */ \
|
||||
if (M_UNLIKELY (size <= v->size)) { \
|
||||
/* Unlikely case of nothing to do */ \
|
||||
if (num == 0) return; \
|
||||
M_MEMORY_FULL(sizeof (type) * v->size); \
|
||||
return ; \
|
||||
} \
|
||||
/* Test if alloc array is sufficient */ \
|
||||
if (size > v->alloc) { \
|
||||
size_t alloc = M_CALL_INC_ALLOC(oplist, size) ; \
|
||||
if (M_UNLIKELY (size <= v->size || alloc <= v->alloc)) { \
|
||||
if (M_UNLIKELY (alloc <= v->alloc)) { \
|
||||
M_MEMORY_FULL(sizeof (type) * alloc); \
|
||||
return ; \
|
||||
} \
|
||||
|
||||
+18
-6
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* M*LIB - Thin stdatomic wrapper for C++ compatibility
|
||||
*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -98,7 +98,7 @@ using std::memory_order_seq_cst;
|
||||
|
||||
#define _Atomic(T) std::atomic< T >
|
||||
|
||||
#elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L) \
|
||||
#elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L) \
|
||||
|| ( defined(__GNUC__) && !defined(__clang__) && !defined(__cplusplus) && (__GNUC__*100 + __GNUC_MINOR__) >= 409) \
|
||||
|| (defined(__clang__) && __clang_major__ >= 4)
|
||||
|
||||
@@ -106,6 +106,18 @@ using std::memory_order_seq_cst;
|
||||
and doesn't provide its own stdatomic.h */
|
||||
#include <stdatomic.h>
|
||||
|
||||
/* MSYS2 has a conflict between cdefs.h which defines a _Atomic macro (if not C11)
|
||||
not compatible with the used stdatomic.h (from GCC).
|
||||
Provide a configurable mechanism to undef it with auto-detection of msys2 / gcc */
|
||||
#ifndef M_USE_UNDEF_ATOMIC
|
||||
# if defined(__MSYS__) && defined(__GNUC__) && (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 201112L)
|
||||
# define M_USE_UNDEF_ATOMIC 1
|
||||
# endif
|
||||
#endif
|
||||
#if defined(M_USE_UNDEF_ATOMIC) && M_USE_UNDEF_ATOMIC == 1
|
||||
# undef _Atomic
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
/* Non working atomic.h, nor working stdatomic.h found.
|
||||
@@ -125,10 +137,10 @@ using std::memory_order_seq_cst;
|
||||
*/
|
||||
#define _Atomic(T) \
|
||||
struct { \
|
||||
volatile T _val; \
|
||||
T _zero; \
|
||||
T _previous; \
|
||||
m_mutex_t _lock; \
|
||||
T volatile _val; \
|
||||
T _zero; \
|
||||
T _previous; \
|
||||
m_mutex_t _lock; \
|
||||
}
|
||||
|
||||
/* Even if memory order is defined, only the strongest constraint is used */
|
||||
|
||||
+22
-18
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* M*LIB - BITSET module
|
||||
*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -30,33 +30,21 @@
|
||||
|
||||
/********************************** INTERNAL ************************************/
|
||||
|
||||
// Define a limb of a bitset
|
||||
// Define the limb of a bitset
|
||||
typedef uint64_t bitset_limb;
|
||||
// And its size
|
||||
#define BITSET_LIMB_BIT (sizeof(bitset_limb) * CHAR_BIT)
|
||||
|
||||
typedef struct bitset_s {
|
||||
size_t size; // Size is the number of bits
|
||||
size_t alloc; // Alloc is the number of allocated limbs
|
||||
bitset_limb *ptr; // Pointer to the allocated limbs
|
||||
} bitset_t[1];
|
||||
typedef struct bitset_s *bitset_ptr;
|
||||
typedef const struct bitset_s *bitset_srcptr;
|
||||
|
||||
typedef struct bitset_it_s {
|
||||
size_t index; // index to the array of bit
|
||||
bool value; // value used for _ref & _cref to store the value
|
||||
struct bitset_s *set; // the associated bitset
|
||||
} bitset_it_t[1];
|
||||
|
||||
// bitset grow policy
|
||||
// bitset grow policy. n is limb size
|
||||
#define BITSETI_INC_ALLOC_SIZE(n) ((n) < 4 ? 4 : (n) * 2)
|
||||
|
||||
// Compute the number of allocated limbs needed to handle 'n' bits.
|
||||
#define BITSETI_TO_ALLOC(n) (((n) + BITSET_LIMB_BIT - 1) / BITSET_LIMB_BIT)
|
||||
|
||||
// Compute the number of bits available from the allocated size
|
||||
// Compute the number of bits available from the allocated size in limbs
|
||||
#define BITSETI_FROM_ALLOC(n) ((n) * BITSET_LIMB_BIT)
|
||||
|
||||
// Contract of a bitset
|
||||
#define BITSETI_CONTRACT(t) do { \
|
||||
assert (t != NULL); \
|
||||
assert (t->size <= BITSETI_FROM_ALLOC (t->alloc)); \
|
||||
@@ -68,6 +56,21 @@ typedef struct bitset_it_s {
|
||||
|
||||
/********************************** EXTERNAL ************************************/
|
||||
|
||||
typedef struct bitset_s {
|
||||
size_t size; // Size is the number of bits
|
||||
size_t alloc; // Alloc is the number of allocated limbs
|
||||
bitset_limb *ptr; // Pointer to the allocated limbs
|
||||
} bitset_t[1];
|
||||
|
||||
typedef struct bitset_s *bitset_ptr;
|
||||
typedef const struct bitset_s *bitset_srcptr;
|
||||
|
||||
typedef struct bitset_it_s {
|
||||
size_t index; // index to the array of bit
|
||||
bool value; // value used for _ref & _cref to store the value
|
||||
struct bitset_s *set; // the associated bitset
|
||||
} bitset_it_t[1];
|
||||
|
||||
static inline void
|
||||
bitset_init(bitset_t t)
|
||||
{
|
||||
@@ -689,6 +692,7 @@ bitset_clz(const bitset_t set)
|
||||
,TYPE(bitset_t) \
|
||||
,SUBTYPE(bool) \
|
||||
,TEST_EMPTY(bitset_empty_p), \
|
||||
,GET_SIZE(bitset_size) \
|
||||
,IT_TYPE(bitset_it_t) \
|
||||
,IT_FIRST(bitset_it) \
|
||||
,IT_SET(bitset_it_set) \
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* M*LIB - B+TREE module
|
||||
*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -400,7 +400,7 @@
|
||||
return n; \
|
||||
} \
|
||||
\
|
||||
static inline value_t *M_C(name, _get)(tree_t b, key_t const key) \
|
||||
static inline value_t *M_C(name, _get)(const tree_t b, key_t const key) \
|
||||
{ \
|
||||
BPTREEI_CONTRACT(N, key_oplist, b); \
|
||||
pit_t pit; \
|
||||
@@ -415,7 +415,7 @@
|
||||
return NULL; \
|
||||
} \
|
||||
\
|
||||
static inline const value_t *M_C(name, _cget)(tree_t b, key_t const key) \
|
||||
static inline const value_t *M_C(name, _cget)(const tree_t b, key_t const key) \
|
||||
{ \
|
||||
return M_CONST_CAST(value_t, M_C(name, _get)(b, key)); \
|
||||
} \
|
||||
|
||||
+90
-17
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* M*LIB - Fixed size BUFFER & STACK interface
|
||||
* M*LIB - Fixed size (Bounded) QUEUE & STACK interface
|
||||
*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -29,12 +29,12 @@
|
||||
#include "m-mutex.h"
|
||||
#include "m-atomic.h"
|
||||
|
||||
/* Define the different kind of policy a buffer can have:
|
||||
/* Define the different kind of policy a lock-based buffer can have:
|
||||
* - the buffer can be either a queue (policy is FIFO) or a stack (policy is FILO),
|
||||
* - if the push is blocking (waiting for the buffer to has some space) or not,
|
||||
* - if the pop is blocking (waiting for the buffer to has some space) or not,
|
||||
* - if both push / pop are blocking,
|
||||
* - if it shall be thread safe or not (i.e. remove mutex and atomic costs),
|
||||
* - if the push method is by default blocking (waiting for the buffer to has some space) or not,
|
||||
* - if the pop method is by default blocking (waiting for the buffer to has some data) or not,
|
||||
* - if both methods are blocking,
|
||||
* - if it shall be thread safe or not (i.e. remove the mutex lock and atomic costs),
|
||||
* - if the buffer has to be init with empty elements, or if it shall init an element when it is pushed (and moved when popped),
|
||||
* - if the buffer has to overwrite the last element if the buffer is full,
|
||||
* - if the pop of an element is not complete until the call to pop_release (preventing push until this call).
|
||||
@@ -581,6 +581,8 @@ M_C(name, _init)(buffer_t v, size_t size) \
|
||||
,PUSH(M_C(name,_push)) \
|
||||
,POP(M_C(name,_pop)) \
|
||||
,OPLIST(oplist) \
|
||||
,TEST_EMPTY(M_C(name, _empty_p)), \
|
||||
,GET_SIZE(M_C(name, _size)) \
|
||||
)
|
||||
|
||||
|
||||
@@ -624,22 +626,22 @@ M_C(name, _init)(buffer_t v, size_t size) \
|
||||
typedef struct M_C(name, _el_s) { \
|
||||
atomic_uint seq; /* Can only increase until overflow */ \
|
||||
type x; \
|
||||
char align[M_ALIGN_FOR_CACHELINE_EXCLUSION > sizeof(atomic_uint)+sizeof(type) ? M_ALIGN_FOR_CACHELINE_EXCLUSION - sizeof(atomic_uint)-sizeof(type) : 1]; \
|
||||
M_CACHELINE_ALIGN(align, atomic_uint, type); \
|
||||
} M_C(name, _el_t); \
|
||||
\
|
||||
/* If there is only one producer and one consummer, then they won't \
|
||||
typically use the same cache line, increasing performance. */ \
|
||||
typedef struct M_C(name, _s) { \
|
||||
atomic_uint ProdIdx; /* Can only increase until overflow */ \
|
||||
char align1[M_ALIGN_FOR_CACHELINE_EXCLUSION]; \
|
||||
M_CACHELINE_ALIGN(align1, atomic_uint); \
|
||||
atomic_uint ConsoIdx; /* Can only increase until overflow */ \
|
||||
char align2[M_ALIGN_FOR_CACHELINE_EXCLUSION]; \
|
||||
M_CACHELINE_ALIGN(align2, atomic_uint); \
|
||||
M_C(name, _el_t) *Tab; \
|
||||
unsigned int size; \
|
||||
} buffer_t[1]; \
|
||||
\
|
||||
static inline bool \
|
||||
M_C(name, _push)(buffer_t table, type x) \
|
||||
M_C(name, _push)(buffer_t table, type const x) \
|
||||
{ \
|
||||
QUEUEI_MPMC_CONTRACT(table); \
|
||||
unsigned int idx = atomic_load_explicit(&table->ProdIdx, \
|
||||
@@ -818,12 +820,12 @@ M_C(name, _init)(buffer_t v, size_t size) \
|
||||
atomic_uint consoIdx; /* Can only increase until overflow */ \
|
||||
unsigned int size; \
|
||||
M_C(name, _el_t) *Tab; \
|
||||
char align[M_ALIGN_FOR_CACHELINE_EXCLUSION > sizeof(atomic_uint) +sizeof(size_t) + sizeof(void*) ? M_ALIGN_FOR_CACHELINE_EXCLUSION - sizeof(atomic_uint) -sizeof(size_t)-sizeof(void*): 1]; \
|
||||
M_CACHELINE_ALIGN(align, atomic_uint, size_t, M_C(name, _el_t) *); \
|
||||
atomic_uint prodIdx; /* Can only increase until overflow */ \
|
||||
} buffer_t[1]; \
|
||||
\
|
||||
static inline bool \
|
||||
M_C(name, _push)(buffer_t table, type x) \
|
||||
M_C(name, _push)(buffer_t table, type const x) \
|
||||
{ \
|
||||
QUEUEI_SPSC_CONTRACT(table); \
|
||||
unsigned int r = atomic_load_explicit(&table->consoIdx, \
|
||||
@@ -865,6 +867,81 @@ M_C(name, _init)(buffer_t v, size_t size) \
|
||||
return true; \
|
||||
} \
|
||||
\
|
||||
static inline unsigned \
|
||||
M_C(name, _push_bulk)(buffer_t table, unsigned n, type const x[]) \
|
||||
{ \
|
||||
QUEUEI_SPSC_CONTRACT(table); \
|
||||
assert (x != NULL); \
|
||||
assert (n <= table->size); \
|
||||
unsigned int r = atomic_load_explicit(&table->consoIdx, \
|
||||
memory_order_relaxed); \
|
||||
unsigned int w = atomic_load_explicit(&table->prodIdx, \
|
||||
memory_order_acquire); \
|
||||
unsigned int max = M_MIN(n, table->size - (w-r) ); \
|
||||
if (max == 0) \
|
||||
return 0; \
|
||||
for(unsigned int k = 0; k < max; k++) { \
|
||||
unsigned int i = (w+k) & (table->size -1); \
|
||||
if (!BUFFERI_POLICY_P((policy), BUFFER_PUSH_INIT_POP_MOVE)) { \
|
||||
M_CALL_SET(oplist, table->Tab[i].x, x[k]); \
|
||||
} else { \
|
||||
M_CALL_INIT_SET(oplist, table->Tab[i].x, x[k]); \
|
||||
} \
|
||||
} \
|
||||
atomic_store_explicit(&table->prodIdx, w+max, memory_order_release); \
|
||||
QUEUEI_SPSC_CONTRACT(table); \
|
||||
return max; \
|
||||
} \
|
||||
\
|
||||
static inline unsigned \
|
||||
M_C(name, _pop_bulk)(unsigned int n, type ptr[], buffer_t table) \
|
||||
{ \
|
||||
QUEUEI_SPSC_CONTRACT(table); \
|
||||
assert (ptr != NULL); \
|
||||
assert (n <= table->size); \
|
||||
unsigned int w = atomic_load_explicit(&table->prodIdx, \
|
||||
memory_order_relaxed); \
|
||||
unsigned int r = atomic_load_explicit(&table->consoIdx, \
|
||||
memory_order_acquire); \
|
||||
if (w-r == 0) \
|
||||
return 0; \
|
||||
unsigned int max = M_MIN(w-r, n); \
|
||||
for(unsigned int k = 0; k < max; k++) { \
|
||||
unsigned int i = (r+k) & (table->size -1); \
|
||||
if (!BUFFERI_POLICY_P((policy), BUFFER_PUSH_INIT_POP_MOVE)) { \
|
||||
M_CALL_SET(oplist, ptr[k], table->Tab[i].x); \
|
||||
} else { \
|
||||
M_DO_INIT_MOVE (oplist, ptr[k], table->Tab[i].x); \
|
||||
} \
|
||||
} \
|
||||
atomic_store_explicit(&table->consoIdx, r+max, memory_order_release); \
|
||||
QUEUEI_SPSC_CONTRACT(table); \
|
||||
return max; \
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _push_force)(buffer_t table, type const x) \
|
||||
{ \
|
||||
QUEUEI_SPSC_CONTRACT(table); \
|
||||
unsigned int r = atomic_load_explicit(&table->consoIdx, \
|
||||
memory_order_relaxed); \
|
||||
unsigned int w = atomic_load_explicit(&table->prodIdx, \
|
||||
memory_order_acquire); \
|
||||
/* If no place in queue, try to skip the last one */ \
|
||||
while (w-r >= table->size) { \
|
||||
bool b = atomic_compare_exchange_strong(&table->consoIdx, &r, r+1); \
|
||||
r += b; \
|
||||
} \
|
||||
unsigned int i = w & (table->size -1); \
|
||||
if (!BUFFERI_POLICY_P((policy), BUFFER_PUSH_INIT_POP_MOVE)) { \
|
||||
M_CALL_SET(oplist, table->Tab[i].x, x); \
|
||||
} else { \
|
||||
M_CALL_INIT_SET(oplist, table->Tab[i].x, x); \
|
||||
} \
|
||||
atomic_store_explicit(&table->prodIdx, w+1, memory_order_release); \
|
||||
QUEUEI_SPSC_CONTRACT(table); \
|
||||
} \
|
||||
\
|
||||
static inline size_t \
|
||||
M_C(name, _size)(buffer_t table) \
|
||||
{ \
|
||||
@@ -952,8 +1029,4 @@ M_C(name, _init)(buffer_t v, size_t size) \
|
||||
} \
|
||||
\
|
||||
|
||||
|
||||
// NOTE: SET & INIT_SET are deliberatly missing. TBC if it the right way.
|
||||
// NOTE: There is no oplist defined for this container.
|
||||
|
||||
#endif
|
||||
+823
@@ -0,0 +1,823 @@
|
||||
/*
|
||||
* M*LIB - Concurrent memory pool allocator
|
||||
*
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* + Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* + Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef MSTARLIB_CONCURRENT_MEMPOOL_H
|
||||
#define MSTARLIB_CONCURRENT_MEMPOOL_H
|
||||
|
||||
#include "m-core.h"
|
||||
#include "m-atomic.h"
|
||||
#include "m-genint.h"
|
||||
|
||||
/* Minimum number of nodes per group of nodes */
|
||||
#define C_MEMPOOL_MIN_NODE_PER_GROUP 16
|
||||
|
||||
#define C_MEMPOOL_DEF(name, type_t) \
|
||||
C_MEMPOOL_DEF_SINGLY_LIST(name, type_t) \
|
||||
C_MEMPOOL_DEF_LF_QUEUE(name, type_t) \
|
||||
C_MEMPOOL_DEF_LFMP_THREAD_MEMPOOL(name, type_t) \
|
||||
C_MEMPOOL_DEF_SYSTEM_ALLOC(name, type_t) \
|
||||
C_MEMPOOL_DEF_LF_MEMPOOL(name, type_t)
|
||||
|
||||
/* Classic Singly List without allocation */
|
||||
#define C_MEMPOOL_DEF_SINGLY_LIST(name, type_t) \
|
||||
\
|
||||
typedef struct M_C(name, _slist_node_s) { \
|
||||
struct M_C(name, _slist_node_s) *next; \
|
||||
type_t data; \
|
||||
} M_C(name, _slist_node_t); \
|
||||
\
|
||||
typedef struct M_C(name, _slist_node_s) *M_C(name, _slist_t)[1]; \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _slist_init)(M_C(name, _slist_t) list) \
|
||||
{ \
|
||||
*list = NULL; \
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _slist_push)(M_C(name, _slist_t) list, \
|
||||
M_C(name, _slist_node_t) *node) \
|
||||
{ \
|
||||
node->next = *list; \
|
||||
*list = node; \
|
||||
} \
|
||||
\
|
||||
static inline M_C(name, _slist_node_t) * \
|
||||
M_C(name, _slist_pop)(M_C(name, _slist_t) list) \
|
||||
{ \
|
||||
assert (*list != NULL); \
|
||||
M_C(name, _slist_node_t) *node = *list; \
|
||||
*list = node->next; \
|
||||
M_IF_DEBUG(node->next = NULL;) \
|
||||
return node; \
|
||||
} \
|
||||
\
|
||||
static inline bool \
|
||||
M_C(name, _slist_empty_p)(M_C(name, _slist_t) list) \
|
||||
{ \
|
||||
return *list == NULL; \
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _slist_move)(M_C(name, _slist_t) list, \
|
||||
M_C(name, _slist_t) src) \
|
||||
{ \
|
||||
*list = *src; \
|
||||
*src = NULL; \
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _slist_clear)(M_C(name, _slist_t) list) \
|
||||
{ \
|
||||
M_C(name, _slist_node_t) *it = *list, *next; \
|
||||
while (it) { \
|
||||
next = it->next; \
|
||||
M_MEMORY_DEL(it); \
|
||||
it = next; \
|
||||
} \
|
||||
*list = NULL; \
|
||||
} \
|
||||
|
||||
|
||||
/* Lock Free free queue list (not generic one) of lists without allocation
|
||||
Based on Michael & Scott Lock Free Queue List algorithm.
|
||||
Each list is considered empty if there is only one node within.
|
||||
This LF Queue List doesn't try to prevent the ABA problem. It is up to the
|
||||
caller to avoid recycling the nodes too fast.
|
||||
Each list has its own unique NIL ptr in order to avoid issues when
|
||||
migrating a node from a Q to another: in the following scenario,
|
||||
- Thread 1 performs a PUSH of N in Q1 with Q1 empty (only node is NA)
|
||||
NA.next is NIL.
|
||||
- Thread 1 is interrupted just before the CAS on NA.next
|
||||
- Thread 2 performs a sucessfull push of NB in Q1. NA.next is set to NB.
|
||||
- Thread 2 performs a sucessfull pop of NA in Q1
|
||||
- Thread 2 performs a sucessfull push of NA in Q2. NA.next is set to NIL.
|
||||
- Thread 1 is restored and will succeed as NA.next is once again NIL.
|
||||
In order to prevent the last CAS to succeed, each queue uses its own NIL pointer.
|
||||
It is a derived problem of the ABA problem.
|
||||
*/
|
||||
/* TODO: Optimize alignement to reduce memory consumption. NIL object can use []
|
||||
to reduce memory consumption too (non compatible with C++ ...) */
|
||||
#define C_MEMPOOL_DEF_LF_QUEUE(name, type_t) \
|
||||
\
|
||||
typedef struct M_C(name, _lf_node_s) { \
|
||||
M_ATTR_EXTENSION _Atomic(struct M_C(name, _lf_node_s) *) next; \
|
||||
m_gc_atomic_ticket_t cpt; \
|
||||
M_C(name, _slist_t) list; \
|
||||
} M_C(name, _lf_node_t); \
|
||||
\
|
||||
typedef struct M_C(name, _lflist_s) { \
|
||||
M_ATTR_EXTENSION _Atomic(M_C(name, _lf_node_t) *) head; \
|
||||
char align1[M_ALIGN_FOR_CACHELINE_EXCLUSION]; \
|
||||
M_ATTR_EXTENSION _Atomic(M_C(name, _lf_node_t) *) tail; \
|
||||
char align2[M_ALIGN_FOR_CACHELINE_EXCLUSION]; \
|
||||
M_C(name, _lf_node_t) nil; \
|
||||
} M_C(name, _lflist_t)[1]; \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _lflist_init)(M_C(name, _lflist_t) list, \
|
||||
M_C(name, _lf_node_t) *node) \
|
||||
{ \
|
||||
atomic_init(&list->head, node); \
|
||||
atomic_init(&list->tail, node); \
|
||||
atomic_store_explicit(&node->next, &list->nil, memory_order_relaxed); \
|
||||
} \
|
||||
\
|
||||
static inline bool \
|
||||
M_C(name, _lflist_empty_p)(M_C(name, _lflist_t) list) \
|
||||
{ \
|
||||
return atomic_load(&list->tail) == atomic_load(&list->head); \
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _lflist_push)(M_C(name, _lflist_t) list, \
|
||||
M_C(name, _lf_node_t) *node, m_backoff_t bkoff) \
|
||||
{ \
|
||||
M_C(name, _lf_node_t) *tail; \
|
||||
M_C(name, _lf_node_t) *next; \
|
||||
\
|
||||
atomic_store_explicit(&node->next, &list->nil, memory_order_relaxed); \
|
||||
m_backoff_reset(bkoff); \
|
||||
while (true) { \
|
||||
tail = atomic_load(&list->tail); \
|
||||
next = atomic_load_explicit(&tail->next, memory_order_acquire); \
|
||||
if (M_UNLIKELY(next != &list->nil)) { \
|
||||
/* Tail was not pointing to the last node \
|
||||
Try to swing Tail to the next node */ \
|
||||
atomic_compare_exchange_weak_explicit(&list->tail, \
|
||||
&tail, next, \
|
||||
memory_order_release, \
|
||||
memory_order_relaxed); \
|
||||
} else { \
|
||||
/* Try to link node at the end of the linked list */ \
|
||||
if (atomic_compare_exchange_strong_explicit(&tail->next, \
|
||||
&next, node, \
|
||||
memory_order_release, \
|
||||
memory_order_relaxed)) \
|
||||
break; \
|
||||
m_backoff_wait(bkoff); \
|
||||
} \
|
||||
} \
|
||||
/* Enqueue is done. Try to swing Tail to the inserted node \
|
||||
If it fails, someone else will do it or has already did it. */ \
|
||||
atomic_compare_exchange_strong_explicit(&list->tail, &tail, node, \
|
||||
memory_order_acq_rel, \
|
||||
memory_order_relaxed); \
|
||||
} \
|
||||
\
|
||||
static inline M_C(name, _lf_node_t) * \
|
||||
M_C(name, _lflist_pop)(M_C(name, _lflist_t) list, m_backoff_t bkoff) \
|
||||
{ \
|
||||
M_C(name, _lf_node_t) *head; \
|
||||
M_C(name, _lf_node_t) *tail; \
|
||||
M_C(name, _lf_node_t) *next; \
|
||||
\
|
||||
/* Reinitialize backoff */ \
|
||||
m_backoff_reset(bkoff); \
|
||||
while (true) { \
|
||||
head = atomic_load(&list->head); \
|
||||
tail = atomic_load(&list->tail); \
|
||||
next = atomic_load(&head->next); \
|
||||
/* Are head, tail, and next consistent?*/ \
|
||||
if (M_LIKELY(head == \
|
||||
atomic_load_explicit(&list->head, memory_order_relaxed))) \
|
||||
{ \
|
||||
/* Is queue empty or Tail falling behind? */ \
|
||||
if (head == tail) { \
|
||||
/* Is queue empty? */ \
|
||||
if (next == &list->nil) \
|
||||
return NULL; \
|
||||
/* Tail is falling behind. Try to advance it */ \
|
||||
atomic_compare_exchange_strong_explicit(&list->tail, &tail, \
|
||||
next, \
|
||||
memory_order_release, \
|
||||
memory_order_relaxed); \
|
||||
} else { \
|
||||
/* Try to swing Head to the next node */ \
|
||||
if (atomic_compare_exchange_strong_explicit(&list->head, \
|
||||
&head, next, \
|
||||
memory_order_release, \
|
||||
memory_order_relaxed)) { \
|
||||
break; \
|
||||
} \
|
||||
/* Failure: perform a random exponential backoff */ \
|
||||
m_backoff_wait(bkoff); \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
/* dequeue returns an element that becomes the new dummy element (the new head), \
|
||||
and the former dummy element (the former head) is removed: \
|
||||
Since we want a link of free list, and we don't care about the content itsef, \
|
||||
provided that the node we return is older than the one we should return, \
|
||||
Therefore, we return the previous dummy head. \
|
||||
As such, it is not the original MSqueue algorithm. */ \
|
||||
M_IF_DEBUG(atomic_store(&head->next, (M_C(name, _lf_node_t) *) 0);) \
|
||||
return head; \
|
||||
} \
|
||||
\
|
||||
/* Dequeue a node if the node is old enough */ \
|
||||
static inline M_C(name, _lf_node_t) * \
|
||||
M_C(name, _lflist_pop_if)(M_C(name, _lflist_t) list, \
|
||||
m_gc_ticket_t age, m_backoff_t bkoff) \
|
||||
{ \
|
||||
M_C(name, _lf_node_t) *head; \
|
||||
M_C(name, _lf_node_t) *tail; \
|
||||
M_C(name, _lf_node_t) *next; \
|
||||
\
|
||||
m_backoff_reset(bkoff); \
|
||||
while (true) { \
|
||||
head = atomic_load(&list->head); \
|
||||
tail = atomic_load(&list->tail); \
|
||||
next = atomic_load(&head->next); \
|
||||
if (M_LIKELY(head == atomic_load_explicit(&list->head, memory_order_relaxed))) \
|
||||
{ \
|
||||
if (head == tail) { \
|
||||
if (next == &list->nil) \
|
||||
return NULL; \
|
||||
atomic_compare_exchange_strong_explicit(&list->tail, &tail, next, \
|
||||
memory_order_release, \
|
||||
memory_order_relaxed); \
|
||||
} else { \
|
||||
/* Test if the node is old enought to be popped */ \
|
||||
if (atomic_load_explicit(&next->cpt, memory_order_relaxed) >= age) \
|
||||
return NULL; \
|
||||
/* Try to swing Head to the next node */ \
|
||||
if (atomic_compare_exchange_strong_explicit(&list->head, \
|
||||
&head, next, \
|
||||
memory_order_release, \
|
||||
memory_order_relaxed)) { \
|
||||
break; \
|
||||
} \
|
||||
m_backoff_wait(bkoff); \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
M_IF_DEBUG(atomic_store(&head->next, (M_C(name, _lf_node_t) *) 0);) \
|
||||
return head; \
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _lflist_clear)(M_C(name, _lflist_t) list) \
|
||||
{ \
|
||||
m_backoff_t bkoff; \
|
||||
m_backoff_init(bkoff); \
|
||||
while (true) { \
|
||||
M_C(name, _lf_node_t) *node = M_C(name, _lflist_pop)(list, bkoff); \
|
||||
if (node == NULL) break; \
|
||||
M_C(name, _lf_node_t) *next = atomic_load_explicit(&node->next, \
|
||||
memory_order_relaxed); \
|
||||
M_C(name, _slist_clear)(node->list); \
|
||||
M_MEMORY_DEL(node); \
|
||||
node = next; \
|
||||
} \
|
||||
/* Dummy node to free too */ \
|
||||
M_C(name, _lf_node_t) *dummy; \
|
||||
dummy = atomic_load_explicit(&list->head, memory_order_relaxed); \
|
||||
M_C(name, _slist_clear)(dummy->list); \
|
||||
M_MEMORY_DEL(dummy); \
|
||||
} \
|
||||
|
||||
/* System node allocator: request memory to the system.
|
||||
As such it is a non Lock-Free path. */
|
||||
#define C_MEMPOOL_DEF_SYSTEM_ALLOC(name, type_t) \
|
||||
\
|
||||
static inline M_C(name, _lf_node_t) * \
|
||||
M_C(name, _alloc_node)(unsigned int initial) \
|
||||
{ \
|
||||
M_C(name, _lf_node_t) * node; \
|
||||
node = M_MEMORY_ALLOC(M_C(name, _lf_node_t)); \
|
||||
if (M_UNLIKELY(node == NULL)) { \
|
||||
M_MEMORY_FULL(sizeof(M_C(name, _lf_node_t))); \
|
||||
return NULL; \
|
||||
} \
|
||||
atomic_init(&node->next, (M_C(name, _lf_node_t) *) 0); \
|
||||
atomic_init(&node->cpt, 0UL); \
|
||||
M_C(name, _slist_init)(node->list); \
|
||||
for(unsigned i = 0; i < initial; i++) { \
|
||||
M_C(name, _slist_node_t) *n; \
|
||||
n = M_MEMORY_ALLOC(M_C(name, _slist_node_t)); \
|
||||
if (M_UNLIKELY(n == NULL)) { \
|
||||
M_MEMORY_FULL(sizeof(M_C(name, _lf_node_t))); \
|
||||
return NULL; \
|
||||
} \
|
||||
M_C(name, _slist_push)(node->list, n); \
|
||||
} \
|
||||
return node; \
|
||||
} \
|
||||
|
||||
/* Concurrent Memory pool
|
||||
The data structure is the following.
|
||||
Each thread has its own pool of nodes (local) that only it can
|
||||
access (it is a singly list). If there is no longer any node in this
|
||||
pool, it requests a new pool to the lock free queue of pool (group of
|
||||
nodes). If it fails, it requests a new pool to the system allocator
|
||||
(and from there it is no longer lock free).
|
||||
This memory pool can only be lock free if the initial state is
|
||||
sufficiently dimensionned to avoid calling the system allocator during
|
||||
the normal processing.
|
||||
Then each thread pushs its deleted node into another pool of nodes,
|
||||
where the node is logically deleted (no contain of the node is destroyed
|
||||
at this point and the node can be freely accessed by other threads).
|
||||
Once the thread mempool is put to sleep, the age of the pool of logical
|
||||
deleted nodes is computed and this pool is move to the Lock Free Queue
|
||||
List of pools to be reclaimed. Then A Garbage Collector is performed
|
||||
on this Lock Free Queue list to reclaim all pools thare are sufficiently
|
||||
aged (taking into account the grace period of the pool) to be moved back
|
||||
to the Lock Free Queue of the free pools.
|
||||
|
||||
Each pool of nodes can be in the following state:
|
||||
* FREE state if it is present in the Lock Free Queue of free pools.
|
||||
* EMPTY state if it is present in the Lock Free Queue of empty pools
|
||||
which means that the nodes present in it has been USED directly by a thread,
|
||||
* TO_BE_RECLAIMED state if it is present in the Lock Free Queue of TBR pools
|
||||
|
||||
A pool of nodes will go to the following state:
|
||||
FREE --> EMPTY --> TO_BE_RECLAIMED
|
||||
^ |
|
||||
+----------------------+
|
||||
|
||||
The ABA problem is taken into account as a node cannot be reused in the
|
||||
same queue without performing a full cycle of its state. Moreover
|
||||
it can only move from TO_BE_RECLAIMED to FREE if and only if a grace
|
||||
period is finished (and then we are sure that no thread references any
|
||||
older node).
|
||||
|
||||
Each thread has its own backoff structure (with local pseudo-random
|
||||
generator).
|
||||
|
||||
The grace period is detected through a global age counter (ticket)
|
||||
that is incremented each time a thread is awaken / sleep.
|
||||
Each thread has its own age that is set to the global ticket on sleep/awaken.
|
||||
The age of the pool to be reclaimed is also set to this global age counter.
|
||||
|
||||
To ensure that the grace period is finished, it tests if all threads
|
||||
are younger than the age of the pool to be reclaimed.
|
||||
|
||||
From a performance point of view, this puts a bottleneck on the global
|
||||
age counter that is shared and incremented by all threads. However,
|
||||
the sleep/awaken operations are much less frequent than other operations.
|
||||
Thus, it shall not have a huge impact on the performance if the user
|
||||
code is intelligent with the sleep/awaken operations.
|
||||
|
||||
As such it won't support more than ULONG_MAX sleep for all threads.
|
||||
*/
|
||||
#define C_MEMPOOL_DEF_LFMP_THREAD_MEMPOOL(name, type_t) \
|
||||
\
|
||||
typedef struct M_C(name, _lfmp_thread_s) { \
|
||||
M_C(name, _slist_t) free; \
|
||||
M_C(name, _slist_t) to_be_reclaimed; \
|
||||
M_CACHELINE_ALIGN(align1, M_C(name, _slist_t), M_C(name, _slist_t)); \
|
||||
} M_C(name, _lfmp_thread_t); \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _lfmp_thread_init)(M_C(name, _lfmp_thread_t) *t) \
|
||||
{ \
|
||||
M_C(name, _slist_init)(t->free); \
|
||||
M_C(name, _slist_init)(t->to_be_reclaimed); \
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _lfmp_thread_clear)(M_C(name, _lfmp_thread_t) *t) \
|
||||
{ \
|
||||
assert(M_C(name, _slist_empty_p)(t->to_be_reclaimed)); \
|
||||
M_C(name, _slist_clear)(t->free); \
|
||||
M_C(name, _slist_clear)(t->to_be_reclaimed); \
|
||||
} \
|
||||
|
||||
/* NOTE: once a node is deleted, its data are kept readable until the future GC */
|
||||
#define C_MEMPOOL_DEF_LF_MEMPOOL(name, type_t) \
|
||||
\
|
||||
typedef struct M_C(name, _s) { \
|
||||
unsigned initial; \
|
||||
M_C(name, _lfmp_thread_t) *thread_data; \
|
||||
M_C(name, _lflist_t) free; \
|
||||
M_C(name, _lflist_t) to_be_reclaimed; \
|
||||
M_C(name, _lflist_t) empty; \
|
||||
m_gc_mempool_list_t mempool_node; \
|
||||
struct m_gc_s *gc_mem; \
|
||||
} M_C(name, _t)[1]; \
|
||||
\
|
||||
/* Garbage collect of the nodes of the mempool on sleep */ \
|
||||
static inline void \
|
||||
M_C(name, _int_gc_on_sleep)(m_gc_t gc_mem, m_gc_mempool_list_t *data, \
|
||||
m_gc_tid_t id, m_gc_ticket_t ticket, m_gc_ticket_t min_ticket) \
|
||||
{ \
|
||||
/* Get back the mempool from the node */ \
|
||||
struct M_C(name, _s) *mempool = \
|
||||
M_TYPE_FROM_FIELD(struct M_C(name, _s), data, m_gc_mempool_list_t, mempool_node); \
|
||||
\
|
||||
/* Move the local nodes of the mempool to be reclaimed to the thread into the global pool */ \
|
||||
if (!M_C(name, _slist_empty_p)(mempool->thread_data[id].to_be_reclaimed)) { \
|
||||
M_C(name, _lf_node_t) *node; \
|
||||
/* Get a new empty group of nodes */ \
|
||||
node = M_C(name, _lflist_pop)(mempool->empty, gc_mem->thread_data[id].bkoff); \
|
||||
if (M_UNLIKELY (node == NULL)) { \
|
||||
/* Fail to get an empty group of node. \
|
||||
Alloc a new one from the system */ \
|
||||
node = M_C(name, _alloc_node)(0); \
|
||||
} \
|
||||
assert(M_C(name, _slist_empty_p)(node->list)); \
|
||||
M_C(name, _slist_move)(node->list, mempool->thread_data[id].to_be_reclaimed); \
|
||||
atomic_store_explicit(&node->cpt, ticket, memory_order_relaxed); \
|
||||
M_C(name, _lflist_push)(mempool->to_be_reclaimed, node, gc_mem->thread_data[id].bkoff); \
|
||||
} \
|
||||
\
|
||||
/* Perform a GC of the freelist of nodes */ \
|
||||
while (true) { \
|
||||
M_C(name, _lf_node_t) *node; \
|
||||
node = M_C(name, _lflist_pop_if)(mempool->to_be_reclaimed, \
|
||||
min_ticket, gc_mem->thread_data[id].bkoff); \
|
||||
if (node == NULL) break; \
|
||||
M_C(name, _lflist_push)(mempool->free, node, gc_mem->thread_data[id].bkoff); \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _init)(M_C(name, _t) mem, m_gc_t gc_mem, \
|
||||
unsigned init_node_count, unsigned init_group_count) \
|
||||
{ \
|
||||
const unsigned long max_thread = gc_mem->max_thread; \
|
||||
/* Initialize the thread data of the mempool */ \
|
||||
mem->thread_data = M_MEMORY_REALLOC(M_C(name, _lfmp_thread_t), NULL, max_thread); \
|
||||
if (mem->thread_data == NULL) { \
|
||||
M_MEMORY_FULL(max_thread * sizeof(M_C(name, _lfmp_thread_t))); \
|
||||
return; \
|
||||
} \
|
||||
for(unsigned i = 0; i < max_thread;i++) { \
|
||||
M_C(name, _lfmp_thread_init)(&mem->thread_data[i]); \
|
||||
} \
|
||||
/* Preallocate some group of nodes for the mempool */ \
|
||||
mem->initial = M_MAX(C_MEMPOOL_MIN_NODE_PER_GROUP, init_node_count); \
|
||||
M_C(name, _lflist_init)(mem->free, M_C(name, _alloc_node)(init_node_count)); \
|
||||
M_C(name, _lflist_init)(mem->to_be_reclaimed, M_C(name, _alloc_node)(init_node_count)); \
|
||||
M_C(name, _lflist_init)(mem->empty, M_C(name, _alloc_node)(0)); \
|
||||
for(unsigned i = 1; i < init_group_count; i++) { \
|
||||
M_C(name, _lflist_push)(mem->free, M_C(name, _alloc_node)(init_node_count), \
|
||||
gc_mem->thread_data[0].bkoff); \
|
||||
M_C(name, _lflist_push)(mem->empty, M_C(name, _alloc_node)(0), \
|
||||
gc_mem->thread_data[0].bkoff); \
|
||||
} \
|
||||
/* Register the mempool in the GC */ \
|
||||
mem->mempool_node.gc_on_sleep = M_C(name, _int_gc_on_sleep); \
|
||||
mem->mempool_node.next = gc_mem->mempool_list; \
|
||||
gc_mem->mempool_list = &mem->mempool_node; \
|
||||
mem->gc_mem = gc_mem; \
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _clear)(M_C(name, _t) mem) \
|
||||
{ \
|
||||
const unsigned max_thread = mem->gc_mem->max_thread; \
|
||||
for(unsigned i = 0; i < max_thread;i++) { \
|
||||
M_C(name, _lfmp_thread_clear)(&mem->thread_data[i]); \
|
||||
} \
|
||||
M_MEMORY_FREE(mem->thread_data); \
|
||||
mem->thread_data = NULL; \
|
||||
M_C(name, _lflist_clear)(mem->empty); \
|
||||
M_C(name, _lflist_clear)(mem->free); \
|
||||
assert(M_C(name, _lflist_empty_p)(mem->to_be_reclaimed)); \
|
||||
M_C(name, _lflist_clear)(mem->to_be_reclaimed); \
|
||||
/* TODO: Unregister from the GC? */ \
|
||||
} \
|
||||
\
|
||||
static inline type_t * \
|
||||
M_C(name, _new)(M_C(name, _t) mem, m_gc_tid_t id) \
|
||||
{ \
|
||||
M_C(name, _slist_node_t) *snode; \
|
||||
M_C(name, _lf_node_t) *node; \
|
||||
while (true) { \
|
||||
/* Fast & likely path where we access the thread pool of nodes */ \
|
||||
if (M_LIKELY(!M_C(name, _slist_empty_p)(mem->thread_data[id].free))) { \
|
||||
snode = M_C(name, _slist_pop)(mem->thread_data[id].free); \
|
||||
return &snode->data; \
|
||||
} \
|
||||
/* Request a group node to the freelist of groups */ \
|
||||
node = M_C(name, _lflist_pop)(mem->free, mem->gc_mem->thread_data[id].bkoff); \
|
||||
if (M_UNLIKELY (node == NULL)) { \
|
||||
/* Request a new group to the system. Non Lock Free path */ \
|
||||
assert(mem->initial > 0); \
|
||||
node = M_C(name, _alloc_node)(mem->initial); \
|
||||
assert(!M_C(name, _slist_empty_p)(node->list)); \
|
||||
} \
|
||||
M_C(name, _slist_move)(mem->thread_data[id].free, node->list); \
|
||||
/* Push back the empty group */ \
|
||||
assert (M_C(name, _slist_empty_p)(node->list)); \
|
||||
M_C(name, _lflist_push)(mem->empty, node, mem->gc_mem->thread_data[id].bkoff); \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _del)(M_C(name, _t) mem, type_t *d, m_gc_tid_t id) \
|
||||
{ \
|
||||
M_C(name, _slist_node_t) *snode; \
|
||||
assert( d != NULL); \
|
||||
snode = M_TYPE_FROM_FIELD(M_C(name, _slist_node_t), d, type_t, data); \
|
||||
M_C(name, _slist_push)(mem->thread_data[id].to_be_reclaimed, snode); \
|
||||
} \
|
||||
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
/* Define the ID of a thread */
|
||||
typedef int m_gc_tid_t;
|
||||
|
||||
/* Define the age of a node */
|
||||
/* TODO: Compute if sufficient (worst cast ULONG_MAX is 32 bits) */
|
||||
typedef unsigned long m_gc_ticket_t;
|
||||
typedef atomic_ulong m_gc_atomic_ticket_t;
|
||||
|
||||
/* Define the Linked List of mempools that are registered in the GC */
|
||||
struct m_gc_s;
|
||||
typedef struct m_gc_mempool_list_s {
|
||||
struct m_gc_mempool_list_s *next;
|
||||
void (*gc_on_sleep)(struct m_gc_s *gc_mem,
|
||||
struct m_gc_mempool_list_s *data, m_gc_tid_t id,
|
||||
m_gc_ticket_t ticket, m_gc_ticket_t min_ticket);
|
||||
void *data;
|
||||
} m_gc_mempool_list_t;
|
||||
|
||||
/* Define the Garbage collector thread data */
|
||||
typedef struct m_gc_lfmp_thread_s {
|
||||
m_gc_atomic_ticket_t ticket;
|
||||
m_backoff_t bkoff;
|
||||
M_CACHELINE_ALIGN(align1, atomic_ulong, m_backoff_t);
|
||||
} m_gc_lfmp_thread_t;
|
||||
|
||||
/* Define the Garbage collector coordinator */
|
||||
typedef struct m_gc_s {
|
||||
m_gc_atomic_ticket_t ticket;
|
||||
m_gc_tid_t max_thread;
|
||||
genint_t thread_alloc;
|
||||
m_gc_lfmp_thread_t *thread_data;
|
||||
m_gc_mempool_list_t *mempool_list;
|
||||
} m_gc_t[1];
|
||||
|
||||
static inline void
|
||||
m_gc_init(m_gc_t gc_mem, unsigned long max_thread)
|
||||
{
|
||||
assert(gc_mem != NULL);
|
||||
assert(max_thread > 0 && max_thread < INT_MAX);
|
||||
|
||||
atomic_init(&gc_mem->ticket, 0UL);
|
||||
genint_init(gc_mem->thread_alloc, max_thread);
|
||||
gc_mem->thread_data = M_MEMORY_REALLOC(m_gc_lfmp_thread_t, NULL, max_thread);
|
||||
if (gc_mem->thread_data == NULL) {
|
||||
M_MEMORY_FULL(max_thread * sizeof(m_gc_lfmp_thread_t));
|
||||
return;
|
||||
}
|
||||
for(unsigned i = 0; i < max_thread;i++) {
|
||||
atomic_init(&gc_mem->thread_data[i].ticket, ULONG_MAX);
|
||||
m_backoff_init(gc_mem->thread_data[i].bkoff);
|
||||
}
|
||||
gc_mem->max_thread = max_thread;
|
||||
gc_mem->mempool_list = NULL;
|
||||
}
|
||||
|
||||
static inline void
|
||||
m_gc_clear(m_gc_t gc_mem)
|
||||
{
|
||||
assert(gc_mem != NULL && gc_mem->max_thread > 0);
|
||||
|
||||
for(m_gc_tid_t i = 0; i < gc_mem->max_thread;i++) {
|
||||
m_backoff_clear(gc_mem->thread_data[i].bkoff);
|
||||
}
|
||||
M_MEMORY_FREE(gc_mem->thread_data);
|
||||
gc_mem->thread_data = NULL;
|
||||
genint_clear(gc_mem->thread_alloc);
|
||||
}
|
||||
|
||||
static inline m_gc_tid_t
|
||||
m_gc_attach_thread(m_gc_t gc_mem)
|
||||
{
|
||||
assert(gc_mem != NULL && gc_mem->max_thread > 0);
|
||||
|
||||
unsigned id = genint_pop(gc_mem->thread_alloc);
|
||||
return M_ASSIGN_CAST(m_gc_tid_t, id);
|
||||
}
|
||||
|
||||
static inline void
|
||||
m_gc_detach_thread(m_gc_t gc_mem, m_gc_tid_t id)
|
||||
{
|
||||
assert(gc_mem != NULL && gc_mem->max_thread > 0);
|
||||
assert(id >= 0 && id < gc_mem->max_thread);
|
||||
assert(atomic_load(&gc_mem->thread_data[id].ticket) == ULONG_MAX);
|
||||
|
||||
genint_push(gc_mem->thread_alloc, id);
|
||||
}
|
||||
|
||||
static inline void
|
||||
m_gc_awake(m_gc_t gc_mem, m_gc_tid_t id)
|
||||
{
|
||||
assert(gc_mem != NULL && gc_mem->max_thread > 0);
|
||||
assert(id >= 0 && id < gc_mem->max_thread);
|
||||
assert(atomic_load(&gc_mem->thread_data[id].ticket) == ULONG_MAX);
|
||||
|
||||
m_gc_ticket_t t = atomic_fetch_add(&gc_mem->ticket, 1UL) + 1;
|
||||
atomic_store(&gc_mem->thread_data[id].ticket, t);
|
||||
}
|
||||
|
||||
static inline m_gc_ticket_t
|
||||
m_gc_int_min_ticket(m_gc_t gc_mem)
|
||||
{
|
||||
m_gc_ticket_t min = atomic_load(&gc_mem->thread_data[0].ticket);
|
||||
for(m_gc_tid_t i = 1; i < gc_mem->max_thread; i++) {
|
||||
m_gc_ticket_t t = atomic_load(&gc_mem->thread_data[i].ticket);
|
||||
min = M_MIN(t, min);
|
||||
}
|
||||
return min;
|
||||
}
|
||||
|
||||
static inline void
|
||||
m_gc_sleep(m_gc_t gc_mem, m_gc_tid_t id)
|
||||
{
|
||||
/* Increase life time of the thread */
|
||||
m_gc_ticket_t t = atomic_fetch_add(&gc_mem->ticket, 1UL);
|
||||
atomic_store(&gc_mem->thread_data[id].ticket, t+1);
|
||||
const m_gc_ticket_t min_ticket = m_gc_int_min_ticket(gc_mem);
|
||||
/* Iterate over all registered mempools */
|
||||
m_gc_mempool_list_t *it = gc_mem->mempool_list;
|
||||
|
||||
while (it) {
|
||||
/* Perform a garbage collect of the mempool */
|
||||
it->gc_on_sleep(gc_mem, it, id, t, min_ticket);
|
||||
/* Next mempool to scan for GC */
|
||||
it = it->next;
|
||||
}
|
||||
/* Sleep the thread */
|
||||
atomic_store(&gc_mem->thread_data[id].ticket, ULONG_MAX);
|
||||
}
|
||||
|
||||
|
||||
/***********************************************************************/
|
||||
/* */
|
||||
/* VLA MEMPOOL */
|
||||
/* */
|
||||
/***********************************************************************/
|
||||
|
||||
C_MEMPOOL_DEF_SINGLY_LIST(m_vlapool, char)
|
||||
C_MEMPOOL_DEF_LF_QUEUE(m_vlapool, char)
|
||||
C_MEMPOOL_DEF_SYSTEM_ALLOC(m_vlapool, char)
|
||||
|
||||
typedef struct m_vlapool_lfmp_thread_s {
|
||||
m_vlapool_slist_t to_be_reclaimed;
|
||||
M_CACHELINE_ALIGN(align1, m_vlapool_slist_t);
|
||||
} m_vlapool_lfmp_thread_t;
|
||||
|
||||
static inline void
|
||||
m_vlapool_lfmp_thread_init(m_vlapool_lfmp_thread_t *t)
|
||||
{
|
||||
m_vlapool_slist_init(t->to_be_reclaimed);
|
||||
}
|
||||
|
||||
static inline void
|
||||
m_vlapool_lfmp_thread_clear(m_vlapool_lfmp_thread_t *t)
|
||||
{
|
||||
assert(m_vlapool_slist_empty_p(t->to_be_reclaimed));
|
||||
m_vlapool_slist_clear(t->to_be_reclaimed);
|
||||
}
|
||||
|
||||
typedef struct m_vlapool_s {
|
||||
m_vlapool_lflist_t to_be_reclaimed;
|
||||
m_vlapool_lflist_t empty;
|
||||
m_vlapool_lfmp_thread_t *thread_data;
|
||||
m_gc_mempool_list_t mvla_node;
|
||||
struct m_gc_s *gc_mem;
|
||||
} m_vlapool_t[1];
|
||||
|
||||
/* Garbage collect of the nodes of the vla mempool on sleep */
|
||||
static inline void
|
||||
m_vlapool_int_gc_on_sleep(m_gc_t gc_mem, m_gc_mempool_list_t *data,
|
||||
m_gc_tid_t id, m_gc_ticket_t ticket, m_gc_ticket_t min_ticket)
|
||||
{
|
||||
/* Get back the mempool from the node */
|
||||
struct m_vlapool_s *vlapool =
|
||||
M_TYPE_FROM_FIELD(struct m_vlapool_s, data, m_gc_mempool_list_t, mvla_node);
|
||||
|
||||
/* Move the local nodes of the vlapool to be reclaimed to the thread into the global pool */
|
||||
if (!m_vlapool_slist_empty_p(vlapool->thread_data[id].to_be_reclaimed)) {
|
||||
m_vlapool_lf_node_t *node;
|
||||
/* Get a new empty group of nodes */
|
||||
node = m_vlapool_lflist_pop(vlapool->empty, gc_mem->thread_data[id].bkoff);
|
||||
if (M_UNLIKELY (node == NULL)) {
|
||||
/* Fail to get an empty group of node.
|
||||
Alloc a new one from the system */
|
||||
node = m_vlapool_alloc_node(0);
|
||||
}
|
||||
assert(m_vlapool_slist_empty_p(node->list));
|
||||
m_vlapool_slist_move(node->list, vlapool->thread_data[id].to_be_reclaimed);
|
||||
atomic_store_explicit(&node->cpt, ticket, memory_order_relaxed);
|
||||
m_vlapool_lflist_push(vlapool->to_be_reclaimed, node, gc_mem->thread_data[id].bkoff);
|
||||
}
|
||||
|
||||
/* Perform a GC of the freelist of nodes */
|
||||
while (true) {
|
||||
m_vlapool_lf_node_t *node;
|
||||
node = m_vlapool_lflist_pop_if(vlapool->to_be_reclaimed,
|
||||
min_ticket, gc_mem->thread_data[id].bkoff);
|
||||
if (node == NULL) break;
|
||||
// No reuse of VLA nodes. Free physically the node back to the system
|
||||
m_vlapool_slist_clear(node->list);
|
||||
// Add back the empty group of nodes
|
||||
m_vlapool_slist_init(node->list);
|
||||
m_vlapool_lflist_push(vlapool->empty, node, gc_mem->thread_data[id].bkoff);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
m_vlapool_init(m_vlapool_t mem, m_gc_t gc_mem)
|
||||
{
|
||||
const unsigned long max_thread = gc_mem->max_thread;
|
||||
|
||||
/* Initialize the thread data of the vlapool */
|
||||
mem->thread_data = M_MEMORY_REALLOC(m_vlapool_lfmp_thread_t, NULL, max_thread);
|
||||
if (mem->thread_data == NULL) {
|
||||
M_MEMORY_FULL(max_thread * sizeof(m_vlapool_lfmp_thread_t));
|
||||
return;
|
||||
}
|
||||
for(unsigned i = 0; i < max_thread;i++) {
|
||||
m_vlapool_lfmp_thread_init(&mem->thread_data[i]);
|
||||
}
|
||||
|
||||
/* Initialize the lists */
|
||||
m_vlapool_lflist_init(mem->to_be_reclaimed, m_vlapool_alloc_node(0));
|
||||
m_vlapool_lflist_init(mem->empty, m_vlapool_alloc_node(0));
|
||||
|
||||
/* Register the mempool in the GC */
|
||||
mem->mvla_node.gc_on_sleep = m_vlapool_int_gc_on_sleep;
|
||||
mem->mvla_node.next = gc_mem->mempool_list;
|
||||
gc_mem->mempool_list = &mem->mvla_node;
|
||||
mem->gc_mem = gc_mem;
|
||||
}
|
||||
|
||||
static inline void
|
||||
m_vlapool_clear(m_vlapool_t mem)
|
||||
{
|
||||
const unsigned max_thread = mem->gc_mem->max_thread;
|
||||
for(unsigned i = 0; i < max_thread;i++) {
|
||||
m_vlapool_lfmp_thread_clear(&mem->thread_data[i]);
|
||||
}
|
||||
M_MEMORY_FREE(mem->thread_data);
|
||||
mem->thread_data = NULL;
|
||||
m_vlapool_lflist_clear(mem->empty);
|
||||
assert(m_vlapool_lflist_empty_p(mem->to_be_reclaimed));
|
||||
m_vlapool_lflist_clear(mem->to_be_reclaimed);
|
||||
/* TODO: Unregister from the GC? */
|
||||
}
|
||||
|
||||
static inline void *
|
||||
m_vlapool_new(m_vlapool_t mem, m_gc_tid_t id, size_t size)
|
||||
{
|
||||
assert(mem != NULL && mem->gc_mem != NULL);
|
||||
assert(id >= 0 && id < mem->gc_mem->max_thread);
|
||||
assert( atomic_load(&mem->gc_mem->thread_data[id].ticket) != ULONG_MAX);
|
||||
|
||||
// Nothing to do with theses parameters yet
|
||||
(void) mem;
|
||||
(void) id;
|
||||
|
||||
// Ensure the size is big enough to also represent a node
|
||||
size += offsetof(struct m_vlapool_slist_node_s, data);
|
||||
|
||||
// Simply wrap around a system call to get the memory
|
||||
char *ptr = M_MEMORY_REALLOC(char, NULL, size);
|
||||
return (ptr == NULL) ? NULL : M_ASSIGN_CAST(void *, ptr + offsetof(struct m_vlapool_slist_node_s, data));
|
||||
}
|
||||
|
||||
static inline void
|
||||
m_vlapool_del(m_vlapool_t mem, void *d, m_gc_tid_t id)
|
||||
{
|
||||
assert(mem != NULL && mem->gc_mem != NULL);
|
||||
assert(id >= 0 && id < mem->gc_mem->max_thread);
|
||||
assert(atomic_load(&mem->gc_mem->thread_data[id].ticket) != ULONG_MAX);
|
||||
assert(d != NULL);
|
||||
|
||||
// Get back the pointer to a struct m_vlapool_slist_node_s.
|
||||
d = M_ASSIGN_CAST(void *, M_ASSIGN_CAST(char *, d) - offsetof(struct m_vlapool_slist_node_s, data));
|
||||
m_vlapool_slist_node_t *snode = M_ASSIGN_CAST(m_vlapool_slist_node_t *, d);
|
||||
// Push the logicaly free memory into the list of the nodes to be reclaimed.
|
||||
m_vlapool_slist_push(mem->thread_data[id].to_be_reclaimed, snode);
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
+314
-90
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* M*LIB - Basic Protected Concurrent module over container.
|
||||
*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -27,8 +27,10 @@
|
||||
|
||||
#include "m-core.h"
|
||||
#include "m-mutex.h"
|
||||
#include "m-atomic.h"
|
||||
|
||||
/* Define a protected concurrent container and its associated functions.
|
||||
/* Define a protected concurrent container and its associated functions
|
||||
based on the given container.
|
||||
USAGE: CONCURRENT_DEF(name, type [, oplist_of_the_type]) */
|
||||
#define CONCURRENT_DEF(name, ...) \
|
||||
CONCURRENTI_DEF(M_IF_NARGS_EQ1(__VA_ARGS__) \
|
||||
@@ -44,6 +46,15 @@
|
||||
(__VA_ARGS__ )))
|
||||
|
||||
|
||||
/* Define a protected concurrent container and its associated functions
|
||||
based on its given container. Operations that perform only read of the container
|
||||
can be done in parallel.
|
||||
USAGE: CONCURRENT_RP_DEF(name, type [, oplist_of_the_type]) */
|
||||
#define CONCURRENT_RP_DEF(name, ...) \
|
||||
CONCURRENTI_RP_DEF(M_IF_NARGS_EQ1(__VA_ARGS__) \
|
||||
((name, __VA_ARGS__, M_GLOBAL_OPLIST_OR_DEF(__VA_ARGS__)(), M_C(name,_t), M_C(name,_it_t) ), \
|
||||
(name, __VA_ARGS__, M_C(name,_t), M_C(name,_it_t))))
|
||||
|
||||
|
||||
/********************************** INTERNAL ************************************/
|
||||
|
||||
@@ -67,6 +78,7 @@
|
||||
,SUBTYPE(M_C(name, _type_t)) \
|
||||
,OPLIST(oplist) \
|
||||
,M_IF_METHOD(TEST_EMPTY, oplist)(TEST_EMPTY(M_C(name,_empty_p)),) \
|
||||
,M_IF_METHOD(GET_SIZE, oplist)(GET_SIZE(M_C(name,_size)),) \
|
||||
,M_IF_METHOD(CLEAN, oplist)(CLEAN(M_C(name,_clean)),) \
|
||||
,M_IF_METHOD(KEY_TYPE, oplist)(KEY_TYPE(M_GET_KEY_TYPE oplist),) \
|
||||
,M_IF_METHOD(VALUE_TYPE, oplist)(VALUE_TYPE(M_GET_VALUE_TYPE oplist),) \
|
||||
@@ -90,7 +102,7 @@
|
||||
)
|
||||
|
||||
#define CONCURRENTI_CONTRACT(c) do { \
|
||||
assert ((c)->self == c); \
|
||||
assert ((c) != NULL); \
|
||||
} while (0)
|
||||
|
||||
// Deferred evaluation for the concurrent definition.
|
||||
@@ -111,14 +123,79 @@
|
||||
\
|
||||
typedef type M_C(name, _type_t); \
|
||||
\
|
||||
\
|
||||
M_IF_METHOD(INIT, oplist)( \
|
||||
/* Define the lock strategy (global & shared lock) */ \
|
||||
static inline void \
|
||||
M_C(name, _init)(concurrent_t out) \
|
||||
M_C(name, _internal_init)(concurrent_t out) \
|
||||
{ \
|
||||
m_mutex_init(out->lock); \
|
||||
m_cond_init(out->there_is_data); \
|
||||
out->self = out; \
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _internal_clear)(concurrent_t out) \
|
||||
{ \
|
||||
assert (out->self == out); \
|
||||
m_mutex_clear(out->lock); \
|
||||
m_cond_clear(out->there_is_data); \
|
||||
out->self = NULL; \
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _read_lock)(const concurrent_t out) \
|
||||
{ \
|
||||
assert (out->self == out); \
|
||||
m_mutex_lock (out->self->lock); \
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _read_unlock)(const concurrent_t out) \
|
||||
{ \
|
||||
assert (out->self == out); \
|
||||
m_mutex_unlock (out->self->lock); \
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _read_wait)(const concurrent_t out) \
|
||||
{ \
|
||||
assert (out->self == out); \
|
||||
m_cond_wait(out->self->there_is_data, out->self->lock); \
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _write_lock)(concurrent_t out) \
|
||||
{ \
|
||||
m_mutex_lock (out->lock); \
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _write_unlock)(concurrent_t out) \
|
||||
{ \
|
||||
m_mutex_unlock (out->lock); \
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _write_wait)(const concurrent_t out) \
|
||||
{ \
|
||||
m_cond_wait(out->self->there_is_data, out->self->lock); \
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _write_signal)(concurrent_t out) \
|
||||
{ \
|
||||
m_cond_broadcast(out->there_is_data); \
|
||||
} \
|
||||
\
|
||||
CONCURRENTI_DEF_FUNC(name, type, oplist, concurrent_t, concurrent_it_t)
|
||||
|
||||
|
||||
// Internal definition of the functions.
|
||||
#define CONCURRENTI_DEF_FUNC(name, type, oplist, concurrent_t, concurrent_it_t) \
|
||||
M_IF_METHOD(INIT, oplist)( \
|
||||
static inline void \
|
||||
M_C(name, _init)(concurrent_t out) \
|
||||
{ \
|
||||
M_C(name, _internal_init)(out); \
|
||||
M_CALL_INIT(oplist, out->data); \
|
||||
CONCURRENTI_CONTRACT(out); \
|
||||
} \
|
||||
@@ -130,12 +207,10 @@
|
||||
{ \
|
||||
CONCURRENTI_CONTRACT(src); \
|
||||
assert (out != src); \
|
||||
m_mutex_init(out->lock); \
|
||||
m_cond_init(out->there_is_data); \
|
||||
out->self = out; \
|
||||
m_mutex_lock (src->self->lock); \
|
||||
M_C(name, _internal_init)(out); \
|
||||
M_C(name, _read_lock)(src); \
|
||||
M_CALL_INIT_SET(oplist, out->data, src->data); \
|
||||
m_mutex_unlock(src->self->lock); \
|
||||
M_C(name, _read_unlock)(src); \
|
||||
CONCURRENTI_CONTRACT(out); \
|
||||
} \
|
||||
,) \
|
||||
@@ -147,19 +222,19 @@
|
||||
CONCURRENTI_CONTRACT(out); \
|
||||
if (out == src) return; \
|
||||
if (out < src) { \
|
||||
m_mutex_lock (out->lock); \
|
||||
m_mutex_lock (src->self->lock); \
|
||||
M_C(name, _write_lock)(out); \
|
||||
M_C(name, _read_lock)(src); \
|
||||
} else { \
|
||||
m_mutex_lock (src->self->lock); \
|
||||
m_mutex_lock (out->lock); \
|
||||
M_C(name, _read_lock)(src); \
|
||||
M_C(name, _write_lock)(out); \
|
||||
} \
|
||||
M_CALL_SET(oplist, out->data, src->data); \
|
||||
if (out < src) { \
|
||||
m_mutex_unlock (src->self->lock); \
|
||||
m_mutex_unlock (out->lock); \
|
||||
M_C(name, _read_lock)(src); \
|
||||
M_C(name, _write_unlock)(out); \
|
||||
} else { \
|
||||
m_mutex_unlock (out->lock); \
|
||||
m_mutex_unlock (src->self->lock); \
|
||||
M_C(name, _write_unlock)(out); \
|
||||
M_C(name, _read_unlock)(src); \
|
||||
} \
|
||||
CONCURRENTI_CONTRACT(out); \
|
||||
} \
|
||||
@@ -172,8 +247,7 @@
|
||||
CONCURRENTI_CONTRACT(out); \
|
||||
/* No need to lock */ \
|
||||
M_CALL_CLEAR(oplist, out->data); \
|
||||
m_mutex_clear (out->lock); \
|
||||
m_cond_clear(out->there_is_data); \
|
||||
M_C(name, _internal_clear)(out); \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
@@ -184,12 +258,9 @@
|
||||
CONCURRENTI_CONTRACT(src); \
|
||||
assert (out != src); \
|
||||
/* No need to lock 'src' ? */ \
|
||||
m_mutex_init (out->lock); \
|
||||
m_cond_init (out->there_is_data); \
|
||||
out->self = out; \
|
||||
M_C(name, _internal_init)(out); \
|
||||
M_CALL_INIT_MOVE(oplist, out->data, src->data); \
|
||||
m_mutex_clear (src->lock); \
|
||||
m_cond_clear (src->there_is_data); \
|
||||
M_C(name, _internal_clear)(src); \
|
||||
CONCURRENTI_CONTRACT(out); \
|
||||
} \
|
||||
,) \
|
||||
@@ -201,11 +272,10 @@
|
||||
CONCURRENTI_CONTRACT(out); \
|
||||
CONCURRENTI_CONTRACT(src); \
|
||||
/* No need to lock 'src' ? */ \
|
||||
m_mutex_lock (out->lock); \
|
||||
M_C(name, _write_lock)(out); \
|
||||
M_CALL_MOVE(oplist, out->data, src->data); \
|
||||
m_mutex_unlock (out->lock); \
|
||||
m_mutex_clear (src->lock); \
|
||||
m_cond_clear (src->there_is_data); \
|
||||
M_C(name, _write_unlock)(out); \
|
||||
M_C(name, _internal_clear)(src); \
|
||||
CONCURRENTI_CONTRACT(out); \
|
||||
} \
|
||||
,) \
|
||||
@@ -217,19 +287,19 @@
|
||||
CONCURRENTI_CONTRACT(out); \
|
||||
CONCURRENTI_CONTRACT(src); \
|
||||
if (out < src) { \
|
||||
m_mutex_lock (out->lock); \
|
||||
m_mutex_lock (src->lock); \
|
||||
M_C(name, _write_lock)(out); \
|
||||
M_C(name, _write_lock)(src); \
|
||||
} else { \
|
||||
m_mutex_lock (src->lock); \
|
||||
m_mutex_lock (out->lock); \
|
||||
M_C(name, _write_lock)(src); \
|
||||
M_C(name, _write_lock)(out); \
|
||||
} \
|
||||
M_CALL_SWAP(oplist, out->data, src->data); \
|
||||
if (out < src) { \
|
||||
m_mutex_unlock (src->lock); \
|
||||
m_mutex_unlock (out->lock); \
|
||||
M_C(name, _write_unlock)(src); \
|
||||
M_C(name, _write_unlock)(out); \
|
||||
} else { \
|
||||
m_mutex_unlock (out->lock); \
|
||||
m_mutex_unlock (src->lock); \
|
||||
M_C(name, _write_unlock)(out); \
|
||||
M_C(name, _write_unlock)(src); \
|
||||
} \
|
||||
} \
|
||||
,) \
|
||||
@@ -239,9 +309,9 @@
|
||||
M_C(name, _clean)(concurrent_t out) \
|
||||
{ \
|
||||
CONCURRENTI_CONTRACT(out); \
|
||||
m_mutex_lock (out->lock); \
|
||||
M_C(name, _write_lock)(out); \
|
||||
M_CALL_CLEAN(oplist, out->data); \
|
||||
m_mutex_unlock (out->lock); \
|
||||
M_C(name, _write_unlock)(out); \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
@@ -250,11 +320,23 @@
|
||||
M_C(name, _empty_p)(concurrent_t const out) \
|
||||
{ \
|
||||
CONCURRENTI_CONTRACT(out); \
|
||||
m_mutex_lock (out->self->lock); \
|
||||
M_C(name, _read_lock)(out); \
|
||||
bool b = M_CALL_TEST_EMPTY(oplist, out->data); \
|
||||
m_mutex_unlock (out->self->lock); \
|
||||
M_C(name, _read_unlock)(out); \
|
||||
return b; \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
M_IF_METHOD(GET_SIZE, oplist)( \
|
||||
static inline size_t \
|
||||
M_C(name, _size)(concurrent_t const out) \
|
||||
{ \
|
||||
CONCURRENTI_CONTRACT(out); \
|
||||
M_C(name, _read_lock)(out); \
|
||||
size_t r = M_CALL_GET_SIZE(oplist, out->data); \
|
||||
M_C(name, _read_unlock)(out); \
|
||||
return r; \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
M_IF_METHOD(SET_KEY, oplist)( \
|
||||
@@ -262,25 +344,25 @@
|
||||
M_C(name, _set_at)(concurrent_t out, M_GET_KEY_TYPE oplist const key, M_GET_VALUE_TYPE oplist const data) \
|
||||
{ \
|
||||
CONCURRENTI_CONTRACT(out); \
|
||||
m_mutex_lock (out->lock); \
|
||||
M_C(name, _write_lock)(out); \
|
||||
M_CALL_SET_KEY(oplist, out->data, key, data); \
|
||||
m_cond_broadcast(out->there_is_data); \
|
||||
m_mutex_unlock (out->lock); \
|
||||
M_C(name, _write_signal)(out); \
|
||||
M_C(name, _write_unlock)(out); \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
M_IF_METHOD(GET_KEY, oplist)( \
|
||||
static inline bool \
|
||||
M_C(name, _get_copy)(M_GET_VALUE_TYPE oplist *out_data, concurrent_t out, M_GET_KEY_TYPE oplist const key) \
|
||||
M_C(name, _get_copy)(M_GET_VALUE_TYPE oplist *out_data, const concurrent_t out, M_GET_KEY_TYPE oplist const key) \
|
||||
{ \
|
||||
CONCURRENTI_CONTRACT(out); \
|
||||
assert (out_data != NULL); \
|
||||
m_mutex_lock (out->lock); \
|
||||
M_C(name, _read_lock)(out); \
|
||||
M_GET_VALUE_TYPE oplist *p = M_CALL_GET_KEY(oplist, out->data, key); \
|
||||
if (p != NULL) { \
|
||||
M_CALL_SET(M_GET_VALUE_OPLIST oplist, *out_data, *p); \
|
||||
} \
|
||||
m_mutex_unlock (out->lock); \
|
||||
M_C(name, _read_unlock)(out); \
|
||||
return p != NULL; \
|
||||
} \
|
||||
,) \
|
||||
@@ -291,11 +373,11 @@
|
||||
{ \
|
||||
CONCURRENTI_CONTRACT(out); \
|
||||
assert (out_data != NULL); \
|
||||
m_mutex_lock (out->lock); \
|
||||
M_C(name, _write_lock)(out); \
|
||||
M_GET_VALUE_TYPE oplist *p = M_CALL_GET_SET_KEY(oplist, out->data, key); \
|
||||
assert (p != NULL); \
|
||||
M_CALL_SET(M_GET_VALUE_OPLIST oplist, *out_data, *p); \
|
||||
m_mutex_unlock (out->lock); \
|
||||
M_C(name, _write_unlock)(out); \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
@@ -304,9 +386,9 @@
|
||||
M_C(name, _erase)(concurrent_t out, M_GET_KEY_TYPE oplist const key) \
|
||||
{ \
|
||||
CONCURRENTI_CONTRACT(out); \
|
||||
m_mutex_lock (out->lock); \
|
||||
M_C(name, _write_lock)(out); \
|
||||
bool b = M_CALL_ERASE_KEY(oplist, out->data, key); \
|
||||
m_mutex_unlock (out->lock); \
|
||||
M_C(name, _write_unlock)(out); \
|
||||
return b; \
|
||||
} \
|
||||
,) \
|
||||
@@ -316,10 +398,10 @@
|
||||
M_C(name, _push)(concurrent_t out, M_GET_SUBTYPE oplist const data) \
|
||||
{ \
|
||||
CONCURRENTI_CONTRACT(out); \
|
||||
m_mutex_lock (out->lock); \
|
||||
M_C(name, _write_lock)(out); \
|
||||
M_CALL_PUSH(oplist, out->data, data); \
|
||||
m_cond_broadcast(out->there_is_data); \
|
||||
m_mutex_unlock (out->lock); \
|
||||
M_C(name, _write_signal)(out); \
|
||||
M_C(name, _write_unlock)(out); \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
@@ -328,9 +410,9 @@
|
||||
M_C(name, _pop)(M_GET_SUBTYPE oplist *p, concurrent_t out) \
|
||||
{ \
|
||||
CONCURRENTI_CONTRACT(out); \
|
||||
m_mutex_lock (out->lock); \
|
||||
M_C(name, _write_lock)(out); \
|
||||
M_CALL_POP(oplist, p, out->data); \
|
||||
m_mutex_unlock (out->lock); \
|
||||
M_C(name, _write_unlock)(out); \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
@@ -339,10 +421,10 @@
|
||||
M_C(name, _push_move)(concurrent_t out, M_GET_SUBTYPE oplist *data) \
|
||||
{ \
|
||||
CONCURRENTI_CONTRACT(out); \
|
||||
m_mutex_lock (out->lock); \
|
||||
M_C(name, _write_lock)(out); \
|
||||
M_CALL_PUSH_MOVE(oplist, out->data, data); \
|
||||
m_cond_broadcast(out->there_is_data); \
|
||||
m_mutex_unlock (out->lock); \
|
||||
M_C(name, _write_signal)(out); \
|
||||
M_C(name, _write_unlock)(out); \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
@@ -351,9 +433,9 @@
|
||||
M_C(name, _pop_move)(M_GET_SUBTYPE oplist *p, concurrent_t out) \
|
||||
{ \
|
||||
CONCURRENTI_CONTRACT(out); \
|
||||
m_mutex_lock (out->lock); \
|
||||
M_C(name, _write_lock)(out); \
|
||||
M_CALL_POP_MOVE(oplist, p, out->data); \
|
||||
m_mutex_unlock (out->lock); \
|
||||
M_C(name, _write_unlock)(out); \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
@@ -362,9 +444,9 @@
|
||||
M_C(name, _get_str)(string_t str, concurrent_t const out, bool a) \
|
||||
{ \
|
||||
CONCURRENTI_CONTRACT(out); \
|
||||
m_mutex_lock (out->self->lock); \
|
||||
M_C(name, _read_lock)(out); \
|
||||
M_CALL_GET_STR(oplist, str, out->data, a); \
|
||||
m_mutex_unlock (out->self->lock); \
|
||||
M_C(name, _read_unlock)(out); \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
@@ -373,9 +455,9 @@
|
||||
M_C(name, _out_str)(FILE *f, concurrent_t const out) \
|
||||
{ \
|
||||
CONCURRENTI_CONTRACT(out); \
|
||||
m_mutex_lock (out->self->lock); \
|
||||
M_C(name, _read_lock)(out); \
|
||||
M_CALL_OUT_STR(oplist, f, out->data); \
|
||||
m_mutex_unlock (out->self->lock); \
|
||||
M_C(name, _read_unlock)(out); \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
@@ -384,9 +466,10 @@
|
||||
M_C(name, _parse_str)(concurrent_t out, const char str[], const char **e) \
|
||||
{ \
|
||||
CONCURRENTI_CONTRACT(out); \
|
||||
m_mutex_lock (out->lock); \
|
||||
M_C(name, _write_lock)(out); \
|
||||
bool b = M_CALL_PARSE_STR(oplist, out->data, str, e); \
|
||||
m_mutex_unlock (out->lock); \
|
||||
M_C(name, _write_signal)(out); \
|
||||
M_C(name, _write_unlock)(out); \
|
||||
return b; \
|
||||
} \
|
||||
,) \
|
||||
@@ -396,9 +479,10 @@
|
||||
M_C(name, _in_str)(concurrent_t out, FILE *f) \
|
||||
{ \
|
||||
CONCURRENTI_CONTRACT(out); \
|
||||
m_mutex_lock (out->lock); \
|
||||
M_C(name, _write_lock)(out); \
|
||||
bool b = M_CALL_IN_STR(oplist, out->data, f); \
|
||||
m_mutex_unlock (out->lock); \
|
||||
M_C(name, _write_signal)(out); \
|
||||
M_C(name, _write_unlock)(out); \
|
||||
return b; \
|
||||
} \
|
||||
,) \
|
||||
@@ -411,19 +495,19 @@
|
||||
CONCURRENTI_CONTRACT(out2); \
|
||||
if (out1 == out2) return true; \
|
||||
if (out1 < out2) { \
|
||||
m_mutex_lock (out1->self->lock); \
|
||||
m_mutex_lock (out2->self->lock); \
|
||||
M_C(name, _read_lock)(out1); \
|
||||
M_C(name, _read_lock)(out2); \
|
||||
} else { \
|
||||
m_mutex_lock (out2->self->lock); \
|
||||
m_mutex_lock (out1->self->lock); \
|
||||
M_C(name, _read_lock)(out2); \
|
||||
M_C(name, _read_lock)(out1); \
|
||||
} \
|
||||
bool b = M_CALL_EQUAL(oplist, out1->data, out2->data); \
|
||||
if (out1 < out2) { \
|
||||
m_mutex_unlock (out2->self->lock); \
|
||||
m_mutex_unlock (out1->self->lock); \
|
||||
M_C(name, _read_unlock)(out2); \
|
||||
M_C(name, _read_unlock)(out1); \
|
||||
} else { \
|
||||
m_mutex_unlock (out1->self->lock); \
|
||||
m_mutex_unlock (out2->self->lock); \
|
||||
M_C(name, _read_unlock)(out1); \
|
||||
M_C(name, _read_unlock)(out2); \
|
||||
} \
|
||||
return b; \
|
||||
} \
|
||||
@@ -431,12 +515,12 @@
|
||||
\
|
||||
M_IF_METHOD(GET_KEY, oplist)( \
|
||||
static inline bool \
|
||||
M_C(name, _get_blocking)(M_GET_VALUE_TYPE oplist *out_data, concurrent_t out, M_GET_KEY_TYPE oplist const key, bool blocking) \
|
||||
M_C(name, _get_blocking)(M_GET_VALUE_TYPE oplist *out_data, const concurrent_t out, M_GET_KEY_TYPE oplist const key, bool blocking) \
|
||||
{ \
|
||||
CONCURRENTI_CONTRACT(out); \
|
||||
assert (out_data != NULL); \
|
||||
bool ret = false; \
|
||||
m_mutex_lock (out->lock); \
|
||||
M_C(name, _read_lock)(out); \
|
||||
while (true) { \
|
||||
M_GET_VALUE_TYPE oplist *p = M_CALL_GET_KEY(oplist, out->data, key); \
|
||||
if (p != NULL) { \
|
||||
@@ -445,9 +529,9 @@
|
||||
break; \
|
||||
} \
|
||||
if (blocking == false) break; \
|
||||
m_cond_wait(out->there_is_data, out->lock); \
|
||||
M_C(name, _read_wait)(out); \
|
||||
} \
|
||||
m_mutex_unlock (out->lock); \
|
||||
M_C(name, _read_unlock)(out); \
|
||||
return ret; \
|
||||
} \
|
||||
,) \
|
||||
@@ -459,7 +543,7 @@
|
||||
CONCURRENTI_CONTRACT(out); \
|
||||
assert (p != NULL); \
|
||||
bool ret = false; \
|
||||
m_mutex_lock (out->lock); \
|
||||
M_C(name, _write_lock)(out); \
|
||||
while (true) { \
|
||||
if (!M_CALL_TEST_EMPTY(oplist, out->data)) { \
|
||||
M_CALL_POP(oplist, p, out->data); \
|
||||
@@ -467,9 +551,9 @@
|
||||
break; \
|
||||
} \
|
||||
if (blocking == false) break; \
|
||||
m_cond_wait(out->there_is_data, out->lock); \
|
||||
M_C(name, _write_wait)(out); \
|
||||
} \
|
||||
m_mutex_unlock (out->lock); \
|
||||
M_C(name, _write_unlock)(out); \
|
||||
return ret; \
|
||||
} \
|
||||
,) \
|
||||
@@ -481,7 +565,7 @@
|
||||
CONCURRENTI_CONTRACT(out); \
|
||||
assert (p != NULL); \
|
||||
bool ret = false; \
|
||||
m_mutex_lock (out->lock); \
|
||||
M_C(name, _write_lock)(out); \
|
||||
while (true) { \
|
||||
if (!M_CALL_TEST_EMPTY(oplist, out->data)) { \
|
||||
M_CALL_POP_MOVE(oplist, p, out->data); \
|
||||
@@ -489,9 +573,9 @@
|
||||
break; \
|
||||
} \
|
||||
if (blocking == false) break; \
|
||||
m_cond_wait(out->there_is_data, out->lock); \
|
||||
M_C(name, _write_wait)(out); \
|
||||
} \
|
||||
m_mutex_unlock (out->lock); \
|
||||
M_C(name, _write_unlock)(out); \
|
||||
return ret; \
|
||||
} \
|
||||
,) \
|
||||
@@ -501,11 +585,151 @@
|
||||
M_C(name, _hash)(concurrent_t const out) \
|
||||
{ \
|
||||
CONCURRENTI_CONTRACT(out); \
|
||||
m_mutex_lock (out->self->lock); \
|
||||
M_C(name, _read_lock)(out); \
|
||||
size_t h = M_CALL_HASH(oplist, out->data); \
|
||||
m_mutex_unlock (out->self->lock); \
|
||||
M_C(name, _read_unlock)(out); \
|
||||
return h; \
|
||||
} \
|
||||
,) \
|
||||
|
||||
|
||||
|
||||
// Deferred evaluation for the concurrent definition.
|
||||
#define CONCURRENTI_RP_DEF(arg) CONCURRENTI_RP_DEF2 arg
|
||||
|
||||
// Internal definition of RP
|
||||
#define CONCURRENTI_RP_DEF2(name, type, oplist, concurrent_t, concurrent_it_t) \
|
||||
\
|
||||
typedef struct M_C(name, _s) { \
|
||||
struct M_C(name, _s) *self; \
|
||||
m_mutex_t lock; \
|
||||
m_cond_t rw_done; \
|
||||
size_t read_count; \
|
||||
bool writer_waiting; \
|
||||
m_cond_t there_is_data; /* condition raised when there is data */ \
|
||||
type data; \
|
||||
} concurrent_t[1]; \
|
||||
\
|
||||
typedef struct M_C(name, _s) *M_C(name, _ptr); \
|
||||
typedef const struct M_C(name, _s) *M_C(name, _srcptr); \
|
||||
\
|
||||
typedef type M_C(name, _type_t); \
|
||||
\
|
||||
/* Define the lock strategy (multi lock) */ \
|
||||
static inline void \
|
||||
M_C(name, _internal_init)(concurrent_t out) \
|
||||
{ \
|
||||
m_mutex_init(out->lock); \
|
||||
m_cond_init(out->rw_done); \
|
||||
m_cond_init(out->there_is_data); \
|
||||
out->self = out; \
|
||||
out->read_count = 0; \
|
||||
out->writer_waiting = false; \
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _internal_clear)(concurrent_t out) \
|
||||
{ \
|
||||
assert (out->self == out); \
|
||||
m_mutex_clear(out->lock); \
|
||||
m_cond_clear(out->rw_done); \
|
||||
m_cond_clear(out->there_is_data); \
|
||||
out->self = NULL; \
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _read_lock)(const concurrent_t out) \
|
||||
{ \
|
||||
struct M_C(name, _s) *self = out->self; \
|
||||
assert (self == out); \
|
||||
m_mutex_lock (self->lock); \
|
||||
while (self->writer_waiting == true) { \
|
||||
m_cond_wait(self->rw_done, self->lock); \
|
||||
} \
|
||||
self->read_count ++; \
|
||||
m_mutex_unlock (self->lock); \
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _read_unlock)(const concurrent_t out) \
|
||||
{ \
|
||||
struct M_C(name, _s) *self = out->self; \
|
||||
assert (self == out); \
|
||||
m_mutex_lock (self->lock); \
|
||||
self->read_count --; \
|
||||
if (self->read_count == 0) { \
|
||||
m_cond_broadcast (self->rw_done); \
|
||||
} \
|
||||
m_mutex_unlock (self->lock); \
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _write_lock)(concurrent_t out) \
|
||||
{ \
|
||||
m_mutex_lock (out->lock); \
|
||||
while (out->writer_waiting == true) { \
|
||||
m_cond_wait(out->rw_done, out->lock); \
|
||||
} \
|
||||
out->writer_waiting = true; \
|
||||
while (out->read_count > 0) { \
|
||||
m_cond_wait(out->rw_done, out->lock); \
|
||||
} \
|
||||
m_mutex_unlock (out->lock); \
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _write_unlock)(concurrent_t out) \
|
||||
{ \
|
||||
m_mutex_lock (out->lock); \
|
||||
out->writer_waiting = false; \
|
||||
m_cond_broadcast (out->rw_done); \
|
||||
m_mutex_unlock (out->lock); \
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _read_wait)(const concurrent_t out) \
|
||||
{ \
|
||||
struct M_C(name, _s) *self = out->self; \
|
||||
assert (self == out); \
|
||||
m_mutex_lock (out->self->lock); \
|
||||
self->read_count --; \
|
||||
if (self->read_count == 0) { \
|
||||
m_cond_broadcast (self->rw_done); \
|
||||
} \
|
||||
m_cond_wait(self->there_is_data, self->lock); \
|
||||
while (self->writer_waiting == true) { \
|
||||
m_cond_wait(self->rw_done, self->lock); \
|
||||
} \
|
||||
self->read_count ++; \
|
||||
m_mutex_unlock (out->self->lock); \
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _write_wait)(concurrent_t out) \
|
||||
{ \
|
||||
m_mutex_lock (out->lock); \
|
||||
out->writer_waiting = false; \
|
||||
m_cond_broadcast (out->rw_done); \
|
||||
m_cond_wait(out->there_is_data, out->lock); \
|
||||
while (out->writer_waiting == true) { \
|
||||
m_cond_wait(out->rw_done, out->lock); \
|
||||
} \
|
||||
out->writer_waiting = true; \
|
||||
while (out->read_count > 0) { \
|
||||
m_cond_wait(out->rw_done, out->lock); \
|
||||
} \
|
||||
m_mutex_unlock (out->lock); \
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _write_signal)(concurrent_t out) \
|
||||
{ \
|
||||
m_mutex_lock (out->lock); \
|
||||
m_cond_broadcast(out->there_is_data); \
|
||||
m_mutex_unlock (out->lock); \
|
||||
} \
|
||||
\
|
||||
CONCURRENTI_DEF_FUNC(name, type, oplist, concurrent_t, concurrent_it_t)
|
||||
|
||||
|
||||
#endif
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* M*LIB - Extended Pre-processing macros module
|
||||
*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
#define M_CORE_VERSION_MAJOR 0
|
||||
#define M_CORE_VERSION_MINOR 2
|
||||
#define M_CORE_VERSION_PATCHLEVEL 0
|
||||
#define M_CORE_VERSION_PATCHLEVEL 2
|
||||
|
||||
/* By default, always use stdio. Can be turned off in specific environement if needed */
|
||||
#ifndef M_USE_STDIO
|
||||
@@ -78,20 +78,28 @@
|
||||
|
||||
/* Define the exclusion size so that 2 atomic variables can be in
|
||||
separate cache line. This prevents false sharing to occur within the
|
||||
CPU cache. */
|
||||
CPU cache line. */
|
||||
#if defined(_M_X64) || defined(_M_AMD64) || defined(__x86_64__)
|
||||
# define M_ALIGN_FOR_CACHELINE_EXCLUSION 128
|
||||
#else
|
||||
# define M_ALIGN_FOR_CACHELINE_EXCLUSION 64
|
||||
#endif
|
||||
|
||||
/* Deprecated attribute for function */
|
||||
/* Deprecated attribute for a function */
|
||||
#if defined(__GNUC__) && __GNUC__ >= 4
|
||||
#define M_ATTR_DEPRECATED __attribute__((deprecated))
|
||||
#else
|
||||
#define M_ATTR_DEPRECATED
|
||||
#endif
|
||||
|
||||
/* Extension attribute to silent warnings on extensions */
|
||||
#if defined(__GNUC__)
|
||||
#define M_ATTR_EXTENSION __extension__
|
||||
#else
|
||||
#define M_ATTR_EXTENSION
|
||||
#endif
|
||||
|
||||
|
||||
/***************************************************************/
|
||||
/****************** Preprocessing Times Macro ******************/
|
||||
/***************************************************************/
|
||||
@@ -1465,6 +1473,7 @@ M_PARSE_DEFAULT_TYPE_DEF(m_core_parse_ldouble, long double, strtold, )
|
||||
(y) = _tmp; \
|
||||
} while (0)
|
||||
|
||||
|
||||
/* Check if 'n' is assignable to an object of type 'type'.
|
||||
It is as if we create a temporary of type 'type' and assign 'n' to it.
|
||||
Return the object 'n' if it is possible.
|
||||
@@ -1477,6 +1486,7 @@ M_PARSE_DEFAULT_TYPE_DEF(m_core_parse_ldouble, long double, strtold, )
|
||||
# define M_ASSIGN_CAST(type, n) static_cast<type>(n)
|
||||
#endif
|
||||
|
||||
|
||||
/* Cast 'n' of type 'type*' into 'type const*'.
|
||||
This is like (type const*)p but safer as the type of 'n' is checked,
|
||||
and more robust for double arrays type.
|
||||
@@ -1492,10 +1502,12 @@ M_PARSE_DEFAULT_TYPE_DEF(m_core_parse_ldouble, long double, strtold, )
|
||||
# define M_CONST_CAST(type, n) const_cast<type*>(n)
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* From a pointer to a 'field_type' 'field' of a 'type'structure,
|
||||
* return pointer to the structure.
|
||||
* NOTE: Cast Inside!
|
||||
* From a pointer to the 'field' field of type 'field_type' of a 'type' structure,
|
||||
* return the pointer to the structure.
|
||||
* Used to handle intrusive structure.
|
||||
* NOTE: Use an ASSIGN_CAST to make the cast a little bit safer.
|
||||
*/
|
||||
#define M_TYPE_FROM_FIELD(type, ptr, field_type, field) \
|
||||
((type *)(void*)( (char *)M_ASSIGN_CAST(field_type*, (ptr)) - offsetof(type, field) ))
|
||||
@@ -1504,6 +1516,26 @@ M_PARSE_DEFAULT_TYPE_DEF(m_core_parse_ldouble, long double, strtold, )
|
||||
((type const *)(const void*)( (const char *)M_ASSIGN_CAST(field_type const *, (ptr)) - offsetof(type const, field) ))
|
||||
|
||||
|
||||
/* Use to generate a dummy alignment field for cache alignment within a structure
|
||||
Take the name of the dummy field, and a list of the type of the fields that previously fill in the structure.
|
||||
*/
|
||||
#define M_APPLY_SIZEOF(id) sizeof (id)
|
||||
#define M_ADD_SIZE(a,b) a + b
|
||||
#define M_CACHELINE_ALIGN(name, ...) \
|
||||
char name[M_ALIGN_FOR_CACHELINE_EXCLUSION > M_REDUCE(M_APPLY_SIZEOF, M_ADD_SIZE, __VA_ARGS__) \
|
||||
? M_ALIGN_FOR_CACHELINE_EXCLUSION - M_REDUCE(M_APPLY_SIZEOF, M_ADD_SIZE, __VA_ARGS__) : 1]
|
||||
|
||||
|
||||
/* C++ doesn't support flexible array within a structure.
|
||||
Let's define at least one element for an array. */
|
||||
#ifdef __cplusplus
|
||||
# define M_MIN_FLEX_ARRAY_SIZE 1
|
||||
#else
|
||||
# define M_MIN_FLEX_ARRAY_SIZE
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
/************************************************************/
|
||||
/********************* HASH selection ***********************/
|
||||
/************************************************************/
|
||||
@@ -1649,7 +1681,7 @@ m_core_hash (const void *str, size_t length)
|
||||
uint32_t hash32 = 2166136261U ^ M_HASH_SEED;
|
||||
const uint8_t *p = (const uint8_t *)str;
|
||||
|
||||
assert (str != NULL);
|
||||
assert (str != NULL || length == 0);
|
||||
assert ( (( (uintptr_t)p & (sizeof(uint32_t)-1) ) == 0) || (length <= sizeof(uint32_t)));
|
||||
|
||||
while (length >= 2*sizeof(uint32_t)) {
|
||||
@@ -1682,7 +1714,7 @@ m_core_hash (const void *str, size_t length)
|
||||
uint64_t hash64 = 14695981039346656037ULL ^ M_HASH_SEED;
|
||||
const uint8_t *p = M_ASSIGN_CAST(const uint8_t *, str);
|
||||
|
||||
assert (str != NULL);
|
||||
assert (str != NULL || length == 0);
|
||||
assert ( (( (uintptr_t)p & (sizeof(uint64_t)-1) ) == 0) || (length <= sizeof(uint32_t)));
|
||||
|
||||
while (length >= 2*sizeof(uint64_t)) {
|
||||
@@ -1763,6 +1795,7 @@ m_core_hash (const void *str, size_t length)
|
||||
#define M_OPLIST_OPLIST(a) ,a,
|
||||
#define M_SORT_SORT(a) ,a,
|
||||
#define M_UPDATE_UPDATE(a) ,a,
|
||||
#define M_UPDATE_TYPE_UPDATE_TYPE(a) ,a,
|
||||
#define M_SPLICE_BACK_SPLICE_BACK(a) ,a,
|
||||
#define M_SPLICE_AT_SPLICE_AT(a) ,a,
|
||||
#define M_IT_TYPE_IT_TYPE(a) ,a,
|
||||
@@ -1793,6 +1826,7 @@ m_core_hash (const void *str, size_t length)
|
||||
#define M_SET_KEY_SET_KEY(a) ,a,
|
||||
#define M_GET_SET_KEY_GET_SET_KEY(a) ,a,
|
||||
#define M_ERASE_KEY_ERASE_KEY(a) ,a,
|
||||
#define M_GET_SIZE_GET_SIZE(a) ,a,
|
||||
#define M_PUSH_PUSH(a) ,a,
|
||||
#define M_POP_POP(a) ,a,
|
||||
#define M_PUSH_MOVE_PUSH_MOVE(a) ,a,
|
||||
@@ -1835,6 +1869,7 @@ m_core_hash (const void *str, size_t length)
|
||||
#define M_GET_EQUAL(...) M_GET_METHOD(EQUAL, M_EQUAL_DEFAULT, __VA_ARGS__)
|
||||
#define M_GET_CMP(...) M_GET_METHOD(CMP, M_CMP_DEFAULT, __VA_ARGS__)
|
||||
#define M_GET_UPDATE(...) M_GET_METHOD(UPDATE, M_SET_DEFAULT, __VA_ARGS__)
|
||||
#define M_GET_UPDATE_TYPE(...) M_GET_METHOD(UPDATE_TYPE, M_NO_DEFAULT, __VA_ARGS__)
|
||||
#define M_GET_TYPE(...) M_GET_METHOD(TYPE, M_NO_DEFAULT, __VA_ARGS__)
|
||||
#define M_GET_SUBTYPE(...) M_GET_METHOD(SUBTYPE, M_NO_DEFAULT, __VA_ARGS__)
|
||||
#define M_GET_OPLIST(...) M_GET_METHOD(OPLIST, (), __VA_ARGS__)
|
||||
@@ -1869,6 +1904,7 @@ m_core_hash (const void *str, size_t length)
|
||||
#define M_GET_SET_KEY(...) M_GET_METHOD(SET_KEY, M_NO_DEFAULT, __VA_ARGS__)
|
||||
#define M_GET_GET_SET_KEY(...) M_GET_METHOD(GET_SET_KEY, M_NO_DEFAULT, __VA_ARGS__)
|
||||
#define M_GET_ERASE_KEY(...) M_GET_METHOD(ERASE_KEY, M_NO_DEFAULT, __VA_ARGS__)
|
||||
#define M_GET_GET_SIZE(...) M_GET_METHOD(GET_SIZE, M_NO_DEFAULT, __VA_ARGS__)
|
||||
#define M_GET_PUSH(...) M_GET_METHOD(PUSH, M_NO_DEFAULT, __VA_ARGS__)
|
||||
#define M_GET_POP(...) M_GET_METHOD(POP, M_NO_DEFAULT, __VA_ARGS__)
|
||||
#define M_GET_PUSH_MOVE(...) M_GET_METHOD(PUSH_MOVE, M_NO_DEFAULT, __VA_ARGS__)
|
||||
@@ -1934,6 +1970,7 @@ m_core_hash (const void *str, size_t length)
|
||||
#define M_CALL_SET_KEY(oplist, ...) M_APPLY_API(M_GET_SET_KEY oplist, oplist, __VA_ARGS__)
|
||||
#define M_CALL_GET_SET_KEY(oplist, ...) M_APPLY_API(M_GET_GET_SET_KEY oplist, oplist, __VA_ARGS__)
|
||||
#define M_CALL_ERASE_KEY(oplist, ...) M_APPLY_API(M_GET_ERASE_KEY oplist, oplist, __VA_ARGS__)
|
||||
#define M_CALL_GET_SIZE(oplist, ...) M_APPLY_API(M_GET_GET_SIZE oplist, oplist, __VA_ARGS__)
|
||||
#define M_CALL_PUSH(oplist, ...) M_APPLY_API(M_GET_PUSH oplist, oplist, __VA_ARGS__)
|
||||
#define M_CALL_POP(oplist, ...) M_APPLY_API(M_GET_POP oplist, oplist, __VA_ARGS__)
|
||||
#define M_CALL_PUSH_MOVE(oplist, ...) M_APPLY_API(M_GET_PUSH_MOVE oplist, oplist, __VA_ARGS__)
|
||||
@@ -2067,12 +2104,16 @@ m_core_hash (const void *str, size_t length)
|
||||
HASH(M_HASH_DEFAULT), SWAP(M_SWAP_DEFAULT) )
|
||||
#endif
|
||||
|
||||
/* Default oplist for standard types for pointers.
|
||||
*/
|
||||
#define M_PTR_OPLIST \
|
||||
(INIT(M_INIT_DEFAULT), INIT_SET(M_SET_DEFAULT), SET(M_SET_DEFAULT), \
|
||||
CLEAR(M_NOTHING_DEFAULT), EQUAL(M_EQUAL_DEFAULT), \
|
||||
INIT_MOVE(M_MOVE_DEFAULT), MOVE(M_MOVE_DEFAULT) , \
|
||||
SWAP(M_SWAP_DEFAULT) )
|
||||
|
||||
/* Default oplist for complex objects with "classic" names for methods.
|
||||
*/
|
||||
#define M_CLASSIC_OPLIST(name) ( \
|
||||
INIT(M_C(name, _init)), \
|
||||
INIT_SET(M_C(name, _init_set)), \
|
||||
@@ -2345,27 +2386,69 @@ m_core_hash (const void *str, size_t length)
|
||||
// * abort
|
||||
#ifndef M_MEMORY_FULL
|
||||
#define M_MEMORY_FULL(size) do { \
|
||||
fprintf(stderr, "ERROR(M*LIB): Can not allocate memory in function %s of file %s:%d (%zu bytes).\n", \
|
||||
__func__, __FILE__, __LINE__, (size_t) size); \
|
||||
abort(); \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#ifndef M_INIT_FAILURE
|
||||
#define M_INIT_FAILURE() do { \
|
||||
fprintf(stderr, "ERROR: Can not initialize data in (%s:%s:%d).\n", \
|
||||
__FILE__, __func__, __LINE__); \
|
||||
fprintf(stderr, "ERROR(M*LIB): Cannot allocate %zu bytes of memory at (%s:%s:%d).\n", \
|
||||
(size_t) (size), __func__, __FILE__, __LINE__); \
|
||||
abort(); \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#ifndef M_ASSERT_INIT
|
||||
#define M_ASSERT_INIT(expr) do { \
|
||||
#define M_ASSERT_INIT(expr, object) { \
|
||||
if (!(expr)) { \
|
||||
fprintf(stderr, "ERROR: Can not initialize data in (%s:%s:%d): %s\n", \
|
||||
__FILE__, __func__, __LINE__, #expr); \
|
||||
fprintf(stderr, "ERROR(M*LIB): Cannot initialize %s at (%s:%s:%d): %s\n", \
|
||||
(object), __FILE__, __func__, __LINE__, #expr); \
|
||||
abort(); \
|
||||
} } while (0)
|
||||
#endif
|
||||
|
||||
/************************************************************/
|
||||
/******************* Exponential Backoff ********************/
|
||||
/************************************************************/
|
||||
|
||||
#ifndef M_BACKOFF_MAX_COUNT
|
||||
#define M_BACKOFF_MAX_COUNT 6
|
||||
#endif
|
||||
|
||||
typedef struct m_backoff_s {
|
||||
unsigned int count;
|
||||
unsigned int seed;
|
||||
} m_backoff_t[1];
|
||||
|
||||
static inline void
|
||||
m_backoff_init(m_backoff_t backoff)
|
||||
{
|
||||
backoff->count = 0;
|
||||
backoff->seed = rand();
|
||||
}
|
||||
|
||||
static inline void
|
||||
m_backoff_reset(m_backoff_t backoff)
|
||||
{
|
||||
backoff->count = 0;
|
||||
}
|
||||
|
||||
static inline void
|
||||
m_backoff_wait(m_backoff_t backoff)
|
||||
{
|
||||
volatile int x = 0;
|
||||
/* Cheap but fast pseudo random */
|
||||
backoff->seed = backoff->seed * 34721 + 17449;
|
||||
const unsigned int mask = (1U << backoff->count) -1;
|
||||
const unsigned int count = mask & (backoff->seed >> 8);
|
||||
/* Active sleep */
|
||||
for(unsigned int i = 0; i <= count; i++)
|
||||
x = 0;
|
||||
(void) x;
|
||||
/* Increment count for next step if needed */
|
||||
backoff->count += (backoff->count < M_BACKOFF_MAX_COUNT);
|
||||
}
|
||||
|
||||
static inline void
|
||||
m_backoff_clear(m_backoff_t backoff)
|
||||
{
|
||||
// Nothing to do
|
||||
(void) backoff;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* M*LIB - DEQUE module
|
||||
*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -46,14 +46,6 @@
|
||||
|
||||
#define DEQUEUI_DEFAULT_SIZE 8
|
||||
|
||||
/* C++ doesn't support flexible array within a structure.
|
||||
Let's define at least one element. */
|
||||
#ifdef __cplusplus
|
||||
# define DEQUEI_MIN_ARRAY_SIZE 1
|
||||
#else
|
||||
# define DEQUEI_MIN_ARRAY_SIZE
|
||||
#endif
|
||||
|
||||
#define DEQUEI_CONTRACT(d) do { \
|
||||
assert ((d) != NULL); \
|
||||
assert ((d)->default_size >= DEQUEUI_DEFAULT_SIZE); \
|
||||
@@ -74,7 +66,7 @@
|
||||
typedef struct M_C(name, _node_s) { \
|
||||
ILIST_INTERFACE(M_C(name, _node_list), struct M_C(name, _node_s)); \
|
||||
size_t size; \
|
||||
type data[DEQUEI_MIN_ARRAY_SIZE]; \
|
||||
type data[M_MIN_FLEX_ARRAY_SIZE]; \
|
||||
} node_t; \
|
||||
\
|
||||
/* Each node is allocated with a variable size (so we use \
|
||||
@@ -888,6 +880,7 @@
|
||||
,IT_REF(M_C(name,_ref)) \
|
||||
,IT_CREF(M_C(name,_cref)) \
|
||||
,CLEAN(M_C(name,_clean)) \
|
||||
,GET_SIZE(M_C(name, _size)) \
|
||||
,PUSH(M_C(name,_push_back)) \
|
||||
,POP(M_C(name,_pop_back)) \
|
||||
,OPLIST(oplist) \
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* M*LIB - DICTIONARY Module
|
||||
*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -519,6 +519,13 @@
|
||||
M_C(name, _list_pair_it_set)(it->list_it, ref->list_it); \
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _it_end)(dict_it_t it, const dict_t d) \
|
||||
{ \
|
||||
DICTI_CONTRACT(name, d); \
|
||||
M_C(name, _array_list_pair_it_end)(it->array_it, d->table); \
|
||||
} \
|
||||
\
|
||||
static inline bool \
|
||||
M_C(name, _end_p)(const dict_it_t it) \
|
||||
{ \
|
||||
@@ -829,6 +836,7 @@
|
||||
IT_TYPE(M_C(name, _it_t)), \
|
||||
IT_FIRST(M_C(name,_it)), \
|
||||
IT_SET(M_C(name, _it_set)), \
|
||||
IT_END(M_C(name,_it_end)), \
|
||||
IT_END_P(M_C(name,_end_p)), \
|
||||
IT_LAST_P(M_C(name,_last_p)), \
|
||||
IT_NEXT(M_C(name,_next)), \
|
||||
@@ -842,6 +850,7 @@
|
||||
,ERASE_KEY(M_C(name, _erase)) \
|
||||
,KEY_OPLIST(key_oplist) \
|
||||
,VALUE_OPLIST(value_oplist) \
|
||||
,GET_SIZE(M_C(name, _size)) \
|
||||
,M_IF_METHOD_BOTH(GET_STR, key_oplist, value_oplist)(GET_STR(M_C(name, _get_str)),) \
|
||||
,M_IF_METHOD_BOTH(PARSE_STR, key_oplist, value_oplist)(PARSE_STR(M_C(name, _parse_str)),) \
|
||||
,M_IF_METHOD_BOTH(OUT_STR, key_oplist, value_oplist)(OUT_STR(M_C(name, _out_str)),) \
|
||||
@@ -875,9 +884,11 @@
|
||||
ERASE_KEY(M_C(name, _erase)), \
|
||||
KEY_OPLIST(oplist), \
|
||||
VALUE_OPLIST(oplist), \
|
||||
GET_SIZE(M_C(name, _size)), \
|
||||
IT_TYPE(M_C(name, _it_t)), \
|
||||
IT_FIRST(M_C(name,_it)), \
|
||||
IT_SET(M_C(name, _it_set)), \
|
||||
IT_END(M_C(name,_it_end)), \
|
||||
IT_END_P(M_C(name,_end_p)), \
|
||||
IT_LAST_P(M_C(name,_last_p)), \
|
||||
IT_NEXT(M_C(name,_next)), \
|
||||
@@ -933,16 +944,18 @@ typedef enum {
|
||||
#define DICTI_OA_DEF_P1(args) DICTI_OA_DEF_P2 args
|
||||
#define DICTI_OA_DEF_P2(name, key_type, key_oplist, value_type, value_oplist) \
|
||||
DICTI_OA_DEF_P3(name, key_type, key_oplist, value_type, value_oplist, \
|
||||
M_GET_OOR_EQUAL key_oplist, M_GET_OOR_SET key_oplist, \
|
||||
0.2, 0.7, M_C(name,_t), M_C(name, _it_t) )
|
||||
0.2, 0.7, M_C(name,_t), M_C(name, _it_t) )
|
||||
|
||||
#define DICTI_OA_DEF_P3(name, key_type, key_oplist, value_type, value_oplist, oor_equal_p, oor_set, coeff_down, coeff_up, dict_t, dict_it_t) \
|
||||
#define DICTI_OA_DEF_P3(name, key_type, key_oplist, value_type, value_oplist, coeff_down, coeff_up, dict_t, dict_it_t) \
|
||||
\
|
||||
typedef struct M_C(name, _pair_s) { \
|
||||
key_type key; \
|
||||
value_type value; \
|
||||
} M_C(name, _pair_t); \
|
||||
\
|
||||
typedef key_type M_C(name, _key_type_t); \
|
||||
typedef value_type M_C(name, _value_type_t); \
|
||||
\
|
||||
/* NOTE: We don't want a real oplist for this type */ \
|
||||
ARRAY_DEF(M_C(name, _array_pair), M_C(name, _pair_t), \
|
||||
(INIT(M_NOTHING_DEFAULT), SET(M_MEMCPY_DEFAULT), \
|
||||
@@ -979,11 +992,11 @@ typedef enum {
|
||||
M_C(name,_int_limit)(dict, DICTI_INITIAL_SIZE); \
|
||||
dict->data = M_CALL_REALLOC(key_oplist, M_C(name, _pair_t), NULL, DICTI_INITIAL_SIZE); \
|
||||
if (dict->data == NULL) { \
|
||||
M_MEMORY_FULL(sizeof (M_C(name, _pair_t)) * DICTI_INITIAL_SIZE); \
|
||||
M_MEMORY_FULL(sizeof (M_C(name, _pair_t)) * DICTI_INITIAL_SIZE); \
|
||||
return ; \
|
||||
} \
|
||||
for(size_t i = 0; i < DICTI_INITIAL_SIZE; i++) { \
|
||||
oor_set(dict->data[i].key, DICTI_OA_EMPTY); \
|
||||
M_CALL_OOR_SET(key_oplist, dict->data[i].key, DICTI_OA_EMPTY); \
|
||||
} \
|
||||
DICTI_OA_CONTRACT(dict); \
|
||||
} \
|
||||
@@ -993,8 +1006,8 @@ typedef enum {
|
||||
{ \
|
||||
DICTI_OA_CONTRACT(dict); \
|
||||
for(size_t i = 0; i <= dict->mask; i++) { \
|
||||
if (!oor_equal_p(dict->data[i].key, DICTI_OA_EMPTY) \
|
||||
&& !oor_equal_p(dict->data[i].key, DICTI_OA_DELETED)) { \
|
||||
if (!M_CALL_OOR_EQUAL(key_oplist, dict->data[i].key, DICTI_OA_EMPTY) \
|
||||
&& !M_CALL_OOR_EQUAL(key_oplist, dict->data[i].key, DICTI_OA_DELETED)) { \
|
||||
M_CALL_CLEAR(key_oplist, dict->data[i].key); \
|
||||
M_CALL_CLEAR(value_oplist, dict->data[i].value); \
|
||||
} \
|
||||
@@ -1010,8 +1023,8 @@ typedef enum {
|
||||
{ \
|
||||
DICTI_OA_CONTRACT(dict); \
|
||||
/* NOTE: Key can not be the representation of empty or deleted */ \
|
||||
assert (!oor_equal_p(key, DICTI_OA_EMPTY)); \
|
||||
assert (!oor_equal_p(key, DICTI_OA_DELETED)); \
|
||||
assert (!M_CALL_OOR_EQUAL(key_oplist, key, DICTI_OA_EMPTY)); \
|
||||
assert (!M_CALL_OOR_EQUAL(key_oplist, key, DICTI_OA_DELETED)); \
|
||||
\
|
||||
M_C(name, _pair_t) *const data = dict->data; \
|
||||
const size_t mask = dict->mask; \
|
||||
@@ -1020,7 +1033,7 @@ typedef enum {
|
||||
/* Random access, and probably cache miss */ \
|
||||
if (M_LIKELY (M_CALL_EQUAL(key_oplist, data[p].key, key)) ) \
|
||||
return &data[p].value; \
|
||||
else if (M_LIKELY (oor_equal_p (data[p].key, DICTI_OA_EMPTY)) ) \
|
||||
else if (M_LIKELY (M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY)) ) \
|
||||
return NULL; \
|
||||
\
|
||||
/* Unlikely case */ \
|
||||
@@ -1030,7 +1043,7 @@ typedef enum {
|
||||
if (M_CALL_EQUAL(key_oplist, data[p].key, key)) \
|
||||
return &data[p].value; \
|
||||
assert (s <= dict->mask); \
|
||||
} while (!oor_equal_p(data[p].key, DICTI_OA_EMPTY) ); \
|
||||
} while (!M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY) ); \
|
||||
\
|
||||
return NULL; \
|
||||
} \
|
||||
@@ -1050,8 +1063,8 @@ typedef enum {
|
||||
size_t empty = 0; \
|
||||
size_t del = 0; \
|
||||
for(size_t i = 0 ; i <= h->mask ; i++) { \
|
||||
empty += oor_equal_p(data[i].key, DICTI_OA_EMPTY); \
|
||||
del += oor_equal_p(data[i].key, DICTI_OA_DELETED); \
|
||||
empty += M_CALL_OOR_EQUAL(key_oplist, data[i].key, DICTI_OA_EMPTY); \
|
||||
del += M_CALL_OOR_EQUAL(key_oplist, data[i].key, DICTI_OA_DELETED); \
|
||||
} \
|
||||
assert(del == 0); \
|
||||
assert(empty + h->count == h->mask + 1); \
|
||||
@@ -1076,7 +1089,7 @@ typedef enum {
|
||||
\
|
||||
/* First mark the extended space as empty */ \
|
||||
for(size_t i = oldSize ; i < newSize; i++) \
|
||||
oor_set(data[i].key, DICTI_OA_EMPTY); \
|
||||
M_CALL_OOR_SET(key_oplist, data[i].key, DICTI_OA_EMPTY); \
|
||||
} \
|
||||
\
|
||||
/* Then let's rehash all the entries in their **exact** position. \
|
||||
@@ -1089,10 +1102,10 @@ typedef enum {
|
||||
const size_t mask = (newSize -1); \
|
||||
\
|
||||
for(size_t i = 0 ; i < oldSize; i++) { \
|
||||
if (!oor_equal_p(data[i].key, DICTI_OA_DELETED) && !oor_equal_p(data[i].key, DICTI_OA_EMPTY)) { \
|
||||
if (!M_CALL_OOR_EQUAL(key_oplist, data[i].key, DICTI_OA_DELETED) && !M_CALL_OOR_EQUAL(key_oplist, data[i].key, DICTI_OA_EMPTY)) { \
|
||||
size_t p = M_CALL_HASH(key_oplist, data[i].key) & mask; \
|
||||
if (p != i) { \
|
||||
if (M_LIKELY (oor_equal_p(data[p].key, DICTI_OA_EMPTY) || oor_equal_p(data[p].key, DICTI_OA_DELETED))) { \
|
||||
if (M_LIKELY (M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY) || M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_DELETED))) { \
|
||||
M_DO_INIT_MOVE(key_oplist, data[p].key, data[i].key); \
|
||||
M_DO_INIT_MOVE(value_oplist, data[p].value, data[i].value); \
|
||||
} else { \
|
||||
@@ -1100,10 +1113,10 @@ typedef enum {
|
||||
M_DO_INIT_MOVE(key_oplist, ptr->key, data[i].key); \
|
||||
M_DO_INIT_MOVE(value_oplist, ptr->value, data[i].value); \
|
||||
} \
|
||||
oor_set(data[i].key, DICTI_OA_EMPTY); \
|
||||
M_CALL_OOR_SET(key_oplist, data[i].key, DICTI_OA_EMPTY); \
|
||||
} \
|
||||
} else { \
|
||||
oor_set(data[i].key, DICTI_OA_EMPTY); \
|
||||
M_CALL_OOR_SET(key_oplist, data[i].key, DICTI_OA_EMPTY); \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
@@ -1114,15 +1127,15 @@ typedef enum {
|
||||
M_C(name, _pair_t) *item = M_C(name, _array_pair_get)(tmp, i); \
|
||||
size_t p = M_CALL_HASH(key_oplist, item->key) & mask; \
|
||||
/* NOTE: since the first pass, the bucket might be free now */ \
|
||||
if (!oor_equal_p(data[p].key, DICTI_OA_EMPTY)) { \
|
||||
if (!M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY)) { \
|
||||
size_t s = 1; \
|
||||
do { \
|
||||
p = (p + DICTI_OA_PROBING(s)) & mask; \
|
||||
assert (s <= h->mask); \
|
||||
} while (!oor_equal_p(data[p].key, DICTI_OA_EMPTY) ); \
|
||||
} while (!M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY) ); \
|
||||
} \
|
||||
/* FIXME: How can I use INIT_MOVE without garbaging the array? ==> POP_INIT_MOVE ! */ \
|
||||
M_CALL_INIT_SET(key_oplist, data[p].key, item->key); \
|
||||
M_CALL_INIT_SET(key_oplist, data[p].key, item->key); \
|
||||
M_CALL_INIT_SET(value_oplist, data[p].value, item->value); \
|
||||
} \
|
||||
\
|
||||
@@ -1143,8 +1156,8 @@ typedef enum {
|
||||
{ \
|
||||
DICTI_OA_CONTRACT(dict); \
|
||||
/* NOTE: key can not be the representation of empty or deleted */ \
|
||||
assert (!oor_equal_p(key, DICTI_OA_EMPTY)); \
|
||||
assert (!oor_equal_p(key, DICTI_OA_DELETED)); \
|
||||
assert (!M_CALL_OOR_EQUAL(key_oplist, key, DICTI_OA_EMPTY)); \
|
||||
assert (!M_CALL_OOR_EQUAL(key_oplist, key, DICTI_OA_DELETED)); \
|
||||
\
|
||||
M_C(name, _pair_t) *const data = dict->data; \
|
||||
const size_t mask = dict->mask; \
|
||||
@@ -1155,9 +1168,9 @@ typedef enum {
|
||||
M_CALL_SET(value_oplist, data[p].value, value); \
|
||||
return; \
|
||||
} \
|
||||
if (M_UNLIKELY (!oor_equal_p(data[p].key, DICTI_OA_EMPTY) ) ) { \
|
||||
if (M_UNLIKELY (!M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY) ) ) { \
|
||||
size_t delPos = -1; \
|
||||
if (oor_equal_p(data[p].key, DICTI_OA_DELETED)) delPos = p; \
|
||||
if (M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_DELETED)) delPos = p; \
|
||||
size_t s = 1; \
|
||||
do { \
|
||||
p = (p + DICTI_OA_PROBING(s)) & mask; \
|
||||
@@ -1166,8 +1179,8 @@ typedef enum {
|
||||
return; \
|
||||
} \
|
||||
assert (s <= dict->mask); \
|
||||
if (oor_equal_p(data[p].key, DICTI_OA_DELETED) && delPos == (size_t)-1) delPos = p; \
|
||||
} while (!oor_equal_p(data[p].key, DICTI_OA_EMPTY) ); \
|
||||
if (M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_DELETED) && delPos == (size_t)-1) delPos = p; \
|
||||
} while (!M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY) ); \
|
||||
if (delPos != (size_t) -1) { \
|
||||
p = delPos; \
|
||||
dict->count_delete --; \
|
||||
@@ -1197,8 +1210,8 @@ typedef enum {
|
||||
{ \
|
||||
DICTI_OA_CONTRACT(dict); \
|
||||
/* NOTE: key can not be the representation of empty or deleted */ \
|
||||
assert (!oor_equal_p(key, DICTI_OA_EMPTY)); \
|
||||
assert (!oor_equal_p(key, DICTI_OA_DELETED)); \
|
||||
assert (!M_CALL_OOR_EQUAL(key_oplist, key, DICTI_OA_EMPTY)); \
|
||||
assert (!M_CALL_OOR_EQUAL(key_oplist, key, DICTI_OA_DELETED)); \
|
||||
\
|
||||
M_C(name, _pair_t) *const data = dict->data; \
|
||||
const size_t mask = dict->mask; \
|
||||
@@ -1207,9 +1220,9 @@ typedef enum {
|
||||
if (M_CALL_EQUAL(key_oplist, data[p].key, key)) { \
|
||||
return &data[p].value; \
|
||||
} \
|
||||
if (M_UNLIKELY (!oor_equal_p(data[p].key, DICTI_OA_EMPTY) ) ) { \
|
||||
if (M_UNLIKELY (!M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY) ) ) { \
|
||||
size_t delPos = -1; \
|
||||
if (oor_equal_p(data[p].key, DICTI_OA_DELETED)) delPos = p; \
|
||||
if (M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_DELETED)) delPos = p; \
|
||||
size_t s = 1; \
|
||||
do { \
|
||||
p = (p + DICTI_OA_PROBING(s)) & mask; \
|
||||
@@ -1217,8 +1230,8 @@ typedef enum {
|
||||
return &data[p].value; \
|
||||
} \
|
||||
assert (s <= dict->mask); \
|
||||
if (oor_equal_p(data[p].key, DICTI_OA_DELETED) && delPos == (size_t)-1) delPos = p; \
|
||||
} while (!oor_equal_p(data[p].key, DICTI_OA_EMPTY) ); \
|
||||
if (M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_DELETED) && delPos == (size_t)-1) delPos = p; \
|
||||
} while (!M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY) ); \
|
||||
if (delPos != (size_t) -1) { \
|
||||
p = delPos; \
|
||||
dict->count_delete --; \
|
||||
@@ -1260,15 +1273,15 @@ typedef enum {
|
||||
\
|
||||
/* Pass 1: scan lower entries, and move them if needed */ \
|
||||
for(size_t i = 0; i < newSize; i++) { \
|
||||
if (oor_equal_p(data[i].key, DICTI_OA_EMPTY)) \
|
||||
if (M_CALL_OOR_EQUAL(key_oplist, data[i].key, DICTI_OA_EMPTY)) \
|
||||
continue; \
|
||||
if (oor_equal_p(data[i].key, DICTI_OA_DELETED)) { \
|
||||
oor_set(data[i].key, DICTI_OA_EMPTY); \
|
||||
if (M_CALL_OOR_EQUAL(key_oplist, data[i].key, DICTI_OA_DELETED)) { \
|
||||
M_CALL_OOR_SET(key_oplist, data[i].key, DICTI_OA_EMPTY); \
|
||||
continue; \
|
||||
} \
|
||||
size_t p = M_CALL_HASH(key_oplist, data[i].key) & mask; \
|
||||
if (p != i) { \
|
||||
if (oor_equal_p(data[p].key, DICTI_OA_EMPTY) || oor_equal_p(data[p].key, DICTI_OA_DELETED)) { \
|
||||
if (M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY) || M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_DELETED)) { \
|
||||
M_DO_INIT_MOVE(key_oplist, data[p].key, data[i].key); \
|
||||
M_DO_INIT_MOVE(value_oplist, data[p].value, data[i].value); \
|
||||
} else { \
|
||||
@@ -1276,21 +1289,21 @@ typedef enum {
|
||||
M_DO_INIT_MOVE(key_oplist, ptr->key, data[i].key); \
|
||||
M_DO_INIT_MOVE(value_oplist, ptr->value, data[i].value); \
|
||||
} \
|
||||
oor_set(data[i].key, DICTI_OA_EMPTY); \
|
||||
M_CALL_OOR_SET(key_oplist, data[i].key, DICTI_OA_EMPTY); \
|
||||
} \
|
||||
} \
|
||||
/* Pass 2: scan upper entries and move them back */ \
|
||||
for(size_t i = newSize; i < oldSize; i++) { \
|
||||
if (!oor_equal_p(data[i].key, DICTI_OA_DELETED) \
|
||||
&& !oor_equal_p(data[i].key, DICTI_OA_EMPTY)) { \
|
||||
if (!M_CALL_OOR_EQUAL(key_oplist, data[i].key, DICTI_OA_DELETED) \
|
||||
&& !M_CALL_OOR_EQUAL(key_oplist, data[i].key, DICTI_OA_EMPTY)) { \
|
||||
size_t p = M_CALL_HASH(key_oplist, data[i].key) & mask; \
|
||||
assert (p < i); \
|
||||
if (!oor_equal_p(data[p].key, DICTI_OA_EMPTY)) { \
|
||||
if (!M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY)) { \
|
||||
size_t s = 1; \
|
||||
do { \
|
||||
p = (p + DICTI_OA_PROBING(s)) & mask; \
|
||||
assert (s <= h->mask); \
|
||||
} while (!oor_equal_p(data[p].key, DICTI_OA_EMPTY) ); \
|
||||
} while (!M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY) ); \
|
||||
} \
|
||||
M_DO_INIT_MOVE(key_oplist, data[p].key, data[i].key); \
|
||||
M_DO_INIT_MOVE(value_oplist, data[p].value, data[i].value); \
|
||||
@@ -1300,12 +1313,12 @@ typedef enum {
|
||||
for(size_t i = 0; i < M_C(name, _array_pair_size)(tmp); i++) { \
|
||||
M_C(name, _pair_t) *item = M_C(name, _array_pair_get)(tmp, i); \
|
||||
size_t p = M_CALL_HASH(key_oplist, item->key) & mask; \
|
||||
if (!oor_equal_p(data[p].key, DICTI_OA_EMPTY)) { \
|
||||
if (!M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY)) { \
|
||||
size_t s = 1; \
|
||||
do { \
|
||||
p = (p + DICTI_OA_PROBING(s)) & mask; \
|
||||
assert (s <= h->mask); \
|
||||
} while (!oor_equal_p(data[p].key, DICTI_OA_EMPTY) ); \
|
||||
} while (!M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY) ); \
|
||||
} \
|
||||
M_CALL_INIT_SET(key_oplist, data[p].key, item->key); \
|
||||
M_CALL_INIT_SET(value_oplist, data[p].value, item->value); \
|
||||
@@ -1329,28 +1342,28 @@ typedef enum {
|
||||
{ \
|
||||
DICTI_OA_CONTRACT(dict); \
|
||||
/* NOTE: key can't be the representation of empty or deleted */ \
|
||||
assert (!oor_equal_p(key, DICTI_OA_EMPTY)); \
|
||||
assert (!oor_equal_p(key, DICTI_OA_DELETED)); \
|
||||
\
|
||||
assert (!M_CALL_OOR_EQUAL(key_oplist, key, DICTI_OA_EMPTY)); \
|
||||
assert (!M_CALL_OOR_EQUAL(key_oplist, key, DICTI_OA_DELETED)); \
|
||||
\
|
||||
M_C(name, _pair_t) *const data = dict->data; \
|
||||
const size_t mask = dict->mask; \
|
||||
size_t p = M_CALL_HASH(key_oplist, key) & mask; \
|
||||
\
|
||||
/* Random access, and probably cache miss */ \
|
||||
if (M_UNLIKELY (!M_CALL_EQUAL(key_oplist, data[p].key, key)) ) { \
|
||||
if (oor_equal_p (data[p].key, DICTI_OA_EMPTY)) \
|
||||
if (M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY)) \
|
||||
return false; \
|
||||
size_t s = 1; \
|
||||
do { \
|
||||
p = (p + DICTI_OA_PROBING(s)) & mask; \
|
||||
if (oor_equal_p(data[p].key, DICTI_OA_EMPTY) ) \
|
||||
if (M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY) ) \
|
||||
return false; \
|
||||
assert (s <= dict->mask); \
|
||||
} while (!M_CALL_EQUAL(key_oplist, data[p].key, key)); \
|
||||
} \
|
||||
M_CALL_CLEAR(key_oplist, data[p].key); \
|
||||
M_CALL_CLEAR(value_oplist, data[p].value); \
|
||||
oor_set (data[p].key, DICTI_OA_DELETED); \
|
||||
M_CALL_OOR_SET(key_oplist, data[p].key, DICTI_OA_DELETED); \
|
||||
assert (dict->count >= 1); \
|
||||
dict->count--; \
|
||||
if (M_UNLIKELY (dict->count < dict->lower_limit)) { \
|
||||
@@ -1390,10 +1403,10 @@ typedef enum {
|
||||
return ; \
|
||||
} \
|
||||
for(size_t i = 0; i <= org->mask; i++) { \
|
||||
if (oor_equal_p(org->data[i].key, DICTI_OA_EMPTY)) { \
|
||||
oor_set(map->data[i].key, DICTI_OA_EMPTY); \
|
||||
} else if (oor_equal_p(org->data[i].key, DICTI_OA_DELETED)) { \
|
||||
oor_set(map->data[i].key, DICTI_OA_DELETED); \
|
||||
if (M_CALL_OOR_EQUAL(key_oplist, org->data[i].key, DICTI_OA_EMPTY)) { \
|
||||
M_CALL_OOR_SET(key_oplist, map->data[i].key, DICTI_OA_EMPTY); \
|
||||
} else if (M_CALL_OOR_EQUAL(key_oplist, org->data[i].key, DICTI_OA_DELETED)) { \
|
||||
M_CALL_OOR_SET(key_oplist, map->data[i].key, DICTI_OA_DELETED); \
|
||||
} else { \
|
||||
M_CALL_INIT_SET(key_oplist, map->data[i].key, org->data[i].key); \
|
||||
M_CALL_INIT_SET(value_oplist, map->data[i].value, org->data[i].value); \
|
||||
@@ -1463,8 +1476,8 @@ typedef enum {
|
||||
{ \
|
||||
DICTI_OA_CONTRACT(d); \
|
||||
for(size_t i = 0; i <= d->mask; i++) { \
|
||||
if (!oor_equal_p(d->data[i].key, DICTI_OA_EMPTY) \
|
||||
&& !oor_equal_p(d->data[i].key, DICTI_OA_DELETED)) { \
|
||||
if (!M_CALL_OOR_EQUAL(key_oplist, d->data[i].key, DICTI_OA_EMPTY) \
|
||||
&& !M_CALL_OOR_EQUAL(key_oplist, d->data[i].key, DICTI_OA_DELETED)) { \
|
||||
M_CALL_CLEAR(key_oplist, d->data[i].key); \
|
||||
M_CALL_CLEAR(value_oplist, d->data[i].value); \
|
||||
} \
|
||||
@@ -1477,7 +1490,7 @@ typedef enum {
|
||||
d->data, DICTI_INITIAL_SIZE); \
|
||||
assert(d->data != NULL); \
|
||||
for(size_t i = 0; i <= d->mask; i++) { \
|
||||
oor_set(d->data[i].key, DICTI_OA_EMPTY); \
|
||||
M_CALL_OOR_SET(key_oplist, d->data[i].key, DICTI_OA_EMPTY); \
|
||||
} \
|
||||
DICTI_OA_CONTRACT(d); \
|
||||
} \
|
||||
@@ -1489,8 +1502,8 @@ typedef enum {
|
||||
assert (it != NULL); \
|
||||
it->dict = d; \
|
||||
size_t i = 0; \
|
||||
while ((oor_equal_p (d->data[i].key, DICTI_OA_EMPTY) \
|
||||
|| oor_equal_p (d->data[i].key, DICTI_OA_DELETED)) \
|
||||
while ((M_CALL_OOR_EQUAL(key_oplist, d->data[i].key, DICTI_OA_EMPTY) \
|
||||
|| M_CALL_OOR_EQUAL(key_oplist, d->data[i].key, DICTI_OA_DELETED)) \
|
||||
&& i <= d->mask) { \
|
||||
i++; \
|
||||
} \
|
||||
@@ -1514,8 +1527,8 @@ typedef enum {
|
||||
assert (it != NULL); \
|
||||
it->dict = d; \
|
||||
size_t i = d->mask; \
|
||||
while ((oor_equal_p (d->data[i].key, DICTI_OA_EMPTY) \
|
||||
|| oor_equal_p (d->data[i].key, DICTI_OA_DELETED)) \
|
||||
while ((M_CALL_OOR_EQUAL(key_oplist, d->data[i].key, DICTI_OA_EMPTY) \
|
||||
|| M_CALL_OOR_EQUAL(key_oplist, d->data[i].key, DICTI_OA_DELETED)) \
|
||||
&& i <= d->mask) { \
|
||||
i--; \
|
||||
} \
|
||||
@@ -1546,8 +1559,8 @@ typedef enum {
|
||||
DICTI_OA_CONTRACT (it->dict); \
|
||||
size_t i = it->index + 1; \
|
||||
while (i <= it->dict->mask && \
|
||||
(oor_equal_p (it->dict->data[i].key, DICTI_OA_EMPTY) \
|
||||
|| oor_equal_p (it->dict->data[i].key, DICTI_OA_DELETED))) { \
|
||||
(M_CALL_OOR_EQUAL(key_oplist, it->dict->data[i].key, DICTI_OA_EMPTY) \
|
||||
|| M_CALL_OOR_EQUAL(key_oplist, it->dict->data[i].key, DICTI_OA_DELETED))) { \
|
||||
i++; \
|
||||
} \
|
||||
it->index = i; \
|
||||
@@ -1561,8 +1574,8 @@ typedef enum {
|
||||
/* if index was 0, the operation will overflow, and stops the loop */ \
|
||||
size_t i = it->index - 1; \
|
||||
while (i <= it->dict->mask && \
|
||||
(oor_equal_p (it->dict->data[i].key, DICTI_OA_EMPTY) \
|
||||
|| oor_equal_p (it->dict->data[i].key, DICTI_OA_DELETED))) { \
|
||||
(M_CALL_OOR_EQUAL(key_oplist, it->dict->data[i].key, DICTI_OA_EMPTY) \
|
||||
|| M_CALL_OOR_EQUAL(key_oplist, it->dict->data[i].key, DICTI_OA_DELETED))) { \
|
||||
i--; \
|
||||
} \
|
||||
it->index = i; \
|
||||
@@ -1594,8 +1607,8 @@ typedef enum {
|
||||
DICTI_OA_CONTRACT (it -> dict); \
|
||||
const size_t i = it->index; \
|
||||
assert (i <= it->dict->mask); \
|
||||
assert (!oor_equal_p (it->dict->data[i].key, DICTI_OA_EMPTY)); \
|
||||
assert (!oor_equal_p (it->dict->data[i].key, DICTI_OA_DELETED)); \
|
||||
assert (!M_CALL_OOR_EQUAL(key_oplist, it->dict->data[i].key, DICTI_OA_EMPTY)); \
|
||||
assert (!M_CALL_OOR_EQUAL(key_oplist, it->dict->data[i].key, DICTI_OA_DELETED)); \
|
||||
return &it->dict->data[i]; \
|
||||
} \
|
||||
\
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* M*LIB - GENINT module
|
||||
*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
+7
-7
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* M*LIB - Intrusive List module
|
||||
*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -339,23 +339,23 @@ typedef struct ilist_head_s {
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _it_last)(list_it_t it, list_t list) \
|
||||
M_C(name, _it_last)(list_it_t it, list_t const list) \
|
||||
{ \
|
||||
ILISTI_CONTRACT(name, list); \
|
||||
assert (it != NULL); \
|
||||
it->head = &list->name; \
|
||||
it->head = list->name.next->prev; \
|
||||
it->current = list->name.prev; \
|
||||
it->next = &list->name; \
|
||||
it->next = it->head; \
|
||||
it->previous = list->name.prev->prev; \
|
||||
ILISTI_NODE_CONTRACT(it->current); \
|
||||
} \
|
||||
\
|
||||
static inline void \
|
||||
M_C(name, _it_end)(list_it_t it, list_t list) \
|
||||
M_C(name, _it_end)(list_it_t it, list_t const list) \
|
||||
{ \
|
||||
assert (it != NULL && list != NULL); \
|
||||
it->head = &list->name; \
|
||||
it->current = &list->name; \
|
||||
it->head = list->name.next->prev; \
|
||||
it->current = it->head; \
|
||||
it->next = list->name.next; \
|
||||
it->previous = list->name.prev; \
|
||||
ILISTI_NODE_CONTRACT(it->current); \
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* M*LIB - INTRUSIVE SHARED PTR Module
|
||||
*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* M*LIB - LIST module
|
||||
*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* M*LIB - MEMPOOL module
|
||||
*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* M*LIB - Thin Mutex & Thread wrapper
|
||||
*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -42,7 +42,7 @@ typedef thrd_t m_thread_t[1];
|
||||
static inline void m_mutex_init(m_mutex_t m)
|
||||
{
|
||||
int rc = mtx_init(m, mtx_plain);
|
||||
M_ASSERT_INIT (rc == 0);
|
||||
M_ASSERT_INIT (rc == 0, "mutex");
|
||||
}
|
||||
|
||||
static inline void m_mutex_clear(m_mutex_t m)
|
||||
@@ -63,7 +63,7 @@ static inline void m_mutex_unlock(m_mutex_t m)
|
||||
static inline void m_cond_init(m_cond_t c)
|
||||
{
|
||||
int rc = cnd_init(c);
|
||||
M_ASSERT_INIT (rc == 0);
|
||||
M_ASSERT_INIT (rc == 0, "conditional variable");
|
||||
}
|
||||
|
||||
static inline void m_cond_clear(m_cond_t c)
|
||||
@@ -89,7 +89,7 @@ static inline void m_cond_wait(m_cond_t c, m_mutex_t m)
|
||||
static inline void m_thread_create(m_thread_t t, void (*func)(void*), void* arg)
|
||||
{
|
||||
int rc = thrd_create(t, (int(*)(void*))(void(*)(void))func, arg);
|
||||
M_ASSERT_INIT (rc == thrd_success);
|
||||
M_ASSERT_INIT (rc == thrd_success, "thread");
|
||||
}
|
||||
|
||||
static inline void m_thread_join(m_thread_t t)
|
||||
@@ -120,6 +120,7 @@ static inline void m_oncei_call(m_oncei_t o, void (*func)(void))
|
||||
call_once(o,func);
|
||||
}
|
||||
|
||||
#define M_THREAD_ATTR _Thread_local
|
||||
|
||||
|
||||
// MSYS2 doesn't define _WIN32 by default.
|
||||
@@ -191,7 +192,7 @@ static inline void m_cond_wait(m_cond_t c, m_mutex_t m)
|
||||
static inline void m_thread_create(m_thread_t t, void (*func)(void*), void *arg)
|
||||
{
|
||||
*t = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) func, arg, 0, NULL);
|
||||
M_ASSERT_INIT (*t != NULL);
|
||||
M_ASSERT_INIT (*t != NULL, "thread");
|
||||
}
|
||||
|
||||
static inline void m_thread_join(m_thread_t t)
|
||||
@@ -212,7 +213,7 @@ static inline bool m_thread_sleep(unsigned long long usec)
|
||||
LARGE_INTEGER ft;
|
||||
ft.QuadPart = -(10ULL*usec);
|
||||
HANDLE hd = CreateWaitableTimer(NULL, TRUE, NULL);
|
||||
M_ASSERT_INIT (hd != NULL);
|
||||
M_ASSERT_INIT (hd != NULL, "timer");
|
||||
SetWaitableTimer(hd, &ft, 0, NULL, NULL, 0);
|
||||
DWORD dwWaitResult = WaitForSingleObject(hd, INFINITE);
|
||||
CloseHandle(hd);
|
||||
@@ -237,6 +238,12 @@ static inline void m_oncei_call(m_oncei_t o, void (*func)(void))
|
||||
InitOnceExecuteOnce(o, m_oncei_callback, (void*)(intptr_t)func, NULL);
|
||||
}
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
# define M_THREAD_ATTR __declspec( thread )
|
||||
#else
|
||||
# define M_THREAD_ATTR __thread
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
/**************************** PTHREAD version ******************************/
|
||||
@@ -259,7 +266,7 @@ typedef pthread_t m_thread_t[1];
|
||||
static inline void m_mutex_init(m_mutex_t m)
|
||||
{
|
||||
int _rc = pthread_mutex_init(m, NULL);
|
||||
M_ASSERT_INIT (_rc == 0);
|
||||
M_ASSERT_INIT (_rc == 0, "mutex");
|
||||
}
|
||||
|
||||
static inline void m_mutex_clear(m_mutex_t m)
|
||||
@@ -286,7 +293,7 @@ static inline void m_mutexi_lazy_lock(m_mutex_t m)
|
||||
static inline void m_cond_init(m_cond_t c)
|
||||
{
|
||||
int _rc = pthread_cond_init(c, NULL);
|
||||
M_ASSERT_INIT (_rc == 0);
|
||||
M_ASSERT_INIT (_rc == 0, "conditional variable");
|
||||
}
|
||||
|
||||
static inline void m_cond_clear(m_cond_t c)
|
||||
@@ -312,7 +319,7 @@ static inline void m_cond_wait(m_cond_t c, m_mutex_t m)
|
||||
static inline void m_thread_create(m_thread_t t, void (*func)(void*), void *arg)
|
||||
{
|
||||
int _rc = pthread_create(t, NULL, (void*(*)(void*))(void(*)(void))func, arg);
|
||||
M_ASSERT_INIT (_rc == 0);
|
||||
M_ASSERT_INIT (_rc == 0, "thread");
|
||||
}
|
||||
|
||||
static inline void m_thread_join(m_thread_t t)
|
||||
@@ -348,8 +355,13 @@ static inline void m_oncei_call(m_oncei_t o, void (*func)(void))
|
||||
pthread_once(o,func);
|
||||
}
|
||||
|
||||
#define M_THREAD_ATTR __thread
|
||||
|
||||
#endif
|
||||
|
||||
// TODO: m_thread_sleep doesn't yield thread during waiting, making it a poor choice for active waiting.
|
||||
// TODO: Obsolete M_LOCK macro.
|
||||
|
||||
/* M_LOCK macro. Allow simple locking encapsulation.
|
||||
USAGE:
|
||||
static M_LOCK_DECL(name);
|
||||
|
||||
+4
-1
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* M*LIB - dynamic priority queue module
|
||||
*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -65,6 +65,8 @@
|
||||
,PUSH(M_C(name,_push)) \
|
||||
,POP(M_C(name,_pop)) \
|
||||
,OPLIST(oplist) \
|
||||
,TEST_EMPTY(M_C(name, _empty_p)), \
|
||||
,GET_SIZE(M_C(name, _size)) \
|
||||
)
|
||||
|
||||
#define PRIOQUEUEI_DEF2(name, type, oplist, prioqueue_t, it_t) \
|
||||
@@ -342,5 +344,6 @@
|
||||
, /* No EQUAL */ \) \
|
||||
|
||||
|
||||
// TODO: set all & remove all function
|
||||
|
||||
#endif
|
||||
+2
-1
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* M*LIB - RED BLACK TREE module
|
||||
*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -61,6 +61,7 @@
|
||||
TYPE(M_C(name,_t)), \
|
||||
SUBTYPE(M_C(name, _type_t)), \
|
||||
TEST_EMPTY(M_C(name,_empty_p)), \
|
||||
GET_SIZE(M_C(name, _size)), \
|
||||
IT_TYPE(M_C(name, _it_t)), \
|
||||
IT_FIRST(M_C(name,_it)), \
|
||||
IT_SET(M_C(name,_it_set)), \
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* M*LIB - SHARED Module
|
||||
*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -305,7 +305,7 @@ static inline int sharedi_integer_cref(int *p) { return *p; }
|
||||
typedef struct M_C(name, _atype_s) { \
|
||||
atomic_uint cpt; \
|
||||
type x; \
|
||||
char align[M_ALIGN_FOR_CACHELINE_EXCLUSION > sizeof(type) +sizeof(atomic_uint) ? M_ALIGN_FOR_CACHELINE_EXCLUSION - sizeof(type) -sizeof(atomic_uint) : 1]; \
|
||||
M_CACHELINE_ALIGN(align, type, atomic_uint); \
|
||||
} M_C(name, _atype_t); \
|
||||
\
|
||||
typedef struct M_C(name, _s) { \
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* M*LIB - SNAPSHOT Module
|
||||
*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -120,7 +120,7 @@
|
||||
/* Create an aligned type to avoid false sharing between threads */ \
|
||||
typedef struct M_C(name, _aligned_type_s) { \
|
||||
type x; \
|
||||
char align[M_ALIGN_FOR_CACHELINE_EXCLUSION > sizeof(type) ? M_ALIGN_FOR_CACHELINE_EXCLUSION - sizeof(type) : 1]; \
|
||||
M_CACHELINE_ALIGN(align, type); \
|
||||
} M_C(name, _aligned_type_t); \
|
||||
\
|
||||
typedef struct M_C(name, _s) { \
|
||||
@@ -487,7 +487,7 @@ static inline void snapshot_mrsw_int_read_end(snapshot_mrsw_int_t s, unsigned in
|
||||
/* Create an aligned type to avoid false sharing between threads */ \
|
||||
typedef struct M_C(name, _aligned_type_s) { \
|
||||
type x; \
|
||||
char align[M_ALIGN_FOR_CACHELINE_EXCLUSION > sizeof(type) ? M_ALIGN_FOR_CACHELINE_EXCLUSION - sizeof(type) : 1]; \
|
||||
M_CACHELINE_ALIGN(align, type); \
|
||||
} M_C(name, _aligned_type_t); \
|
||||
\
|
||||
typedef struct M_C(name, _s) { \
|
||||
|
||||
+528
-294
File diff suppressed because it is too large.
Load diff
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* M*LIB - VARIANT module
|
||||
*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
+44
-32
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* M*LIB / WORKER - Extra worker interface
|
||||
*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -25,13 +25,22 @@
|
||||
#ifndef MSTARLIB_WORKER_H
|
||||
#define MSTARLIB_WORKER_H
|
||||
|
||||
#ifndef WORKER_DISABLE
|
||||
/* The User Code can define M_USE_WORKER to 0 to disable the use of workers.
|
||||
The macros / functions are then defined to only use one core.
|
||||
By default, the behavior is to use workers.
|
||||
*/
|
||||
#ifndef M_USE_WORKER
|
||||
# define M_USE_WORKER 1
|
||||
#endif
|
||||
|
||||
|
||||
#if M_USE_WORKER
|
||||
|
||||
#include "m-atomic.h"
|
||||
#include "m-buffer.h"
|
||||
#include "m-mutex.h"
|
||||
|
||||
/* Needed header for detection of how many core on the system */
|
||||
/* Include needed system header for detection of how many core on the system */
|
||||
#if defined(_WIN32)
|
||||
# include <windows.h>
|
||||
#elif (defined(__APPLE__) && defined(__MACH__)) \
|
||||
@@ -39,7 +48,7 @@
|
||||
|| defined(__NetBSD__) || defined(__OpenBSD__)
|
||||
# include <sys/param.h>
|
||||
# include <sys/sysctl.h>
|
||||
# define WORKER_USE_SYSCTL
|
||||
# define M_USE_WORKER_SYSCTL 1
|
||||
#else
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
@@ -53,29 +62,35 @@
|
||||
if C++, it will use Lambda function (and std::function) instead
|
||||
(It doesn't support pre-C++11 compiler).
|
||||
|
||||
Otherwise go with nested function for the MACRO version.
|
||||
Otherwise go with nested function (GCC) for the MACRO version.
|
||||
|
||||
This behavior can be overriden by User Code by defining to 1 or 0 the
|
||||
following macros:
|
||||
* M_USE_WORKER_CPP_FUNCTION
|
||||
* M_USE_WORKER_CLANG_BLOCK
|
||||
*/
|
||||
#if defined(__cplusplus) && !defined(WORKER_USE_CPP_FUNCTION)
|
||||
# define WORKER_USE_CPP_FUNCTION 1
|
||||
|
||||
#if defined(__cplusplus) && !defined(M_USE_WORKER_CPP_FUNCTION)
|
||||
# define M_USE_WORKER_CPP_FUNCTION 1
|
||||
# include <functional>
|
||||
#elif defined(__has_extension) && !defined(WORKER_USE_CLANG_BLOCK)
|
||||
#elif defined(__has_extension) && !defined(M_USE_WORKER_CLANG_BLOCK)
|
||||
# if __has_extension(blocks)
|
||||
# define WORKER_USE_CLANG_BLOCK 1
|
||||
# define M_USE_WORKER_CLANG_BLOCK 1
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef WORKER_USE_CLANG_BLOCK
|
||||
# define WORKER_USE_CLANG_BLOCK 0
|
||||
#ifndef M_USE_WORKER_CLANG_BLOCK
|
||||
# define M_USE_WORKER_CLANG_BLOCK 0
|
||||
#endif
|
||||
#ifndef WORKER_USE_CPP_FUNCTION
|
||||
# define WORKER_USE_CPP_FUNCTION 0
|
||||
#ifndef M_USE_WORKER_CPP_FUNCTION
|
||||
# define M_USE_WORKER_CPP_FUNCTION 0
|
||||
#endif
|
||||
|
||||
/* Control that not both options are selected at the same time.
|
||||
Note: there are not really incompatible, but if we use C++ we shall go to
|
||||
lambda directly (there is no need to support blocks)! */
|
||||
#if WORKER_USE_CLANG_BLOCK && WORKER_USE_CPP_FUNCTION
|
||||
# error WORKER_USE_CPP_FUNCTION and WORKER_USE_CLANG_BLOCK are both defined (not supported).
|
||||
lambda directly (there is no need to support blocks). */
|
||||
#if M_USE_WORKER_CLANG_BLOCK && M_USE_WORKER_CPP_FUNCTION
|
||||
# error M_USE_WORKER_CPP_FUNCTION and M_USE_WORKER_CLANG_BLOCK are both defined. This is not supported.
|
||||
#endif
|
||||
|
||||
/* This type defines a work order */
|
||||
@@ -83,10 +98,10 @@ typedef struct work_order_s {
|
||||
struct worker_sync_s *block;
|
||||
void * data;
|
||||
void (*func) (void *data);
|
||||
#if WORKER_USE_CLANG_BLOCK
|
||||
#if M_USE_WORKER_CLANG_BLOCK
|
||||
void (^blockFunc)(void *data);
|
||||
#endif
|
||||
#if WORKER_USE_CPP_FUNCTION
|
||||
#if M_USE_WORKER_CPP_FUNCTION
|
||||
std::function<void(void*)> function;
|
||||
#endif
|
||||
} worker_order_t;
|
||||
@@ -95,7 +110,7 @@ typedef struct work_order_s {
|
||||
* * MACRO to be used to send an empty order to stop the thread
|
||||
* * MACRO to complete the not-used fields
|
||||
*/
|
||||
#if WORKER_USE_CLANG_BLOCK || WORKER_USE_CPP_FUNCTION
|
||||
#if M_USE_WORKER_CLANG_BLOCK || M_USE_WORKER_CPP_FUNCTION
|
||||
# define WORKER_EMPTY_ORDER { NULL, NULL, NULL, NULL }
|
||||
# define WORKER_EXTRA_ORDER , NULL
|
||||
#else
|
||||
@@ -146,7 +161,7 @@ workeri_get_cpu_count(void)
|
||||
SYSTEM_INFO sysinfo;
|
||||
GetSystemInfo(&sysinfo);
|
||||
return sysinfo.dwNumberOfProcessors;
|
||||
#elif defined(WORKER_USE_SYSCTL)
|
||||
#elif defined(M_USE_WORKER_SYSCTL)
|
||||
int nm[2];
|
||||
int count = 0;
|
||||
size_t len = sizeof (count);
|
||||
@@ -167,13 +182,13 @@ workeri_exec(worker_order_t *w)
|
||||
{
|
||||
assert (w!= NULL && w->block != NULL);
|
||||
//printf ("Starting thread with data %p\n", w->data);
|
||||
#if WORKER_USE_CLANG_BLOCK
|
||||
#if M_USE_WORKER_CLANG_BLOCK
|
||||
//printf ("Running %s f=%p b=%p\n", (w->func == NULL) ? "Blocks" : "Function", w->func, w->blockFunc);
|
||||
if (w->func == NULL)
|
||||
w->blockFunc(w->data);
|
||||
else
|
||||
#endif
|
||||
#if WORKER_USE_CPP_FUNCTION
|
||||
#if M_USE_WORKER_CPP_FUNCTION
|
||||
//printf ("Running %s f=%p b=%p\n", (w->function == NULL) ? "Lambda" : "Function", w->func, w->blockFunc);
|
||||
if (w->function)
|
||||
w->function(w->data);
|
||||
@@ -291,7 +306,7 @@ worker_spawn(worker_sync_t block, void (*func)(void *data), void *data)
|
||||
(*func) (data);
|
||||
}
|
||||
|
||||
#if WORKER_USE_CLANG_BLOCK
|
||||
#if M_USE_WORKER_CLANG_BLOCK
|
||||
/* Spawn or not the given work order to workers,
|
||||
or do it ourself if no worker is available */
|
||||
static inline void
|
||||
@@ -310,7 +325,7 @@ worker_spawn_block(worker_sync_t block, void (^func)(void *data), void *data)
|
||||
}
|
||||
#endif
|
||||
|
||||
#if WORKER_USE_CPP_FUNCTION
|
||||
#if M_USE_WORKER_CPP_FUNCTION
|
||||
/* Spawn or not the given work order to workers,
|
||||
or do it ourself if no worker is available */
|
||||
static inline void
|
||||
@@ -377,12 +392,12 @@ worker_count(worker_t g)
|
||||
'input' is the list of input variables of the 'core' block within "( )"
|
||||
'output' is the list of output variables of the 'core' block within "( )"
|
||||
Output variables are only available after a synchronisation block. */
|
||||
#if WORKER_USE_CLANG_BLOCK
|
||||
#if M_USE_WORKER_CLANG_BLOCK
|
||||
#define WORKER_SPAWN(_block, _input, _core, _output) \
|
||||
WORKER_DEF_DATA(_input, _output) \
|
||||
WORKER_DEF_SUBBLOCK(_input, _output, _core) \
|
||||
worker_spawn_block ((_block), WORKER_SPAWN_SUBFUNC_NAME, &WORKER_SPAWN_DATA_NAME)
|
||||
#elif WORKER_USE_CPP_FUNCTION
|
||||
#elif M_USE_WORKER_CPP_FUNCTION
|
||||
// TODO: Explicit pass all arguments by reference.
|
||||
#define WORKER_SPAWN(_block, _input, _core, _output) \
|
||||
worker_spawn_function ((_block), [&](void *param) {(void)param ; _core } , NULL)
|
||||
@@ -452,12 +467,9 @@ worker_count(worker_t g)
|
||||
M_MAP(WORKER_PROPAGATE_SINGLE_OUTPUT, __VA_ARGS__)
|
||||
|
||||
|
||||
#else /* WORKER_DISABLE */
|
||||
#else /* M_USE_WORKER */
|
||||
|
||||
|
||||
/* User has defined WORKER_DISABLE to disable the use of workers.
|
||||
Define empty types and empty functions
|
||||
*/
|
||||
/* Define empty types and empty functions to not use any worker */
|
||||
|
||||
typedef struct worker_block_s {
|
||||
int x;
|
||||
@@ -475,6 +487,6 @@ typedef struct worker_s {
|
||||
#define worker_count(w) 1
|
||||
#define WORKER_SPAWN(b, i, c, o) do { c } while (0)
|
||||
|
||||
#endif /* WORKER_DISABLE */
|
||||
#endif /* M_USE_WORKER */
|
||||
|
||||
#endif
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
+6
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -25,9 +25,11 @@
|
||||
#include "m-array.h"
|
||||
#include "m-string.h"
|
||||
#include "m-deque.h"
|
||||
#include "test-obj.h"
|
||||
#include "m-dict.h"
|
||||
#include "m-algo.h"
|
||||
|
||||
#include "test-obj.h"
|
||||
|
||||
typedef struct over_s {
|
||||
unsigned long data;
|
||||
ILIST_INTERFACE(ilist_over, over_s);
|
||||
@@ -42,6 +44,7 @@ ILIST_DEF(ilist_over, over_t, M_POD_OPLIST)
|
||||
LIST_DEF(list_string, string_t, STRING_OPLIST)
|
||||
DEQUE_DEF(deque_obj, testobj_t, TESTOBJ_CMP_OPLIST)
|
||||
#define M_OPL_deque_obj_t() DEQUE_OPLIST(deque_obj, TESTOBJ_CMP_OPLIST)
|
||||
DICT_DEF2(dict_obj, string_t, STRING_OPLIST, testobj_t, TESTOBJ_OPLIST)
|
||||
|
||||
#include "coverage.h"
|
||||
START_COVERAGE
|
||||
@@ -50,6 +53,7 @@ ALGO_DEF(algo_list, LIST_OPLIST(list_int))
|
||||
ALGO_DEF(algo_over, ILIST_OPLIST(ilist_over, M_POD_OPLIST))
|
||||
ALGO_DEF(algo_string, LIST_OPLIST(list_string, STRING_OPLIST))
|
||||
ALGO_DEF(algo_deque, deque_obj_t)
|
||||
ALGO_DEF(algo_dict, DICT_OPLIST(dict_obj, STRING_OPLIST, TESTOBJ_OPLIST))
|
||||
END_COVERAGE
|
||||
|
||||
int g_min, g_max, g_count;
|
||||
|
||||
+10
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -128,7 +128,15 @@ static void test_uint(void)
|
||||
assert(*array_uint_cget(v2, 500) == 0);
|
||||
assert(array_uint_size(v2) == 1005);
|
||||
assert(*array_uint_cget(v2, 1004) == 0);
|
||||
|
||||
array_uint_set_at(v2, 1004, 465487);
|
||||
array_uint_insert_v (v2, 500, 0);
|
||||
assert(array_uint_size(v2) == 1005);
|
||||
assert(*array_uint_cget(v2, 1004) == 465487);
|
||||
array_uint_insert_v (v2, 1004, 10000);
|
||||
assert(array_uint_size(v2) == 11005);
|
||||
assert(*array_uint_cget(v2, 11004) == 465487);
|
||||
array_uint_resize(v2, 1005);
|
||||
|
||||
array_uint_init(v);
|
||||
array_uint_swap(v,v2);
|
||||
assert(array_uint_size(v) == 1005);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
+72
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -407,6 +407,76 @@ static void test_queue(size_t n, int cpu_count, unsigned long long ref)
|
||||
queue_uint_clear(g_buff2);
|
||||
}
|
||||
|
||||
/********************************************************************************************/
|
||||
|
||||
static void test_spsc(void)
|
||||
{
|
||||
squeue_uint_t q;
|
||||
bool b;
|
||||
unsigned j;
|
||||
|
||||
squeue_uint_init(q, 256);
|
||||
|
||||
assert(squeue_uint_empty_p(q));
|
||||
|
||||
for(unsigned i = 0; i < 256; i++) {
|
||||
b = squeue_uint_push(q, i);
|
||||
assert(b);
|
||||
}
|
||||
assert(!squeue_uint_empty_p(q));
|
||||
assert(squeue_uint_full_p(q));
|
||||
assert(squeue_uint_size(q) == 256);
|
||||
assert(squeue_uint_capacity(q) == 256);
|
||||
|
||||
b = squeue_uint_push(q, 256);
|
||||
assert (!b);
|
||||
for(unsigned i = 0; i < 256; i++) {
|
||||
b = squeue_uint_pop(&j, q);
|
||||
assert(b);
|
||||
assert(j == i);
|
||||
}
|
||||
assert(squeue_uint_empty_p(q));
|
||||
assert(!squeue_uint_full_p(q));
|
||||
assert(squeue_uint_size(q) == 0);
|
||||
assert(squeue_uint_capacity(q) == 256);
|
||||
|
||||
for(unsigned i = 0; i < 256; i++) {
|
||||
b = squeue_uint_push(q, i);
|
||||
assert(b);
|
||||
}
|
||||
for(unsigned i = 256; i < 256+128; i++) {
|
||||
assert(squeue_uint_full_p(q));
|
||||
squeue_uint_push_force(q, i);
|
||||
}
|
||||
for(unsigned i = 128; i < 256+128; i++) {
|
||||
b = squeue_uint_pop(&j, q);
|
||||
assert(b);
|
||||
assert(j == i);
|
||||
}
|
||||
|
||||
unsigned tab[16];
|
||||
for(unsigned i = 0; i < 16; i++)
|
||||
tab[i] = i * i;
|
||||
for(unsigned i = 0; i < 15; i++) {
|
||||
j = squeue_uint_push_bulk(q, 16, tab);
|
||||
assert(j == 16);
|
||||
}
|
||||
b = squeue_uint_push(q, 1024);
|
||||
assert(b);
|
||||
j = squeue_uint_push_bulk(q, 16, tab);
|
||||
assert(j == 15);
|
||||
|
||||
for(unsigned i = 0; i < 16; i++)
|
||||
tab[i] = 0;
|
||||
j = squeue_uint_pop_bulk(16, tab, q);
|
||||
assert(j == 16);
|
||||
for(unsigned i = 0; i < 16; i++)
|
||||
assert( tab[i] == i * i);
|
||||
|
||||
squeue_uint_clear(q);
|
||||
}
|
||||
|
||||
|
||||
/********************************************************************************************/
|
||||
|
||||
int main(void)
|
||||
@@ -438,5 +508,6 @@ int main(void)
|
||||
test_no_thread();
|
||||
test_global_ishared();
|
||||
test_queue(1000000, 2, 2148371710223136ULL);
|
||||
test_spsc();
|
||||
exit(0);
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* M*LIB - Test for Concurrent memory pool allocator
|
||||
*
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* + Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* + Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "m-c-mempool.h"
|
||||
#include "m-mutex.h"
|
||||
|
||||
C_MEMPOOL_DEF(lf_mempool, int)
|
||||
|
||||
m_gc_t gc;
|
||||
|
||||
/**********************************************************************/
|
||||
|
||||
#define MAX_VALUE 100
|
||||
#define MAX_THREAD 4
|
||||
|
||||
lf_mempool_t g;
|
||||
|
||||
typedef struct {
|
||||
int step;
|
||||
int *ptr[MAX_VALUE];
|
||||
int value[MAX_VALUE];
|
||||
} p_t;
|
||||
|
||||
p_t g_tab[MAX_THREAD];
|
||||
|
||||
static void do_stuff(m_gc_tid_t id)
|
||||
{
|
||||
// Mix of local and more global allocation
|
||||
int *d[MAX_VALUE];
|
||||
for(int i = 0; i < MAX_VALUE; i++) {
|
||||
d[i] = lf_mempool_new(g, id);
|
||||
*d[i] = i*i;
|
||||
}
|
||||
switch (g_tab[id].step)
|
||||
{
|
||||
case 0:
|
||||
for(int i = 0; i < 50; i++) {
|
||||
g_tab[id].value[i] += i;
|
||||
g_tab[id].ptr[i] = lf_mempool_new(g, id);
|
||||
*g_tab[id].ptr[i] = g_tab[id].value[i];
|
||||
}
|
||||
g_tab[id].step = 1;
|
||||
break;
|
||||
case 1:
|
||||
for(int i = 50; i < MAX_VALUE; i++) {
|
||||
g_tab[id].value[i] += i;
|
||||
g_tab[id].ptr[i] = lf_mempool_new(g, id);
|
||||
*g_tab[id].ptr[i] = g_tab[id].value[i];
|
||||
}
|
||||
g_tab[id].step = 2;
|
||||
break;
|
||||
case 2:
|
||||
for(int i = 0; i < 50; i++) {
|
||||
assert(g_tab[id].ptr[i] != NULL);
|
||||
assert(*g_tab[id].ptr[i] == g_tab[id].value[i] );
|
||||
lf_mempool_del(g, g_tab[id].ptr[i], id);
|
||||
g_tab[id].ptr[i] = NULL;
|
||||
}
|
||||
g_tab[id].step = 3;
|
||||
break;
|
||||
case 3:
|
||||
for(int i = 50; i < MAX_VALUE; i++) {
|
||||
assert(g_tab[id].ptr[i] != NULL);
|
||||
assert(*g_tab[id].ptr[i] == g_tab[id].value[i] );
|
||||
lf_mempool_del(g, g_tab[id].ptr[i], id);
|
||||
g_tab[id].ptr[i] = NULL;
|
||||
}
|
||||
g_tab[id].step = 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
for(int i = 0; i < MAX_VALUE; i++) {
|
||||
assert (*d[i] == i*i);
|
||||
lf_mempool_del(g, d[i], id);
|
||||
}
|
||||
}
|
||||
|
||||
static void thread1(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
/* Attach this thread to the GC */
|
||||
m_gc_tid_t id = m_gc_attach_thread(gc);
|
||||
for(int n = 0; n < 100000; n ++) {
|
||||
/* Awake the thread: now we can alloc nodes */
|
||||
m_gc_awake(gc, id);
|
||||
/* Do some stuff */
|
||||
do_stuff(id);
|
||||
/* Sleep the thread. A Garbage Collect may occur if possible */
|
||||
m_gc_sleep(gc, id);
|
||||
}
|
||||
/* Detach the thread from the GC */
|
||||
m_gc_detach_thread(gc, id);
|
||||
}
|
||||
|
||||
static void thread2(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
m_gc_tid_t id = m_gc_attach_thread(gc);
|
||||
for(int n = 0; n < 10000; n ++) {
|
||||
m_gc_awake(gc, id);
|
||||
do_stuff(id);
|
||||
m_thread_sleep(1);
|
||||
m_gc_sleep(gc, id);
|
||||
}
|
||||
m_gc_detach_thread(gc, id);
|
||||
}
|
||||
|
||||
static void test(void)
|
||||
{
|
||||
/* First create the Garbage Collector */
|
||||
m_gc_init (gc, MAX_THREAD);
|
||||
/* Create a mempool and attach it to the Garbage Collector */
|
||||
lf_mempool_init(g, gc, 256, MAX_THREAD);
|
||||
|
||||
m_thread_t id[MAX_THREAD];
|
||||
for(int i = 0; i < MAX_THREAD; i+=2) {
|
||||
m_thread_create(id[i], thread1, NULL);
|
||||
m_thread_create(id[i+1], thread2, NULL);
|
||||
}
|
||||
for(int i = 0; i < MAX_THREAD; i++) {
|
||||
m_thread_join(id[i]);
|
||||
}
|
||||
/* Destroy the mempool */
|
||||
lf_mempool_clear(g);
|
||||
/* Destroy the Garbage Collector */
|
||||
m_gc_clear (gc);
|
||||
}
|
||||
|
||||
static void test2(void)
|
||||
{
|
||||
/* First create the Garbage Collector */
|
||||
m_gc_init (gc, MAX_THREAD);
|
||||
/* Create a mempool and attach it to the Garbage Collector */
|
||||
lf_mempool_init(g, gc, 256, MAX_THREAD);
|
||||
/* Create another mempool and attach it to the Garbage Collector */
|
||||
lf_mempool_t g2;
|
||||
lf_mempool_init(g2, gc, 256, MAX_THREAD);
|
||||
/* Register VLA */
|
||||
m_vlapool_t vla;
|
||||
m_vlapool_init(vla, gc);
|
||||
|
||||
/* Attach the thread */
|
||||
m_gc_tid_t id = m_gc_attach_thread(gc);
|
||||
/* Awake the thread: now we can alloc nodes */
|
||||
m_gc_awake(gc, id);
|
||||
|
||||
/* Test some allocation */
|
||||
void *ptr = m_vlapool_new(vla, id, sizeof (int));
|
||||
*(int*)ptr = 42;
|
||||
m_vlapool_del(vla, ptr, id);
|
||||
|
||||
int *pi = lf_mempool_new(g, id);
|
||||
*pi = 44;
|
||||
lf_mempool_del(g, pi, id);
|
||||
|
||||
int *pi2 = lf_mempool_new(g2, id);
|
||||
*pi2 = 46;
|
||||
lf_mempool_del(g2, pi2, id);
|
||||
|
||||
/* Test that the data remain accessible
|
||||
as it has only be logically deleted */
|
||||
assert (*(int*)ptr == 42);
|
||||
assert (*pi == 44);
|
||||
assert (*pi2 == 46);
|
||||
|
||||
/* Sleep the thread */
|
||||
m_gc_sleep(gc, id);
|
||||
|
||||
/* Detach the thread */
|
||||
m_gc_detach_thread(gc, id);
|
||||
|
||||
/* Clear everything */
|
||||
m_vlapool_clear(vla);
|
||||
lf_mempool_clear(g);
|
||||
lf_mempool_clear(g2);
|
||||
m_gc_clear(gc);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
test();
|
||||
test2();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -78,6 +78,27 @@ TUPLE_DEF2(my_tuple, (pool, string_pool_ts_t))
|
||||
#define MY_TUPLE_OPLIST TUPLE_OPLIST(my_tuple, STRING_POOL_TS_OPLIST)
|
||||
#define M_OPL_my_tuple_t() MY_TUPLE_OPLIST
|
||||
|
||||
ARRAY_DEF(key_list, string_t)
|
||||
#define M_OPL_key_list_t() ARRAY_OPLIST(key_list, STRING_OPLIST)
|
||||
CONCURRENT_DEF(key_list_ts, key_list_t)
|
||||
|
||||
CONCURRENT_RP_DEF(rparray1, array1_t, ARRAY_OPLIST(array1))
|
||||
|
||||
CONCURRENT_RP_DEF(rpdict1, dict1_t, DICT_OPLIST(dict1))
|
||||
|
||||
static inline bool int_oor_equal_p(int s, unsigned char n)
|
||||
{
|
||||
return s == -n;
|
||||
}
|
||||
static inline void int_oor_set(int *s, unsigned char n)
|
||||
{
|
||||
*s = -n;
|
||||
}
|
||||
#define INT_OA_OPLIST \
|
||||
M_OPEXTEND(M_DEFAULT_OPLIST, OOR_EQUAL(int_oor_equal_p), OOR_SET(API_2(int_oor_set)))
|
||||
DICT_OA_DEF2(dict3, int, INT_OA_OPLIST, int, M_DEFAULT_OPLIST)
|
||||
CONCURRENT_DEF(pdict3, dict3_t, DICT_OPLIST(dict3))
|
||||
|
||||
/********************************/
|
||||
parray1_t arr;
|
||||
|
||||
@@ -92,7 +113,7 @@ static void conso(void *p)
|
||||
}
|
||||
}
|
||||
|
||||
static void test(void)
|
||||
static void test_thread(void)
|
||||
{
|
||||
m_thread_t idx;
|
||||
parray1_init(arr);
|
||||
@@ -118,6 +139,7 @@ static void test_basic(void)
|
||||
bool b = pdict1_get_blocking(&z, dict, 2, false);
|
||||
assert (b);
|
||||
assert (z == 3);
|
||||
assert (pdict1_size(dict) == 1);
|
||||
pdict1_clear(dict);
|
||||
|
||||
M_LET(str, string_t)
|
||||
@@ -132,8 +154,55 @@ static void test_basic(void)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
rparray1_t rarr;
|
||||
|
||||
static void conso_rp(void *p)
|
||||
{
|
||||
assert (p == NULL);
|
||||
for(int i = 0; i < 1000; i++) {
|
||||
int j;
|
||||
bool b = rparray1_pop_blocking(&j, rarr, true);
|
||||
assert (b);
|
||||
assert (j == i);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_rp_thread(void)
|
||||
{
|
||||
m_thread_t idx;
|
||||
rparray1_init(rarr);
|
||||
m_thread_create (idx, conso_rp, NULL);
|
||||
m_thread_sleep(1000);
|
||||
for(int i = 0; i < 1000; i++) {
|
||||
rparray1_push (rarr, i);
|
||||
while (!rparray1_empty_p(rarr));
|
||||
}
|
||||
m_thread_join(idx);
|
||||
rparray1_clear(rarr);
|
||||
}
|
||||
|
||||
static void test_rp_basic(void)
|
||||
{
|
||||
rpdict1_t dict;
|
||||
rpdict1_init(dict);
|
||||
int z;
|
||||
rpdict1_set_at(dict, 2, 3);
|
||||
rpdict1_get_copy(&z, dict, 2);
|
||||
assert (z == 3);
|
||||
z = 1;
|
||||
bool b = rpdict1_get_blocking(&z, dict, 2, false);
|
||||
assert (b);
|
||||
assert (z == 3);
|
||||
assert (rpdict1_size(dict) == 1);
|
||||
rpdict1_clear(dict);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
test_basic();
|
||||
test();
|
||||
test_rp_basic();
|
||||
test_thread();
|
||||
test_rp_thread();
|
||||
exit(0);
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
+49
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -41,6 +41,11 @@ DICT_SET_DEF(dict_setstr, string_t, STRING_OPLIST)
|
||||
DICT_DEF2(dict_int, int, M_DEFAULT_OPLIST, int, M_DEFAULT_OPLIST)
|
||||
DICT_DEF2(dict_mpz, string_t, STRING_OPLIST, testobj_t, TESTOBJ_OPLIST)
|
||||
|
||||
BOUNDED_STRING_DEF(symbol, 15)
|
||||
DICT_OA_DEF2(dict_oa_str, symbol_t, BOUNDED_STRING_OPLIST(symbol), int, M_DEFAULT_OPLIST)
|
||||
|
||||
DICT_OA_DEF2(dict_oa_bstr, string_t, STRING_OPLIST, int, M_DEFAULT_OPLIST)
|
||||
|
||||
/* Helper structure */
|
||||
ARRAY_DEF(array_string, string_t, STRING_OPLIST)
|
||||
array_string_t v_str;
|
||||
@@ -463,6 +468,47 @@ static void test_it_oa(void)
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
test_oa_str1(void)
|
||||
{
|
||||
symbol_t s;
|
||||
int *p;
|
||||
dict_oa_str_t dict;
|
||||
|
||||
symbol_init(s);
|
||||
dict_oa_str_init(dict);
|
||||
|
||||
dict_oa_str_set_at(dict, BOUNDED_STRING_CTE(symbol, "x"), 1);
|
||||
dict_oa_str_set_at(dict, BOUNDED_STRING_CTE(symbol, "y"), 2);
|
||||
dict_oa_str_set_at(dict, BOUNDED_STRING_CTE(symbol, "z"), 3);
|
||||
|
||||
p = dict_oa_str_get_at(dict, BOUNDED_STRING_CTE(symbol, "x"));
|
||||
assert(p != NULL);
|
||||
assert(*p == 1);
|
||||
|
||||
dict_oa_str_clear(dict);
|
||||
symbol_clear(s);
|
||||
}
|
||||
|
||||
static void
|
||||
test_oa_str2(void)
|
||||
{
|
||||
int *p;
|
||||
dict_oa_bstr_t dict;
|
||||
|
||||
dict_oa_bstr_init(dict);
|
||||
|
||||
dict_oa_bstr_set_at(dict, STRING_CTE("x"), 1);
|
||||
dict_oa_bstr_set_at(dict, STRING_CTE("y"), 2);
|
||||
dict_oa_bstr_set_at(dict, STRING_CTE("z"), 3);
|
||||
|
||||
p = dict_oa_bstr_get_at(dict, STRING_CTE("x"));
|
||||
assert(p != NULL);
|
||||
assert(*p == 1);
|
||||
|
||||
dict_oa_bstr_clear(dict);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
test1();
|
||||
@@ -472,5 +518,7 @@ int main(void)
|
||||
test_oa();
|
||||
test_init_oa();
|
||||
test_it_oa();
|
||||
test_oa_str1();
|
||||
test_oa_str2();
|
||||
exit(0);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* MLIB - TEST module
|
||||
*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
+47
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -91,12 +91,17 @@ static void test_utf8_basic(void)
|
||||
static void test_utf8_it(void)
|
||||
{
|
||||
string_t s;
|
||||
string_it_t it;
|
||||
string_init(s);
|
||||
|
||||
for(string_it(it, s) ; !string_end_p(it); string_next(it)) {
|
||||
assert(0); // Shall not be called
|
||||
}
|
||||
|
||||
const unsigned int tab[] = { 45, 1458, 25623, 129, 24 };
|
||||
for(int i = 0; i < 5; i++)
|
||||
string_push_u (s, tab[i]);
|
||||
assert (string_length_u(s) == 5);
|
||||
string_it_t it;
|
||||
int i = 0;
|
||||
for(string_it(it, s) ; !string_end_p(it); string_next(it), i++) {
|
||||
assert (i < 5);
|
||||
@@ -384,6 +389,9 @@ static void test0(void)
|
||||
string_init (s1);
|
||||
string_init (s2);
|
||||
|
||||
size_t h1 = string_hash(s1);
|
||||
assert (h1 != 0);
|
||||
|
||||
string_printf (s1, "Hello %d worlds. How do you do? I'm fine. Thank you! The weather is bad today. I should had brought my umbrella. Oh! You can lend me one! Thank you very much! No really thank you. I wouldn't be able to get in time for my job.", 2);
|
||||
assert (string_equal_str_p(s1, "Hello 2 worlds. How do you do? I'm fine. Thank you! The weather is bad today. I should had brought my umbrella. Oh! You can lend me one! Thank you very much! No really thank you. I wouldn't be able to get in time for my job."));
|
||||
|
||||
@@ -447,7 +455,7 @@ static void test0(void)
|
||||
string_set_str (s1, "");
|
||||
assert (string_capacity(s1) > 0);
|
||||
string_reserve (s1, 0);
|
||||
assert (string_capacity(s1) == 0);
|
||||
assert (string_capacity(s1) == sizeof (string_heap_t) - 1);
|
||||
|
||||
string_set_str(s1, "HELLO XXX!");
|
||||
string_replace_at(s1, 6, 3, "World");
|
||||
@@ -518,6 +526,41 @@ static void test_bounded1(void)
|
||||
string16_clear(s);
|
||||
}
|
||||
|
||||
static void test_bounded_io(void)
|
||||
{
|
||||
string16_t s, d;
|
||||
bool b;
|
||||
FILE *f;
|
||||
|
||||
string16_init(s);
|
||||
string16_init(d);
|
||||
|
||||
string16_set_str(s, "Hello \"world\"");
|
||||
f = fopen ("a-mstring.dat", "wt");
|
||||
assert (f != NULL);
|
||||
string16_out_str (f, s);
|
||||
fclose (f);
|
||||
f = fopen("a-mstring.dat", "rt");
|
||||
assert (f != NULL);
|
||||
b = string16_in_str(d, f);
|
||||
assert(b);
|
||||
fclose(f);
|
||||
assert (string16_equal_p(s, d));
|
||||
|
||||
const char *end;
|
||||
string_t str;
|
||||
string_init(str);
|
||||
string16_get_str(str, s, false);
|
||||
assert(string_equal_str_p(str, "\"Hello \\\"world\\\"\""));
|
||||
b = string16_parse_str(d, string_get_cstr(str), &end);
|
||||
assert (b);
|
||||
assert (string16_equal_p(s, d));
|
||||
|
||||
string_clear(str);
|
||||
string16_clear(s);
|
||||
string16_clear(d);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
test0();
|
||||
@@ -525,5 +568,6 @@ int main(void)
|
||||
test_utf8_basic();
|
||||
test_utf8_it();
|
||||
test_bounded1();
|
||||
test_bounded_io();
|
||||
exit(0);
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -76,7 +76,7 @@ static void test1bis(void)
|
||||
assert (atomic_load(&resetFunc_called) == true || workeri_get_cpu_count() == 1);
|
||||
}
|
||||
|
||||
#if defined(__GNUC__) && (!defined(__clang__) || WORKER_USE_CLANG_BLOCK || WORKER_USE_CPP_FUNCTION)
|
||||
#if defined(__GNUC__) && (!defined(__clang__) || (defined(WORKER_USE_CLANG_BLOCK) && WORKER_USE_CLANG_BLOCK) || (defined(WORKER_USE_CPP_FUNCTION) && WORKER_USE_CPP_FUNCTION))
|
||||
|
||||
/* The macro version will generate warnings about shadow variables.
|
||||
There is no way to avoid this. */
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Patrick Pelissier
|
||||
* Copyright (c) 2017-2019, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
Reference in new issue
Block a user