pthread stack treatment overhaul for application-provided stacks, etc.
authorRich Felker <dalias@aerifal.cx>
Sat, 2 Feb 2013 03:10:40 +0000 (22:10 -0500)
committerRich Felker <dalias@aerifal.cx>
Sat, 2 Feb 2013 03:10:40 +0000 (22:10 -0500)
the main goal of these changes is to address the case where an
application provides a stack of size N, but TLS has size M that's a
significant portion of the size N (or even larger than N), thus giving
the application less stack space than it expected or no stack at all!

the new strategy pthread_create now uses is to only put TLS on the
application-provided stack if TLS is smaller than 1/8 of the stack
size or 2k, whichever is smaller. this ensures that the application
always has "close enough" to what it requested, and the threshold is
chosen heuristically to make sure "sane" amounts of TLS still end up
in the application-provided stack.

if TLS does not fit the above criteria, pthread_create uses mmap to
obtain space for TLS, but still uses the application-provided stack
for actual call frame stack. this is to avoid wasting memory, and for
the sake of supporting ugly hacks like garbage collection based on
assumptions that the implementation will use the provided stack range.

in order for the above heuristics to ever succeed, the amount of TLS
space wasted on POSIX TSD (pthread_key_create based) needed to be
reduced. otherwise, these changes would preclude any use of
pthread_create without mmap, which would have serious memory usage and
performance costs for applications trying to create huge numbers of
threads using pre-allocated stack space. the new value of
PTHREAD_KEYS_MAX is the minimum allowed by POSIX, 128. this should
still be plenty more than real-world applications need, especially now
that C11/gcc-style TLS is now supported in musl, and most apps and
libraries choose to use that instead of POSIX TSD when available.

at the same time, PTHREAD_STACK_MIN has been decreased. it was
originally set to PAGE_SIZE back when there was no support for TLS or
application-provided stacks, and requests smaller than a whole page
did not make sense. now, there are two good reasons to support
requests smaller than a page: (1) applications could provide
pre-allocated stacks smaller than a page, and (2) with smaller stack
sizes, stack+TLS+TSD can all fit in one page, making it possible for
applications which need huge numbers of threads with minimal stack
needs to allocate exactly one page per thread. the new value of
PTHREAD_STACK_MIN, 2k, is aligned with the minimum size for
sigaltstack.

include/limits.h
src/thread/pthread_attr_setstack.c
src/thread/pthread_attr_setstacksize.c
src/thread/pthread_create.c

index ae05f1a..f8314a5 100644 (file)
@@ -58,8 +58,8 @@
 
 /* Implementation choices... */
 
-#define PTHREAD_KEYS_MAX  1024
-#define PTHREAD_STACK_MIN PAGE_SIZE
+#define PTHREAD_KEYS_MAX 128
+#define PTHREAD_STACK_MIN 2048
 #define PTHREAD_DESTRUCTOR_ITERATIONS 4
 #define SEM_VALUE_MAX 0x7fffffff
 #define SEM_NSEMS_MAX 256
index c51ad34..61707a3 100644 (file)
@@ -1,13 +1,8 @@
 #include "pthread_impl.h"
 
-/* pthread_key_create.c overrides this */
-static const size_t dummy = 0;
-weak_alias(dummy, __pthread_tsd_size);
-
 int pthread_attr_setstack(pthread_attr_t *a, void *addr, size_t size)
 {
-       if (size-PTHREAD_STACK_MIN-__pthread_tsd_size > SIZE_MAX/4)
-               return EINVAL;
+       if (size-PTHREAD_STACK_MIN > SIZE_MAX/4) return EINVAL;
        a->_a_stackaddr = (size_t)addr + size;
        a->_a_stacksize = size - DEFAULT_STACK_SIZE;
        return 0;
index e4de520..09d3fda 100644 (file)
@@ -3,6 +3,7 @@
 int pthread_attr_setstacksize(pthread_attr_t *a, size_t size)
 {
        if (size-PTHREAD_STACK_MIN > SIZE_MAX/4) return EINVAL;
+       a->_a_stackaddr = 0;
        a->_a_stacksize = size - DEFAULT_STACK_SIZE;
        return 0;
 }
index a65e88e..4c1deca 100644 (file)
@@ -96,15 +96,15 @@ static void init_file_lock(FILE *f)
 
 void *__copy_tls(unsigned char *);
 
-int pthread_create(pthread_t *restrict res, const pthread_attr_t *restrict attr, void *(*entry)(void *), void *restrict arg)
+int pthread_create(pthread_t *restrict res, const pthread_attr_t *restrict attrp, void *(*entry)(void *), void *restrict arg)
 {
        int ret;
-       size_t size = DEFAULT_STACK_SIZE + DEFAULT_GUARD_SIZE;
-       size_t guard = DEFAULT_GUARD_SIZE;
+       size_t size, guard;
        struct pthread *self = pthread_self(), *new;
-       unsigned char *map, *stack, *tsd;
+       unsigned char *map = 0, *stack = 0, *tsd = 0;
        unsigned flags = 0x7d8f00;
        int do_sched = 0;
+       pthread_attr_t attr = {0};
 
        if (!self) return ENOSYS;
        if (!libc.threaded) {
@@ -115,19 +115,31 @@ int pthread_create(pthread_t *restrict res, const pthread_attr_t *restrict attr,
                init_file_lock(__stderr_used);
                libc.threaded = 1;
        }
+       if (attrp) attr = *attrp;
 
        __acquire_ptc();
 
-       if (attr && attr->_a_stackaddr) {
-               map = 0;
-               tsd = (void *)(attr->_a_stackaddr-__pthread_tsd_size & -16);
-       } else {
-               if (attr) {
-                       guard = ROUND(attr->_a_guardsize + DEFAULT_GUARD_SIZE);
-                       size = guard + ROUND(attr->_a_stacksize
-                               + DEFAULT_STACK_SIZE + libc.tls_size);
+       if (attr._a_stackaddr) {
+               size_t need = libc.tls_size + __pthread_tsd_size;
+               size = attr._a_stacksize + DEFAULT_STACK_SIZE;
+               stack = (void *)(attr._a_stackaddr & -16);
+               /* Use application-provided stack for TLS only when
+                * it does not take more than ~12% or 2k of the
+                * application's stack space. */
+               if (need < size/8 && need < 2048) {
+                       tsd = stack - __pthread_tsd_size;
+                       stack = tsd - libc.tls_size;
+               } else {
+                       size = ROUND(need);
+                       guard = 0;
                }
-               size += __pthread_tsd_size;
+       } else {
+               guard = ROUND(DEFAULT_GUARD_SIZE + attr._a_guardsize);
+               size = guard + ROUND(DEFAULT_STACK_SIZE + attr._a_stacksize
+                       + libc.tls_size +  __pthread_tsd_size);
+       }
+
+       if (!tsd) {
                if (guard) {
                        map = mmap(0, size, PROT_NONE, MAP_PRIVATE|MAP_ANON, -1, 0);
                        if (map == MAP_FAILED) return EAGAIN;
@@ -140,8 +152,10 @@ int pthread_create(pthread_t *restrict res, const pthread_attr_t *restrict attr,
                        if (map == MAP_FAILED) return EAGAIN;
                }
                tsd = map + size - __pthread_tsd_size;
+               if (!stack) stack = tsd - libc.tls_size;
        }
-       new = __copy_tls(stack = tsd - libc.tls_size);
+
+       new = __copy_tls(tsd - libc.tls_size);
        new->map_base = map;
        new->map_size = size;
        new->pid = self->pid;
@@ -150,11 +164,11 @@ int pthread_create(pthread_t *restrict res, const pthread_attr_t *restrict attr,
        new->start_arg = arg;
        new->self = new;
        new->tsd = (void *)tsd;
-       if (attr && attr->_a_detach) {
+       if (attr._a_detach) {
                new->detached = 1;
                flags -= 0x200000;
        }
-       if (attr && attr->_a_sched) {
+       if (attr._a_sched) {
                do_sched = new->startlock[0] = 1;
                __syscall(SYS_rt_sigprocmask, SIG_BLOCK,
                        SIGALL_SET, self->sigmask, __SYSCALL_SSLEN);
@@ -180,7 +194,7 @@ int pthread_create(pthread_t *restrict res, const pthread_attr_t *restrict attr,
 
        if (do_sched) {
                ret = __syscall(SYS_sched_setscheduler, new->tid,
-                       attr->_a_policy, &attr->_a_prio);
+                       attr._a_policy, &attr._a_prio);
                a_store(new->startlock, ret<0 ? 2 : 0);
                __wake(new->startlock, 1, 1);
                if (ret < 0) return -ret;