C
Here's a new, imprecise approach:
/* ncpu.c */
#include <stdio.h>
#include <pthread.h>
#include <signal.h>
pthread_mutex_t count_mutex = PTHREAD_MUTEX_INITIALIZER;
int count = 1; /* start at 1 for the main() thread */
static void *inc_thread (void *arg) {
/* Woohoo - now we're no longer languishing on the ready queue -
* We're actually running on a real core. Atomically bump the counter. */
pthread_mutex_lock(&count_mutex); {
count++;
} pthread_mutex_unlock(&count_mutex);
/* keep this core spinning so another thread doesn't get it */
while (1);
return NULL;
}
#define MAX_CORE 64
int main (int argc, char **argv) {
pthread_t tid_array[MAX_CORE];
int i;
/* create a bunch of threads */
for (i = 0; i < MAX_CORE; i++) {
pthread_create(&tid_array[i], NULL, inc_thread, NULL);
}
/* cancel all those threads, regardless of whether they ran or not */
for (i = 0; i < MAX_CORE; i++) {
pthread_cancel(tid_array[i]);
}
printf("Your core count is approximately %d\n", count);
}
Build with LDFLAGS=-pthread make ncpu
.
This works some of the time on Ubuntu 14.04. I have tested with 1, 2 and 4 virtual cores in a VM.
This kicks off 64 threads, and then (almost) immediately cancels all those threads. pthread_create()
will put the new thread in a ready queue, but won't necessarily jump into the thread start routine immediately. When the threads get to run depends on core availability at that time.
For example if there are 4 cores, then 3 of the threads can potentially start running immediately. Each thread atomically increments a counter then spins for a bit until it is cancelled. Beyond the third thread, the rest of the threads will still be sitting in the ready queue when the main thread cancels them, and so they won't get to increment the counter.
This is certainly not very precise, but its only terribly wrong some of the time. Here are some runs with 1, 2 and 4 virtual cores:
1 core:
$ for i in {1..10}; do ./ncpu; done
Your core count is approximately 65
Your core count is approximately 1
Your core count is approximately 1
Your core count is approximately 15
Your core count is approximately 1
Your core count is approximately 19
Your core count is approximately 1
Your core count is approximately 1
Your core count is approximately 42
Your core count is approximately 1
$
2 cores:
$ for i in {1..10}; do ./ncpu; done
Your core count is approximately 2
Your core count is approximately 2
Your core count is approximately 2
Your core count is approximately 2
Your core count is approximately 2
Your core count is approximately 2
Your core count is approximately 3
Your core count is approximately 3
Your core count is approximately 2
Your core count is approximately 2
$
4 cores:
$ for i in {1..10}; do ./ncpu; done
Your core count is approximately 4
Your core count is approximately 5
Your core count is approximately 4
Your core count is approximately 4
Your core count is approximately 4
Your core count is approximately 4
Your core count is approximately 4
Your core count is approximately 4
Your core count is approximately 4
Your core count is approximately 4
$
My previous answer:
Here's a start, though it only distinguishes between exactly one core and more than one core:
/* 1cpu.c */
#include <stdio.h>
#include <pthread.h>
int c = 0;
static void *inc_thread (void *arg) {
int i;
for (i = 0; i < 10000000; i++) c++;
return NULL;
}
static void *dec_thread (void *arg) {
int i;
for (i = 0; i < 10000000; i++) c--;
return NULL;
}
int main (int argc, char **argv) {
pthread_t inc_tid, dec_tid;
pthread_create(&inc_tid, NULL, inc_thread, NULL);
pthread_create(&dec_tid, NULL, dec_thread, NULL);
pthread_join(inc_tid, NULL);
pthread_join(dec_tid, NULL);
if (c) {
printf("c=%d, More than one CPU\n", c);
} else {
printf("c=%d, Only one CPU\n", c);
}
}
Build with LDFLAGS=-pthread make 1cpu
.
This is a nice demonstration of how normal ++
and --
are not atomic operations. In the single-core situation, inc_thread()
and dec_thread()
can never run concurrently, so the global counter c
will always be incremented and decremented exactly 10 million times and thus end up as 0. However with more than one core, these two threads will likely end up on different cores, and thus colliding with their operations on the global counter. As we know, these operations have to load, [in|de]crement, then save each time, so threads can end up operating on stale data as they go through these operations. Therefore over 10 million runs we will likely end up with a different number of effective increment vs decrement operations and the counter will end up non-zero.
This method seems to be fairly accurate, and works on OSX as well as Linux.
int num_cores() { return 1; }
- only works on certain devices. \$\endgroup\$def num_cores(): return random.choice([1,2,4,8])
- works with about 25% accuracy for most consumer devices. "Your solution needs not to be precise. It should not be terribly wrong all the time though." \$\endgroup\$