Tôi đã hỏi một câu hỏi trước đó để thử và cách ly nguồn tăng sử dụng CPU khi chuyển một ứng dụng từ RHEL 5 sang RHEL 6. Phân tích mà tôi đã làm cho điều đó dường như chỉ ra rằng nó được gây ra bởi CFS trong kernel. Tôi đã viết một ứng dụng thử nghiệm để thử và xác minh xem đây có phải là trường hợp không (ứng dụng thử nghiệm ban đầu được gỡ bỏ để phù hợp với giới hạn kích thước, nhưng vẫn có sẵn trong git repo .
Tôi đã biên dịch nó bằng lệnh sau trên RHEL 5:
cc test_select_work.c -O2 -DSLEEP_TYPE=0 -Wall -Wextra -lm -lpthread -o test_select_work
Sau đó tôi đã chơi với các tham số cho đến khi thời gian thực hiện trên mỗi lần lặp là khoảng 1 ms trên Dell Precision m6500.
Tôi đã nhận được kết quả như sau trên RHEL 5:
./test_select_work 1000 10000 300 4
time_per_iteration: min: 911.5 us avg: 913.7 us max: 917.1 us stddev: 2.4 us
./test_select_work 1000 10000 300 8
time_per_iteration: min: 1802.6 us avg: 1803.9 us max: 1809.1 us stddev: 2.1 us
./test_select_work 1000 10000 300 40
time_per_iteration: min: 7580.4 us avg: 8567.3 us max: 9022.0 us stddev: 299.6 us
Và như sau trên RHEL 6:
./test_select_work 1000 10000 300 4
time_per_iteration: min: 914.6 us avg: 975.7 us max: 1034.5 us stddev: 50.0 us
./test_select_work 1000 10000 300 8
time_per_iteration: min: 1683.9 us avg: 1771.8 us max: 1810.8 us stddev: 43.4 us
./test_select_work 1000 10000 300 40
time_per_iteration: min: 7997.1 us avg: 8709.1 us max: 9061.8 us stddev: 310.0 us
Trên cả hai phiên bản, những kết quả này là về những gì tôi mong đợi với lượng thời gian trung bình cho mỗi lần lặp tỷ lệ tương đối tuyến tính. Sau đó tôi đã biên dịch lại -DSLEEP_TYPE=1
và nhận được các kết quả sau trên RHEL 5:
./test_select_work 1000 10000 300 4
time_per_iteration: min: 1803.3 us avg: 1902.8 us max: 2001.5 us stddev: 113.8 us
./test_select_work 1000 10000 300 8
time_per_iteration: min: 1997.1 us avg: 2002.0 us max: 2010.8 us stddev: 5.0 us
./test_select_work 1000 10000 300 40
time_per_iteration: min: 6958.4 us avg: 8397.9 us max: 9423.7 us stddev: 619.7 us
Và các kết quả sau đây trên RHEL 6:
./test_select_work 1000 10000 300 4
time_per_iteration: min: 2107.1 us avg: 2143.1 us max: 2177.7 us stddev: 30.3 us
./test_select_work 1000 10000 300 8
time_per_iteration: min: 2903.3 us avg: 2903.8 us max: 2904.3 us stddev: 0.3 us
./test_select_work 1000 10000 300 40
time_per_iteration: min: 8877.7.1 us avg: 9016.3 us max: 9112.6 us stddev: 62.9 us
Trên RHEL 5, kết quả đạt được về những gì tôi mong đợi (4 luồng mất thời gian gấp đôi vì thời gian ngủ 1 ms nhưng 8 luồng mất cùng thời gian vì mỗi luồng hiện đang ngủ khoảng một nửa thời gian và vẫn khá tăng tuyến tính).
Tuy nhiên, với RHEL 6, thời gian thực hiện với 4 luồng tăng hơn khoảng 15% so với gấp đôi dự kiến và trường hợp 8 luồng tăng hơn khoảng 45% so với mức tăng nhẹ dự kiến. Sự gia tăng trong trường hợp 4 luồng dường như là RHEL 6 thực sự đang ngủ trong một vài phần triệu giây hơn 1 ms trong khi RHEL 5 chỉ ngủ khoảng 900 chúng tôi, nhưng điều này không giải thích được sự gia tăng lớn bất ngờ trong 8 và 40 trường hợp chủ đề.
Tôi đã thấy các loại hành vi tương tự với tất cả các giá trị 3 -DSLEEP_TYPE. Tôi cũng đã thử chơi với các tham số lịch trình trong sysctl, nhưng dường như không có gì có tác động đáng kể đến kết quả. Bất kỳ ý tưởng về làm thế nào tôi có thể chẩn đoán thêm vấn đề này?
CẬP NHẬT: 2012-05-07
Tôi đã thêm các phép đo sử dụng CPU của người dùng và hệ thống từ / Proc / stat // task // stat làm đầu ra của thử nghiệm để thử và có được một điểm quan sát khác. Tôi cũng tìm thấy một vấn đề với cách độ lệch trung bình và độ lệch chuẩn được cập nhật khi tôi thêm vòng lặp bên ngoài, vì vậy tôi sẽ thêm các ô mới có các phép đo độ lệch chuẩn và trung bình đã sửa. Tôi đã bao gồm các chương trình cập nhật. Tôi cũng đã thực hiện một repo git để theo dõi mã và nó có sẵn ở đây.
#include <limits.h>
#include <math.h>
#include <poll.h>
#include <pthread.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/syscall.h>
#include <sys/time.h>
// Apparently GLIBC doesn't provide a wrapper for this function so provide it here
#ifndef HAS_GETTID
pid_t gettid(void)
{
return syscall(SYS_gettid);
}
#endif
// The different type of sleep that are supported
enum sleep_type {
SLEEP_TYPE_NONE,
SLEEP_TYPE_SELECT,
SLEEP_TYPE_POLL,
SLEEP_TYPE_USLEEP,
SLEEP_TYPE_YIELD,
SLEEP_TYPE_PTHREAD_COND,
SLEEP_TYPE_NANOSLEEP,
};
// Information returned by the processing thread
struct thread_res {
long long clock;
long long user;
long long sys;
};
// Function type for doing work with a sleep
typedef struct thread_res *(*work_func)(const int pid, const int sleep_time, const int num_iterations, const int work_size);
// Information passed to the thread
struct thread_info {
pid_t pid;
int sleep_time;
int num_iterations;
int work_size;
work_func func;
};
inline void get_thread_times(pid_t pid, pid_t tid, unsigned long long *utime, unsigned long long *stime)
{
char filename[FILENAME_MAX];
FILE *f;
sprintf(filename, "/proc/%d/task/%d/stat", pid, tid);
f = fopen(filename, "r");
if (f == NULL) {
*utime = 0;
*stime = 0;
return;
}
fscanf(f, "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %Lu %Lu", utime, stime);
fclose(f);
}
// In order to make SLEEP_TYPE a run-time parameter function pointers are used.
// The function pointer could have been to the sleep function being used, but
// then that would mean an extra function call inside of the "work loop" and I
// wanted to keep the measurements as tight as possible and the extra work being
// done to be as small/controlled as possible so instead the work is declared as
// a seriees of macros that are called in all of the sleep functions. The code
// is a bit uglier this way, but I believe it results in a more accurate test.
// Fill in a buffer with random numbers (taken from latt.c by Jens Axboe <jens.axboe@oracle.com>)
#define DECLARE_FUNC(NAME) struct thread_res *do_work_##NAME(const int pid, const int sleep_time, const int num_iterations, const int work_size)
#define DECLARE_WORK() \
int *buf; \
int pseed; \
int inum, bnum; \
pid_t tid; \
struct timeval clock_before, clock_after; \
unsigned long long user_before, user_after; \
unsigned long long sys_before, sys_after; \
struct thread_res *diff; \
tid = gettid(); \
buf = malloc(work_size * sizeof(*buf)); \
diff = malloc(sizeof(*diff)); \
get_thread_times(pid, tid, &user_before, &sys_before); \
gettimeofday(&clock_before, NULL)
#define DO_WORK(SLEEP_FUNC) \
for (inum=0; inum<num_iterations; ++inum) { \
SLEEP_FUNC \
\
pseed = 1; \
for (bnum=0; bnum<work_size; ++bnum) { \
pseed = pseed * 1103515245 + 12345; \
buf[bnum] = (pseed / 65536) % 32768; \
} \
} \
#define FINISH_WORK() \
gettimeofday(&clock_after, NULL); \
get_thread_times(pid, tid, &user_after, &sys_after); \
diff->clock = 1000000LL * (clock_after.tv_sec - clock_before.tv_sec); \
diff->clock += clock_after.tv_usec - clock_before.tv_usec; \
diff->user = user_after - user_before; \
diff->sys = sys_after - sys_before; \
free(buf); \
return diff
DECLARE_FUNC(nosleep)
{
DECLARE_WORK();
// Let the compiler know that sleep_time isn't used in this function
(void)sleep_time;
DO_WORK();
FINISH_WORK();
}
DECLARE_FUNC(select)
{
struct timeval ts;
DECLARE_WORK();
DO_WORK(
ts.tv_sec = 0;
ts.tv_usec = sleep_time;
select(0, 0, 0, 0, &ts);
);
FINISH_WORK();
}
DECLARE_FUNC(poll)
{
struct pollfd pfd;
const int sleep_time_ms = sleep_time / 1000;
DECLARE_WORK();
pfd.fd = 0;
pfd.events = 0;
DO_WORK(
poll(&pfd, 1, sleep_time_ms);
);
FINISH_WORK();
}
DECLARE_FUNC(usleep)
{
DECLARE_WORK();
DO_WORK(
usleep(sleep_time);
);
FINISH_WORK();
}
DECLARE_FUNC(yield)
{
DECLARE_WORK();
// Let the compiler know that sleep_time isn't used in this function
(void)sleep_time;
DO_WORK(
sched_yield();
);
FINISH_WORK();
}
DECLARE_FUNC(pthread_cond)
{
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
struct timespec ts;
const int sleep_time_ns = sleep_time * 1000;
DECLARE_WORK();
pthread_mutex_lock(&mutex);
DO_WORK(
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_nsec += sleep_time_ns;
if (ts.tv_nsec >= 1000000000) {
ts.tv_sec += 1;
ts.tv_nsec -= 1000000000;
}
pthread_cond_timedwait(&cond, &mutex, &ts);
);
pthread_mutex_unlock(&mutex);
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mutex);
FINISH_WORK();
}
DECLARE_FUNC(nanosleep)
{
struct timespec req, rem;
const int sleep_time_ns = sleep_time * 1000;
DECLARE_WORK();
DO_WORK(
req.tv_sec = 0;
req.tv_nsec = sleep_time_ns;
nanosleep(&req, &rem);
);
FINISH_WORK();
}
void *do_test(void *arg)
{
const struct thread_info *tinfo = (struct thread_info *)arg;
// Call the function to do the work
return (*tinfo->func)(tinfo->pid, tinfo->sleep_time, tinfo->num_iterations, tinfo->work_size);
}
struct thread_res_stats {
double min;
double max;
double avg;
double stddev;
double prev_avg;
};
#ifdef LLONG_MAX
#define THREAD_RES_STATS_INITIALIZER {LLONG_MAX, LLONG_MIN, 0, 0, 0}
#else
#define THREAD_RES_STATS_INITIALIZER {LONG_MAX, LONG_MIN, 0, 0, 0}
#endif
void update_stats(struct thread_res_stats *stats, long long value, int num_samples, int num_iterations, double scale_to_usecs)
{
// Calculate the average time per iteration
double value_per_iteration = value * scale_to_usecs / num_iterations;
// Update the max and min
if (value_per_iteration < stats->min)
stats->min = value_per_iteration;
if (value_per_iteration > stats->max)
stats->max = value_per_iteration;
// Update the average
stats->avg += (value_per_iteration - stats->avg) / (double)(num_samples);
// Update the standard deviation
stats->stddev += (value_per_iteration - stats->prev_avg) * (value_per_iteration - stats->avg);
// And record the current average for use in the next update
stats->prev_avg= stats->avg;
}
void print_stats(const char *name, const struct thread_res_stats *stats)
{
printf("%s: min: %.1f us avg: %.1f us max: %.1f us stddev: %.1f us\n",
name,
stats->min,
stats->avg,
stats->max,
stats->stddev);
}
int main(int argc, char **argv)
{
if (argc <= 6) {
printf("Usage: %s <sleep_time> <outer_iterations> <inner_iterations> <work_size> <num_threads> <sleep_type>\n", argv[0]);
printf(" outer_iterations: Number of iterations for each thread (used to calculate statistics)\n");
printf(" inner_iterations: Number of work/sleep cycles performed in each thread (used to improve consistency/observability))\n");
printf(" work_size: Number of array elements (in kb) that are filled with psuedo-random numbers\n");
printf(" num_threads: Number of threads to spawn and perform work/sleep cycles in\n");
printf(" sleep_type: 0=none 1=select 2=poll 3=usleep 4=yield 5=pthread_cond 6=nanosleep\n");
return -1;
}
struct thread_info tinfo;
int outer_iterations;
int sleep_type;
int s, inum, tnum, num_samples, num_threads;
pthread_attr_t attr;
pthread_t *threads;
struct thread_res *res;
struct thread_res **times;
// Track the stats for each of the measurements
struct thread_res_stats stats_clock = THREAD_RES_STATS_INITIALIZER;
struct thread_res_stats stats_user = THREAD_RES_STATS_INITIALIZER;
struct thread_res_stats stats_sys = THREAD_RES_STATS_INITIALIZER;
// Calculate the conversion factor from clock_t to seconds
const long clocks_per_sec = sysconf(_SC_CLK_TCK);
const double clocks_to_usec = 1000000 / (double)clocks_per_sec;
// Get the parameters
tinfo.pid = getpid();
tinfo.sleep_time = atoi(argv[1]);
outer_iterations = atoi(argv[2]);
tinfo.num_iterations = atoi(argv[3]);
tinfo.work_size = atoi(argv[4]) * 1024;
num_threads = atoi(argv[5]);
sleep_type = atoi(argv[6]);
switch (sleep_type) {
case SLEEP_TYPE_NONE: tinfo.func = &do_work_nosleep; break;
case SLEEP_TYPE_SELECT: tinfo.func = &do_work_select; break;
case SLEEP_TYPE_POLL: tinfo.func = &do_work_poll; break;
case SLEEP_TYPE_USLEEP: tinfo.func = &do_work_usleep; break;
case SLEEP_TYPE_YIELD: tinfo.func = &do_work_yield; break;
case SLEEP_TYPE_PTHREAD_COND: tinfo.func = &do_work_pthread_cond; break;
case SLEEP_TYPE_NANOSLEEP: tinfo.func = &do_work_nanosleep; break;
default:
printf("Invalid sleep type: %d\n", sleep_type);
return -7;
}
// Initialize the thread creation attributes
s = pthread_attr_init(&attr);
if (s != 0) {
printf("Error initializing thread attributes\n");
return -2;
}
// Allocate the memory to track the threads
threads = calloc(num_threads, sizeof(*threads));
times = calloc(num_threads, sizeof(*times));
if (threads == NULL) {
printf("Error allocating memory to track threads\n");
return -3;
}
// Initialize the number of samples
num_samples = 0;
// Perform the requested number of outer iterations
for (inum=0; inum<outer_iterations; ++inum) {
// Start all of the threads
for (tnum=0; tnum<num_threads; ++tnum) {
s = pthread_create(&threads[tnum], &attr, &do_test, &tinfo);
if (s != 0) {
printf("Error starting thread\n");
return -4;
}
}
// Wait for all the threads to finish
for (tnum=0; tnum<num_threads; ++tnum) {
s = pthread_join(threads[tnum], (void **)(&res));
if (s != 0) {
printf("Error waiting for thread\n");
return -6;
}
// Save the result for processing when they're all done
times[tnum] = res;
}
// For each of the threads
for (tnum=0; tnum<num_threads; ++tnum) {
// Increment the number of samples in the statistics
++num_samples;
// Update the statistics with this measurement
update_stats(&stats_clock, times[tnum]->clock, num_samples, tinfo.num_iterations, 1);
update_stats(&stats_user, times[tnum]->user, num_samples, tinfo.num_iterations, clocks_to_usec);
update_stats(&stats_sys, times[tnum]->sys, num_samples, tinfo.num_iterations, clocks_to_usec);
// And clean it up
free(times[tnum]);
}
}
// Clean up the thread creation attributes
s = pthread_attr_destroy(&attr);
if (s != 0) {
printf("Error cleaning up thread attributes\n");
return -5;
}
// Finish the calculation of the standard deviation
stats_clock.stddev = sqrtf(stats_clock.stddev / (num_samples - 1));
stats_user.stddev = sqrtf(stats_user.stddev / (num_samples - 1));
stats_sys.stddev = sqrtf(stats_sys.stddev / (num_samples - 1));
// Print out the statistics of the times
print_stats("gettimeofday_per_iteration", &stats_clock);
print_stats("utime_per_iteration", &stats_user);
print_stats("stime_per_iteration", &stats_sys);
// Clean up the allocated threads and times
free(threads);
free(times);
return 0;
}
Tôi đã chạy lại các thử nghiệm trên Dell Vostro 200 (CPU lõi kép) với một số phiên bản HĐH khác nhau. Tôi nhận ra rằng một vài trong số này sẽ có các bản vá khác nhau được áp dụng và sẽ không phải là "mã hạt nhân thuần túy", nhưng đây là cách đơn giản nhất để tôi có thể chạy thử nghiệm trên các phiên bản khác nhau của hạt nhân và có được sự so sánh. Tôi đã tạo ra các lô với gnuplot và đã bao gồm phiên bản từ bugzilla về vấn đề này .
Tất cả các thử nghiệm này đã được chạy với lệnh sau với tập lệnh sau và lệnh này ./run_test 1000 10 1000 250 8 6 <os_name>
.
#!/bin/bash
if [ $# -ne 7 ]; then
echo "Usage: `basename $0` <sleep_time> <outer_iterations> <inner_iterations> <work_size> <max_num_threads> <max_sleep_type> <test_name>"
echo " max_num_threads: The highest value used for num_threads in the results"
echo " max_sleep_type: The highest value used for sleep_type in the results"
echo " test_name: The name of the directory where the results will be stored"
exit -1
fi
sleep_time=$1
outer_iterations=$2
inner_iterations=$3
work_size=$4
max_num_threads=$5
max_sleep_type=$6
test_name=$7
# Make sure this results directory doesn't already exist
if [ -e $test_name ]; then
echo "$test_name already exists";
exit -1;
fi
# Create the directory to put the results in
mkdir $test_name
# Run through the requested number of SLEEP_TYPE values
for i in $(seq 0 $max_sleep_type)
do
# Run through the requested number of threads
for j in $(seq 1 $max_num_threads)
do
# Print which settings are about to be run
echo "sleep_type: $i num_threads: $j"
# Run the test and save it to the results file
./test_sleep $sleep_time $outer_iterations $inner_iterations $work_size $j $i >> "$test_name/results_$i.txt"
done
done
Đây là tóm tắt của những gì tôi quan sát. Tôi sẽ so sánh chúng theo cặp lần này bởi vì tôi nghĩ rằng nó có nhiều thông tin hơn theo cách đó.
CentOS 5.6 so với CentOS 6.2
Thời gian đồng hồ treo tường (gettimeofday) trên mỗi lần lặp trên CentOS 5.6 khác nhau nhiều hơn 6.2, nhưng điều này có ý nghĩa vì CFS nên làm tốt hơn việc cung cấp cho các quá trình thời gian CPU bằng nhau dẫn đến kết quả phù hợp hơn. Một điều khá rõ ràng là CentOS 6.2 chính xác và nhất quán hơn về thời gian mà nó ngủ với các cơ chế ngủ khác nhau.
"Hình phạt" rõ ràng là rõ ràng vào ngày 6.2 với số lượng luồng thấp (hiển thị trên lô thời gian của người dùng và thời gian của người dùng) nhưng dường như được giảm với số lượng luồng cao hơn (sự khác biệt về thời gian của người dùng có thể chỉ là một vấn đề kế toán vì đo thời gian người dùng là khóa học như vậy).
Biểu đồ thời gian hệ thống cho thấy các cơ chế ngủ trong 6.2 đang tiêu thụ nhiều hệ thống hơn so với 5,6, tương ứng với kết quả trước đó của thử nghiệm đơn giản gồm 50 quy trình chỉ gọi chọn tiêu thụ một lượng CPU không tầm thường vào 6.2 nhưng không phải 5.6 .
Một điều mà tôi tin rằng đáng lưu ý là việc sử dụng calendar_yield () không gây ra hình phạt giống như các phương thức ngủ. Kết luận của tôi từ điều này là không phải chính trình lập lịch biểu là nguồn gốc của vấn đề, mà là sự tương tác của các phương thức ngủ với trình lập lịch biểu mới là vấn đề.
Ubuntu 7.10 so với Ubuntu 8.04-4
Sự khác biệt trong phiên bản kernel giữa hai phiên bản này nhỏ hơn so với CentOS 5.6 và 6.2, nhưng chúng vẫn kéo dài khoảng thời gian khi CFS được giới thiệu. Kết quả thú vị đầu tiên là lựa chọn và thăm dò ý kiến dường như là cơ chế ngủ duy nhất có "hình phạt" trên 8.04 và hình phạt đó tiếp tục với số lượng chủ đề cao hơn so với những gì đã thấy với CentOS 6.2.
Thời gian người dùng để chọn và thăm dò ý kiến và Ubuntu 7.10 thấp một cách vô lý, do đó đây dường như là một vấn đề kế toán tồn tại sau đó, nhưng tôi tin rằng không liên quan đến vấn đề / thảo luận hiện tại.
Thời gian hệ thống dường như cao hơn với Ubuntu 8.04 so với Ubuntu 7.10 nhưng sự khác biệt này là FAR ít khác biệt hơn so với những gì đã thấy với CentOS 5.6 so với 6.2.
Ghi chú về Ubuntu 11.10 và Ubuntu 12.04
Điều đầu tiên cần lưu ý ở đây là các lô cho Ubuntu 12.04 tương đương với các bản đồ từ 11.10 nên chúng không hiển thị để ngăn chặn sự dư thừa không cần thiết.
Nhìn chung, các sơ đồ cho Ubuntu 11.10 cho thấy cùng một xu hướng đã được quan sát với CentOS 6.2 (điều này cho thấy đây là vấn đề hạt nhân nói chung chứ không chỉ là vấn đề của RHEL). Một ngoại lệ là thời gian hệ thống dường như cao hơn một chút với Ubuntu 11.10 so với CentOS 6.2, nhưng một lần nữa, độ phân giải của phép đo này là rất dĩ nhiên nên tôi nghĩ rằng bất kỳ kết luận nào khác ngoài "nó dường như cao hơn một chút "Sẽ bước lên băng mỏng.
Ubuntu 11.10 so với Ubuntu 11.10 với BFS
Một PPA sử dụng BFS với kernel Ubuntu có thể được tìm thấy tại https://launchpad.net/~chogydan/+archive/ppa và điều này đã được cài đặt để tạo ra sự so sánh này. Tôi không thể tìm thấy một cách dễ dàng để chạy CentOS 6.2 với BFS vì vậy tôi đã chạy với so sánh này và vì kết quả của Ubuntu 11.10 so sánh rất tốt với CentOS 6.2, tôi tin rằng đó là một so sánh công bằng và có ý nghĩa.
Điểm lưu ý chính là với BFS chỉ chọn và nanos ngủ tạo ra "hình phạt" với số lượng chủ đề thấp, nhưng dường như nó tạo ra một "hình phạt" tương tự (nếu không phải là lớn hơn) như đã thấy với CFS cho mức cao hơn số của chủ đề.
Điểm thú vị khác là thời gian hệ thống dường như thấp hơn với BFS so với CFS. Một lần nữa, điều này bắt đầu bước lên băng mỏng vì dữ liệu thô, nhưng có một số khác biệt xuất hiện và kết quả này phù hợp với thử nghiệm vòng lặp chọn 50 quy trình đơn giản đã cho thấy việc sử dụng CPU với BFS ít hơn so với CFS .
Kết luận mà tôi rút ra từ hai điểm này là BFS không giải quyết được vấn đề nhưng ít nhất dường như làm giảm ảnh hưởng của nó trong một số lĩnh vực.
Phần kết luận
Như đã nói trước đây, tôi không tin rằng đây là vấn đề với chính bộ lập lịch, nhưng với sự tương tác giữa các cơ chế ngủ và bộ lập lịch. Tôi xem xét việc sử dụng CPU tăng lên này trong các quy trình nên ngủ và sử dụng ít hoặc không có CPU là hồi quy từ CentOS 5.6 và là trở ngại lớn cho bất kỳ chương trình nào muốn sử dụng vòng lặp sự kiện hoặc kiểu cơ chế bỏ phiếu.
Có bất kỳ dữ liệu nào khác tôi có thể nhận được hoặc các xét nghiệm tôi có thể chạy để giúp chẩn đoán thêm vấn đề không?
Cập nhật vào ngày 29 tháng 6 năm 2012
Tôi đã đơn giản hóa chương trình thử nghiệm một chút và có thể tìm thấy ở đây (Bài đăng đã bắt đầu vượt quá giới hạn độ dài nên phải di chuyển nó).