/* Copyright (c) 2006, Google Inc.
* 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.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT
* OWNER OR 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.
*
* ---
* Author: Sanjay Ghemawat
*/
#include "config.h"
#include <time.h> /* For nanosleep() */
#include <sched.h> /* For sched_yield() */
#ifdef HAVE_UNISTD_H
#include <unistd.h> /* For nanosleep() on Windows, read() */
#endif
#if defined(__MACH__) && defined(__APPLE__)
#include <sys/types.h>
#include <sys/sysctl.h> /* how we figure out numcpu's on OS X */
#endif
#include <fcntl.h> /* for open(), O_RDONLY */
#include <string.h> /* for strncmp */
#include <errno.h>
#include "base/spinlock.h"
static int adaptive_spin_count = 0;
static int num_cpus = -1;
// It's important this not call malloc! -- it is called at global-construct
// time, before we've set up all our proper malloc hooks and such.
static int NumCPUs() {
if (num_cpus > 0)
return num_cpus; // value is cached
#if defined(__MACH__) && defined(__APPLE__)
int cpus;
size_t size = sizeof(cpus);
int numcpus_name[] = { CTL_HW, HW_NCPU };
if (::sysctl(numcpus_name, arraysize(numcpus_name), &cpus, &size, 0, 0) == 0
&& (size == sizeof(cpus)))
num_cpus = cpus;
else
num_cpus = 1; // conservative assumption
return num_cpus;
#else /* hope the information is in /proc */
const char* pname = "/proc/cpuinfo";
int fd = open(pname, O_RDONLY);
if (fd == -1) {
num_cpus = 1; // conservative assumption; TODO: do better
return num_cpus;
}
char line[1024];
int chars_read;
num_cpus = 0;
do { // a line at a time
chars_read = 0;
char* linepos = line;
while (linepos - line < sizeof(line)-1 &&
(chars_read=read(fd, linepos, 1)) == 1 &&
*linepos != '\n') // read one line
linepos++;
*linepos = '\0'; // terminate the line at the \n
if (strncmp(line, "processor ", sizeof("processor ")-1) == 0)
num_cpus++;
} while (chars_read > 0); // stop when we get to EOF
close(fd);
if (num_cpus == 0)
num_cpus = 1; // conservative assumption
return num_cpus;
#endif
}
struct SpinLock_InitHelper {
SpinLock_InitHelper() {
// On multi-cpu machines, spin for longer before yielding
// the processor or sleeping. Reduces idle time significantly.
if (NumCPUs() > 1) {
adaptive_spin_count = 1000;
}
}
};
// Hook into global constructor execution:
// We do not do adaptive spinning before that,
// but nothing lock-intensive should be going on at that time.
static SpinLock_InitHelper init_helper;
void SpinLock::SlowLock() {
int saved_errno = errno; // save and restore errno for signal safety
int c = adaptive_spin_count;
// Spin a few times in the hope that the lock holder releases the lock
while ((c > 0) && (lockword_ != 0)) {
c--;
}
if (lockword_ == 1) {
sched_yield(); // Spinning failed. Let's try to be gentle.
}
while (Acquire_CompareAndSwap(&lockword_, 0, 1) != 0) {
// This code was adapted from the ptmalloc2 implementation of
// spinlocks which would sched_yield() upto 50 times before
// sleeping once for a few milliseconds. Mike Burrows suggested
// just doing one sched_yield() outside the loop and always
// sleeping after that. This change helped a great deal on the
// performance of spinlocks under high contention. A test program
// with 10 threads on a dual Xeon (four virtual processors) went
// from taking 30 seconds to 16 seconds.
// Sleep for a few milliseconds
struct timespec tm;
tm.tv_sec = 0;
tm.tv_nsec = 2000001;
nanosleep(&tm, NULL);
}
errno = saved_errno;
}
syntax highlighted by Code2HTML, v. 0.9.1