blob: a00a3dcfa811e510b1145acf79ce195615ab492b [file] [log] [blame]
/*
* smpalloc.c: simple memory allocator intended to demonstrate
* the parallel-fastpath concurrency design.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
* Copyright (c) 2006-2019 Paul E. McKenney, IBM Corporation.
* Copyright (c) 2019 Paul E. McKenney, Facebook.
*/
#include "../api.h"
//\begin{snippet}[labelbase=ln:SMPdesign:smpalloc:data_struct,commandchars=\\\@\$]
#define TARGET_POOL_SIZE 3
#define GLOBAL_POOL_SIZE 40
#ifndef FCV_SNIPPET
struct memblock {
char *bytes[CACHE_LINE_SIZE];
} memblocks[GLOBAL_POOL_SIZE];
#endif /* FCV_SNIPPET */
//\fcvexclude
struct globalmempool {
spinlock_t mutex;
int cur;
struct memblock *pool[GLOBAL_POOL_SIZE];
} globalmem;
struct perthreadmempool {
int cur;
struct memblock *pool[2 * TARGET_POOL_SIZE];
};
DEFINE_PER_THREAD(struct perthreadmempool, perthreadmem);
//\end{snippet}
//\begin{snippet}[labelbase=ln:SMPdesign:smpalloc:alloc,commandchars=\\\@\$]
struct memblock *memblock_alloc(void)
{
int i;
struct memblock *p;
struct perthreadmempool *pcpp;
pcpp = &__get_thread_var(perthreadmem); //\lnlbl{pick}
if (pcpp->cur < 0) { //\lnlbl{chk:empty}
spin_lock(&globalmem.mutex); //\lnlbl{ack}
for (i = 0; i < TARGET_POOL_SIZE && //\lnlbl{loop:b}
globalmem.cur >= 0; i++) {
pcpp->pool[i] = globalmem.pool[globalmem.cur];
globalmem.pool[globalmem.cur--] = NULL;
} //\lnlbl{loop:e}
pcpp->cur = i - 1; //\lnlbl{set}
spin_unlock(&globalmem.mutex); //\lnlbl{rel}
}
if (pcpp->cur >= 0) { //\lnlbl{chk:notempty}
p = pcpp->pool[pcpp->cur]; //\lnlbl{rem:b}
pcpp->pool[pcpp->cur--] = NULL;
return p; //\lnlbl{rem:e}
}
return NULL; //\lnlbl{ret:NULL}
}
//\end{snippet}
//\begin{snippet}[labelbase=ln:SMPdesign:smpalloc:free,commandchars=\\\@\$]
void memblock_free(struct memblock *p)
{
int i;
struct perthreadmempool *pcpp;
pcpp = &__get_thread_var(perthreadmem); //\lnlbl{get}
if (pcpp->cur >= 2 * TARGET_POOL_SIZE - 1) { //\lnlbl{chk:full}
spin_lock(&globalmem.mutex); //\lnlbl{acq}
for (i = pcpp->cur; i >= TARGET_POOL_SIZE; i--) {//\lnlbl{loop:b}
globalmem.pool[++globalmem.cur] = pcpp->pool[i];
pcpp->pool[i] = NULL;
} //\lnlbl{loop:e}
pcpp->cur = i; //\lnlbl{set}
spin_unlock(&globalmem.mutex); //\lnlbl{rel}
} //\lnlbl{empty:e}
pcpp->pool[++pcpp->cur] = p; //\lnlbl{place}
}
//\end{snippet}
void initpools(void)
{
int i;
int j;
for (i = 0; i < NR_THREADS; i++) {
per_thread(perthreadmem, i).cur = -1;
for (j = 0; j < 2 * TARGET_POOL_SIZE; j++) {
per_thread(perthreadmem, i).pool[j] = NULL;
}
}
spin_lock_init(&globalmem.mutex);
globalmem.cur = -1;
for (i = 0; i < GLOBAL_POOL_SIZE; i++) {
memblock_free(&memblocks[i]);
}
}
#ifdef TEST
int goflag;
DEFINE_PER_THREAD(long, results);
DEFINE_PER_THREAD(long, failures);
#define MAX_RUN 100
int memblocks_available(void)
{
int i;
int sum = globalmem.cur + 1;
for_each_thread(i)
sum += per_thread(perthreadmem, i).cur + 1;
return sum;
}
void *memblock_test(void *arg)
{
long cnt = 0;
long cntfail = 0;
int i;
int runlength = (intptr_t)arg;
struct memblock *p[MAX_RUN];
if (runlength > MAX_RUN)
runlength = MAX_RUN;
while (goflag) {
for (i = 0; i < runlength; i++)
p[i] = memblock_alloc();
for (i = 0; i < runlength; i++) {
if (p[i] == NULL) {
cntfail++;
} else {
memblock_free(p[i]);
cnt++;
}
}
}
__get_thread_var(results) += cnt;
__get_thread_var(failures) += cntfail;
return NULL;
}
void usage(char *progname)
{
fprintf(stderr,
"Usage: %s [ #threads [ alloc runlength ] ]\n", progname);
exit(EXIT_FAILURE);
}
int main(int argc, char *argv[])
{
int i;
long long nc;
long long nf;
int nkids = 1;
int runlength = 1;
int totbefore;
smp_init();
initpools();
if (argc > 1) {
nkids = strtoul(argv[1], NULL, 0);
if (nkids > NR_THREADS) {
fprintf(stderr, "nkids = %d too large, max = %d\n",
nkids, NR_THREADS);
usage(argv[0]);
}
}
if (argc > 2) {
runlength = strtoul(argv[2], NULL, 0);
if (runlength > MAX_RUN) {
fprintf(stderr, "nkids = %d too large, max = %d\n",
runlength, MAX_RUN);
usage(argv[0]);
}
}
printf("%d %d ", nkids, runlength);
init_per_thread(results, 0L);
init_per_thread(failures, 0L);
totbefore = memblocks_available();
goflag = 1;
for (i = 0; i < nkids; i++)
create_thread(memblock_test, (void *)(intptr_t)runlength);
sleep(1);
goflag = 0;
wait_all_threads();
nf = nc = 0;
for (i = 0; i < NR_THREADS; i++) {
nc += per_thread(results, i);
nf += per_thread(failures, i);
}
printf("a/f: %Ld fail: %Ld\n", nc, nf);
if (memblocks_available() != totbefore) {
printf("memblocks: before: %d after: %d\n",
totbefore, memblocks_available());
}
exit(EXIT_SUCCESS);
}
#endif /* #ifdef TEST */