나는 물었다이전 질문RHEL 5에서 RHEL 6으로 애플리케이션을 이동할 때 CPU 사용량이 증가하는 원인을 찾아보세요. 이에 대해 내가 수행한 분석에 따르면 이는 커널의 CFS로 인해 발생하는 것으로 보입니다. 이것이 사실인지 확인하기 위해 테스트 애플리케이션을 작성했습니다. (원본 테스트 애플리케이션은 크기 제한에 맞게 제거되었지만 여전히 사용할 수 있습니다.Git 저장소.
다음 명령을 사용하여 RHEL 5에서 컴파일했습니다.
cc test_select_work.c -O2 -DSLEEP_TYPE=0 -Wall -Wextra -lm -lpthread -o test_select_work
그런 다음 Dell Precision m6500에서 반복당 실행 시간이 약 1밀리초가 될 때까지 이러한 매개변수를 조정했습니다.
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
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
두 버전 모두에서 이러한 결과는 내 기대와 일치하며 반복당 평균 시간은 상대적으로 선형적으로 확장됩니다. 그런 다음 RHEL 5에서 다시 컴파일하여 -DSLEEP_TYPE=1
다음과 같은 결과를 얻었습니다.
./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
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
RHEL 5에서 결과는 제가 예상한 것과 거의 비슷했습니다. (4개의 스레드는 1ms의 절전 시간으로 인해 두 배의 시간이 걸렸지만, 8개의 스레드는 이제 각 스레드가 약 절반의 시간을 잠들기 때문에 동일한 시간이 걸렸으며 여전히 꽤 좋습니다.) 증가).
그러나 RHEL 6에서는 4스레드 더블링에 필요한 시간이 예상 더블링보다 약 15% 증가했고, 8스레드의 경우 예상 소폭 증가보다 약 45% 증가했습니다. 4스레드 사례의 증가는 RHEL 6이 실제로 1ms 동안 몇 마이크로초 동안 휴면한 반면 RHEL 5는 약 900us 동안만 휴면한 것으로 보이지만 이는 8 및 40스레드 케이스의 예상치 못한 큰 증가를 설명하지 못합니다. .
3개의 -DSLEEP_TYPE 값 모두에 대해 비슷한 동작이 나타납니다. 또한 sysctl에서 스케줄러 매개변수를 사용해 보았지만 결과에 큰 영향을 미치는 것은 없는 것 같습니다. 이 문제를 추가로 진단하는 방법에 대한 아이디어가 있습니까?
업데이트 날짜: 2012-05-07
또 다른 관찰 지점을 얻기 위해 테스트 출력으로 /proc/stat//tasks//stat의 사용자 및 시스템 CPU 사용량 측정값을 추가했습니다. 또한 외부 반복 루프를 추가할 때 도입된 평균 및 표준 편차가 업데이트되는 방식에서 문제를 발견했으므로 수정된 평균 및 표준 편차 측정값이 포함된 새 플롯을 추가하겠습니다. 업데이트된 절차를 포함시켰습니다. 또한 코드를 추적하기 위해 git 저장소를 만들었습니다. 여기에서 사용할 수 있습니다.
#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 <[email protected]>)
#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;
}
여러 가지 OS 버전이 포함된 Dell Vostro 200(듀얼 코어 CPU)에서 테스트를 다시 실행했습니다. 나는 이들 중 몇몇이 다른 패치를 적용할 것이고 "순수한 커널 코드"가 아닐 것이라는 것을 알고 있습니다. 그러나 이것이 다른 버전의 커널에서 테스트를 실행하고 비교할 수 있는 가장 쉬운 방법입니다. gnuplot을 사용하여 플롯을 생성하고 다음 파일을 포함했습니다.이 문제에 대한 bugzilla.
이러한 모든 테스트는 다음 명령, 다음 스크립트 및 이 명령을 사용하여 실행되었습니다 ./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
제가 관찰한 내용을 요약하면 다음과 같습니다. 이번에는 좀 더 유익할 것 같아서 쌍으로 비교해보겠습니다.
CentOS 5.6 및 CentOS 6.2
반복당 벽시계 시간(gettimeofday)은 CentOS 5.6에서 6.2보다 더 다양하지만 CFS가 프로세스에 동일한 CPU 시간을 제공하는 데 더 뛰어나서 보다 일관된 결과를 얻을 수 있기 때문에 이는 의미가 있습니다. CentOS 6.2는 다양한 절전 메커니즘에서 더욱 정확하고 일관된 절전 시간을 제공한다는 점도 분명합니다.
스레드 수가 적은 6.2에서는 "페널티"가 확실히 눈에 띄지만(gettimeofday 및 사용자 시간 그래프에서 볼 수 있음) 스레드 수가 증가함에 따라 감소하는 것처럼 보입니다(사용자 시간의 차이는 단지 회계상의 문제일 수 있습니다. 사용자 시간 측정 때문에 당연하게 받아들여진다).
시스템 시간 그래프는 절전 메커니즘이 5.6보다 6.2에서 더 많은 시스템을 소비한다는 것을 보여줍니다. 이는 6.2에서 많은 CPU를 소비했지만 5.6에서는 그렇지 않은 select를 호출한 이전의 50개 프로세스에 대한 간단한 테스트 결과에 해당합니다. 그렇지 않으면.
sched_yield()를 사용하면 sleep 메소드와 동일한 페널티가 발생하지 않는다는 점에 주목할 가치가 있다고 생각합니다. 내 결론은 문제의 원인은 스케줄러 자체가 아니라 스케줄러와 sleep 메서드의 상호 작용이라는 것입니다.
우분투 7.10 및 우분투 8.04-4
둘 사이의 커널 버전 차이는 CentOS 5.6 및 6.2보다 작지만 여전히 CFS가 도입된 기간에 걸쳐 있습니다. 첫 번째 흥미로운 결과는 select와 poll이 8.04에서 "페널티가 적용되는" 유일한 절전 메커니즘으로 나타나고 이 페널티는 CentOS 6.2에서 볼 수 있는 것보다 더 높은 스레드 수에서 지속된다는 것입니다.
Ubuntu 7.10의 선택 및 폴링 및 사용자 시간이 너무 낮아 당시 일종의 회계 문제인 것 같지만 현재 질문/토론과 관련이 없다고 생각합니다.
Ubuntu 8.04는 Ubuntu 7.10보다 시스템 시간이 더 긴 것으로 보이지만 CentOS 5.6과 6.2에 비해 그 차이는 훨씬 덜 분명합니다.
Ubuntu 11.10 및 Ubuntu 12.04에 대한 참고 사항
여기서 가장 먼저 주목해야 할 점은 Ubuntu 12.04 플롯은 11.10 플롯과 유사하므로 불필요한 중복을 방지하기 위해 표시되지 않았다는 것입니다.
전반적으로 Ubuntu 11.10의 그래프는 CentOS 6.2에서 관찰된 것과 동일한 경향을 보여줍니다(이것은 단순한 RHEL 문제가 아니라 일반적인 커널 문제임을 시사합니다). 유일한 예외는 Ubuntu 11.10의 시스템 시간이 CentOS 6.2에 비해 약간 높은 것 같지만, 이번에도 이 측정의 해상도는 매우 자연스럽기 때문에 "조금 높은 것 같다" 이외의 결론은 나오지 않을 것 같습니다. 얇은 얼음 위에 있어야합니다.
BFS를 사용하는 Ubuntu 11.10 및 Ubuntu 11.10
Ubuntu 커널에서 BFS를 사용하기 위한 PPA는 다음 위치에서 찾을 수 있습니다.https://launchpad.net/~chogydan/+archive/ppa이 비교를 생성하기 위해 설치되었습니다. BFS를 사용하여 CentOS 6.2를 쉽게 실행할 수 있는 방법을 찾을 수 없어서 이렇게 비교하게 되었는데, Ubuntu 11.10의 결과가 CentOS 6.2와 매우 잘 비교되었기 때문에 공정하고 의미 있는 비교라고 생각합니다.
주목해야 할 중요한 점은 BFS를 사용하면 더 낮은 스레드 번호에서는 select 및 nanosleep만 "페널티"가 발생하지만 더 높은 스레드 번호에서는 CFS와 비슷한(더 크지는 않지만) "페널티"가 발생하는 것 같습니다. 스레드.
또 다른 흥미로운 점은 BFS의 시스템 시간이 CFS보다 낮은 것 같습니다. 다시 말하지만, 이는 데이터의 거칠기 때문에 얇은 얼음에서 시작되지만 약간의 차이가 있는 것으로 보이며 결과는 간단한 50 프로세스 선택 루프 테스트와 일치하며 CFS보다 BFS의 CPU 사용량이 더 낮습니다.
이 두 가지 점에 대한 나의 결론은 BFS가 문제를 해결하지는 못하지만 적어도 어떤 면에서는 그 영향을 줄이는 것 같다는 것입니다.
결론적으로
앞서 언급했듯이 이는 스케줄러 자체의 문제가 아니라 슬립 메커니즘과 스케줄러 간의 상호 작용에 문제가 있다고 생각합니다. 저는 CPU를 거의 사용하지 않고 절전 모드로 있어야 하는 프로세스의 CPU 사용량 증가가 CentOS 5.6의 장애이자 이벤트 루프나 폴링 스타일 메커니즘을 사용하려는 모든 프로그램의 주요 걸림돌이라고 생각합니다.
문제를 더 자세히 진단하는 데 도움이 되도록 얻을 수 있는 추가 데이터나 실행할 수 있는 테스트가 있습니까?
2012년 6월 29일 업데이트됨
테스트 프로그램을 약간 단순화한 결과 다음과 같은 사실을 발견했습니다.여기(게시물이 길이 제한을 초과하기 시작하여 이동하게 되었습니다.)
답변1
~에 따르면SLES 11 SP2 릴리스 노트이는 CFS 구현 방식의 변경일 수 있습니다.
SLES 11 SP2는 현재 SLES 버전이므로 이 동작은 여전히 유효합니다(모든 3.x 커널에 해당되는 것으로 보입니다).
이러한 변경은 의도적인 것이지만 "바람직하지 않은" 부작용이 있을 수 있습니다. 아마도 설명된 솔루션 중 하나가 도움이 될 것입니다.