9

I wrote a test program which consists of just an infinite loop with some computations inside, and performs no I/O operations. I tried starting two instances of the program, one with a high niceness value, and the other with a low niceness value:

sudo nice -n 19 taskset 1 ./test sudo nice -n -20 taskset 1 ./test 

The taskset command ensures that both programs execute on the same core. Contrary to my expectation, top reports that both programs get about 50% of the computation time. Why is that? Does the nice command even have an effect?

5
  • what were the computations? Perhaps there's not enough contention on the processor to make a difference Commented Apr 26, 2012 at 22:59
  • The computations inside the loop are fairly long. I also checked the generated assembler output and nothing is optimized away (compiled with lowest optimization settings on gcc). Commented Apr 26, 2012 at 23:10
  • possible duplicate of Understanding renice Commented Jul 5, 2014 at 10:13
  • this is one of my pet peeves. I'm very curious about how many software engineers don't understand the notion of process/thread priorities. And that with any number of available CPUs this is still an important subject. On SUNOS processes started with nice +19 used to not preempt higher priority processes -- but they "fixed" this meanwhile. On NT there was always priority class idle. Only NT3.51 had a very useful focus controlled multitasking -- but they "fixed" this since NT4.0. Commented Jun 26, 2015 at 17:47
  • the only system which did focus-controlled multitasking was NT3.51. All the other systems are just advertising something which cannot be detected by humans. I'm missing the 90s. Commented Dec 12, 2018 at 16:40

5 Answers 5

11

The behavior you are seeing is almost certainly because of the autogroup feature that was added in Linux 2.6.38 (in 2010). Presumably when you described running the two commands, they were run in different terminal windows. If you had run them in the same terminal window, then you should have seen the nice value have an effect. The rest of this answer elaborates the story.

The kernel provides a feature known as autogrouping to improve interactive desktop performance in the face of multiprocess, CPU-intensive workloads such as building the Linux kernel with large numbers of parallel build processes (i.e., the make(1) -j flag).

A new autogroup is created when a new session is created via setsid(2); this happens, for example, when a new terminal window is started. A new process created by fork(2) inherits its parent's autogroup membership. Thus, all of the processes in a session are members of the same autogroup.

When autogrouping is enabled, all of the members of an autogroup are placed in the same kernel scheduler "task group". The Linux kernel scheduler employs an algorithm that equalizes the distribution of CPU cycles across task groups. The benefits of this for interactive desktop performance can be described via the following example.

Suppose that there are two autogroups competing for the same CPU (i.e., presume either a single CPU system or the use of taskset(1) to confine all the processes to the same CPU on an SMP system). The first group contains ten CPU-bound processes from a kernel build started with make -j10. The other contains a single CPU-bound process: a video player. The effect of autogrouping is that the two groups will each receive half of the CPU cycles. That is, the video player will receive 50% of the CPU cycles, rather than just 9% of the cycles, which would likely lead to degraded video playback. The situation on an SMP system is more complex, but the general effect is the same: the scheduler distributes CPU cycles across task groups such that an autogroup that contains a large number of CPU-bound processes does not end up hogging CPU cycles at the expense of the other jobs on the system.

The nice value and group scheduling

When scheduling non-real-time processes (e.g., those scheduled under the default SCHED_OTHER policy), the scheduler employs a technique known as "group scheduling", under which threads are scheduled in "task groups". Task groups are formed in the various circumstances, with the relevant case here being autogrouping.

If autogrouping is enabled, then all of the threads that are (implicitly) placed in an autogroup (i.e., the same session, as created by setsid(2)) form a task group. Each new autogroup is thus a separate task group.

Under group scheduling, a thread's nice value has an effect for scheduling decisions only relative to other threads in the same task group. This has some surprising consequences in terms of the traditional semantics of the nice value on UNIX systems. In particular, if autogrouping is enabled (which is the default in various Linux distributions), then employing nice(1) on a process has an effect only for scheduling relative to other processes executed in the same session (typically: the same terminal window).

Conversely, for two processes that are (for example) the sole CPU-bound processes in different sessions (e.g., different terminal windows, each of whose jobs are tied to different autogroups), modifying the nice value of the process in one of the sessions has no effect in terms of the scheduler's decisions relative to the process in the other session. This presumably is the scenario you saw, though you don't explicitly mention using two terminal windows.

If you want to prevent autogrouping interfering with the traditional nice behavior as described here, you can disable the feature

echo 0 > /proc/sys/kernel/sched_autogroup_enabled 

Be aware though that this will also have the effect of disabling the benefits for desktop interactivity that the autogroup feature was intended to provide (see above).

The autogroup nice value

A process's autogroup membership can be viewed via the file /proc/[pid]/autogroup:

$ cat /proc/1/autogroup /autogroup-1 nice 0 

This file can also be used to modify the CPU bandwidth allocated to an autogroup. This is done by writing a number in the "nice" range to the file to set the autogroup's nice value. The allowed range is from +19 (low priority) to -20 (high priority).

The autogroup nice setting has the same meaning as the process nice value, but applies to distribution of CPU cycles to the autogroup as a whole, based on the relative nice values of other autogroups. For a process inside an autogroup, the CPU cycles that it receives will be a product of the autogroup's nice value (compared to other autogroups) and the process's nice value (compared to other processes in the same autogroup).

Sign up to request clarification or add additional context in comments.

4 Comments

How can someone change the nice value for an autogroup? and how can one see which processes are in a certain autogroup?
Just echo the niceness like echo "-15" > /proc/$PID/autogroup .
On my Ubuntu I had to do #echo "kernel.sched_autogroup_enabled = 0" >> /etc/sysctl.conf to make this work : sudo renice -n -15 -p $PID (took that from bugzilla.redhat.com/show_bug.cgi?id=721416 ).
(... and don't forget to # sysctl -p after changing sysctl.conf)
2

I put together a test.c that just does:

for(;;) { } 

And then ran it with your nice's. I didn't run a different sudo for each one, but rather sudo'd an interactive shell and ran them both from there. I used two &'s.

I got one ./test hitting my CPU hard, and one barely touching it.

Naturally, the system still felt quite responsive; it takes a lot of CPU-hogging processes on modern processors to get so much load you can "feel" it.

That stands in contrast to I/O-hogging processes and memory-hogging processes; in these cases a single greedy process can make a system painful to use.

I'd guess either your system has a relatively unique priority-related bug (or subtlety), or there's something up with your methodology.

I ran my test on an Ubuntu 11.04 system.

1 Comment

When I ran both processes in background it also worked. I suppose Linux privileges processes with which the user may want to interact (e.g. programs started from bash and running in foreground), and largely ignores their niceness values.
1

I'm assuming that there's a & missing at the end of the command line. Otherwise, the second line won't run until the first completes.

While both processes are running, use something like top and make sure that they each have the nice value that you assigned.

What happens if you launch the processes using only taskset and then adjust their priority with renice after they are running?

5 Comments

The problem was that I was running the instances in two console windows in foreground. When I run them in background (using &) it works. It seems that Linux privileges processes running in foreground in a console and largely (completely?) ignores their niceness values. This makes sense as a user likely wants to interact with the program he/she started in a console window.
Any clarification on how this works exactly would be greatly appreciated.
Running a program in the foreground runs it as a child process of the console that spawned it, and child processes typically inherit the priority of their parent. Adding the & runs the program as a completely separate process, allowing you to control its priority individually.
Yes, but starting a program with nice (without &) does still change the priority of the child process.
It's possible that the system is limiting how far you can increase the priority of a foreground process attached to a console. After all if you increase the priority high enough that the parent console can't get any CPU time, then you can't even Ctrl+C to kill a runaway child process. I forget exactly how the kernel does this, but I do remember there being a mechanism for the kernel enforcing max/min priority limits.
1

Process niceness (priority) setting HAS an effect on Linux! (in practise, but ONLY if you give it enough work to do!)

On my system, as long as all cores are fully loaded, then nice does have an impact. On ubuntu 14.04, processes run with nice -N gets through 0.807 ** N operations compared to processes run without altering the nice value (given you are running one instance per core for each nice level).

In my case I have quad core i7 with hyper threading turned off, so if I run four or less processes, then it doesn't matter what their nice values are - they each get a full core. If I run four processes at nice level 0 and 4 at nice level 12, then the ones at level 12 get through 0.807 ^ 12, ie approx 7% of the work the ones at nice level zero do. The ratio seems to be a reasonable predictor from nice levels 0 through 14, after that it fluctuates (A few runs had nice level 18 processing more than nice 16 for instance) - Running the test for longer may smooth the results out.

(ruby 2.1.2 used)

,cl file:

uptime nices='-0 -6 -12 -18' nices='-0 -18' nices='-0 -2 -4 -6 -8 -10 -12 -14 -16 -18' rm -f ,n-* for i in 1 2 3 4 do for n in $nices do nice $n ruby ,count_loops.rb > ,n${n}-$i & done done ps -l uptime wait uptime ps -l c=`cat ,n-0-[1234] | total` last=$c for n in $nices do echo c2=`cat ,n${n}-[1234] | total` echo total of `cat ,n${n}-[1234]` is $c2 echo -n "nice $n count $2, percentage: " echo "3 k $c2 100 * $c / p" | dc echo -n " percent of last: " echo "3 k $c2 100 * $last / p" | dc last=$c2 done uptime echo total count: `cat ,n-*-[1234] | total` 

,count_loops.rb file

#!/usr/bin/env ruby limit = Time.new + 70 i=0 while Time.new < limit i += 1 j = 0 while (j += 1) < 10000 t = j end end puts i 

results of sh ,cl - initial diagnostic output:

 19:16:25 up 20:55, 2 users, load average: 3.58, 3.59, 2.88 F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD 0 S 1000 4987 4977 0 80 0 - 7297 wait pts/3 00:00:00 bash 0 S 1000 11743 2936 0 80 0 - 2515 wait pts/3 00:00:00 rubymine.sh 0 S 1000 11808 11743 6 80 0 - 834604 futex_ pts/3 00:18:10 java 0 S 1000 11846 11808 0 80 0 - 4061 poll_s pts/3 00:00:02 fsnotifier64 0 S 1000 19613 4987 0 80 0 - 2515 wait pts/3 00:00:00 sh 0 R 1000 19616 19613 0 80 0 - 7582 - pts/3 00:00:00 ruby 0 R 1000 19617 19613 0 82 2 - 7582 - pts/3 00:00:00 ruby 0 R 1000 19618 19613 0 84 4 - 7582 - pts/3 00:00:00 ruby 0 R 1000 19619 19613 0 86 6 - 7416 - pts/3 00:00:00 ruby 0 R 1000 19620 19613 0 88 8 - 6795 - pts/3 00:00:00 ruby 0 R 1000 19621 19613 0 90 10 - 6247 - pts/3 00:00:00 ruby 0 R 1000 19622 19613 0 92 12 - 6247 - pts/3 00:00:00 ruby 0 R 1000 19623 19613 0 94 14 - 6247 - pts/3 00:00:00 ruby 0 R 1000 19624 19613 0 96 16 - 6078 - pts/3 00:00:00 ruby 0 R 1000 19625 19613 0 98 18 - 6012 - pts/3 00:00:00 ruby 0 R 1000 19626 19613 0 80 0 - 7582 - pts/3 00:00:00 ruby 0 R 1000 19627 19613 0 82 2 - 7582 - pts/3 00:00:00 ruby 0 R 1000 19628 19613 0 84 4 - 7582 - pts/3 00:00:00 ruby 0 R 1000 19629 19613 0 86 6 - 7582 - pts/3 00:00:00 ruby 0 R 1000 19630 19613 0 88 8 - 6247 - pts/3 00:00:00 ruby 0 R 1000 19631 19613 0 90 10 - 6247 - pts/3 00:00:00 ruby 0 R 1000 19632 19613 0 92 12 - 6247 - pts/3 00:00:00 ruby 0 R 1000 19633 19613 0 94 14 - 6144 - pts/3 00:00:00 ruby 0 R 1000 19634 19613 0 96 16 - 4971 - pts/3 00:00:00 ruby 0 R 1000 19635 19613 0 98 18 - 4971 - pts/3 00:00:00 ruby 0 R 1000 19636 19613 0 80 0 - 7582 - pts/3 00:00:00 ruby 0 R 1000 19637 19613 0 82 2 - 7449 - pts/3 00:00:00 ruby 0 R 1000 19638 19613 0 84 4 - 7344 - pts/3 00:00:00 ruby 0 R 1000 19639 19613 0 86 6 - 7582 - pts/3 00:00:00 ruby 0 R 1000 19640 19613 0 88 8 - 7416 - pts/3 00:00:00 ruby 0 R 1000 19641 19613 0 90 10 - 6210 - pts/3 00:00:00 ruby 0 R 1000 19642 19613 0 92 12 - 6247 - pts/3 00:00:00 ruby 0 R 1000 19643 19613 0 94 14 - 5976 - pts/3 00:00:00 ruby 0 R 1000 19644 19613 0 96 16 - 6111 - pts/3 00:00:00 ruby 0 R 1000 19645 19613 0 98 18 - 4971 - pts/3 00:00:00 ruby 0 R 1000 19646 19613 0 80 0 - 7582 - pts/3 00:00:00 ruby 0 R 1000 19647 19613 0 82 2 - 7516 - pts/3 00:00:00 ruby 0 R 1000 19648 19613 0 84 4 - 7416 - pts/3 00:00:00 ruby 0 R 1000 19649 19613 0 86 6 - 6247 - pts/3 00:00:00 ruby 0 R 1000 19650 19613 0 88 8 - 6177 - pts/3 00:00:00 ruby 0 R 1000 19651 19613 0 90 10 - 6247 - pts/3 00:00:00 ruby 0 R 1000 19652 19613 0 92 12 - 6078 - pts/3 00:00:00 ruby 0 R 1000 19653 19613 0 94 14 - 6247 - pts/3 00:00:00 ruby 0 R 1000 19654 19613 0 96 16 - 4971 - pts/3 00:00:00 ruby 0 R 1000 19655 19613 0 98 18 - 4971 - pts/3 00:00:00 ruby 0 R 1000 19656 19613 0 80 0 - 3908 - pts/3 00:00:00 ps 19:16:26 up 20:55, 2 users, load average: 3.58, 3.59, 2.88 19:17:37 up 20:56, 3 users, load average: 28.92, 11.25, 5.59 F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD 0 S 1000 4987 4977 0 80 0 - 7297 wait pts/3 00:00:00 bash 0 S 1000 11743 2936 0 80 0 - 2515 wait pts/3 00:00:00 rubymine.sh 0 S 1000 11808 11743 6 80 0 - 834604 futex_ pts/3 00:18:10 java 0 S 1000 11846 11808 0 80 0 - 4061 poll_s pts/3 00:00:02 fsnotifier64 0 S 1000 19613 4987 0 80 0 - 2515 wait pts/3 00:00:00 sh 0 R 1000 19794 19613 0 80 0 - 3908 - pts/3 00:00:00 ps 

results of sh ,cl - statistics: (percentage of last is the percentage of this total compares to the count for the last group of processes)

total of 99951 101725 100681 104046 is 406403 nice -0 count , percentage: 100.000 percent of last: 100.000 total of 64554 62971 64006 63462 is 254993 nice -2 count , percentage: 62.743 percent of last: 62.743 total of 42997 43041 43197 42717 is 171952 nice -4 count , percentage: 42.310 percent of last: 67.434 total of 26882 28250 27151 27244 is 109527 nice -6 count , percentage: 26.950 percent of last: 63.696 total of 17228 17189 17427 17769 is 69613 nice -8 count , percentage: 17.129 percent of last: 63.557 total of 10815 10792 11021 11307 is 43935 nice -10 count , percentage: 10.810 percent of last: 63.113 total of 7023 6923 7885 7323 is 29154 nice -12 count , percentage: 7.173 percent of last: 66.357 total of 5005 4881 4938 5159 is 19983 nice -14 count , percentage: 4.917 percent of last: 68.542 total of 3517 5537 3555 4092 is 16701 nice -16 count , percentage: 4.109 percent of last: 83.576 total of 4372 4307 5552 4527 is 18758 nice -18 count , percentage: 4.615 percent of last: 112.316 19:17:37 up 20:56, 3 users, load average: 28.92, 11.25, 5.59 total count: 1141019 

( Purists will note I am mixing ruby, shell and dc - they will have to forgive me for old habits from last century showing through ;) )

Comments

0

I run an example program from APUE and nice does have the effect.

The example program mainly fork a child and both the parent and child execute a i++ increment operation for given time(10s). By giving the child different nice value, the result shows if nice makes a difference.

The book warns that I should run the program with a uniprocessor PC, fisrt I tried with my own PC, i5-7500 CPU @ 3.40GHz × 4 (4 cores), giving different nice value, almost no difference.

Then I log into my remote server, 1 processor 1 GB, and get the expected difference.


1 core processor 1 GB Test result:

./a.out

NZERO = 20 current nice value in parent is 0 current nice value in child is 0, adjusting by 0 now child nice value is 0 parent count = 13347219 child count = 13357561 

./a.out 20 //child nice set to 20

NZERO = 20 current nice value in parent is 0 current nice value in child is 0, adjusting by 20 now child nice value is 19 parent count = 29770491 ubuntu@VM-0-2-ubuntu:~$ child count = 441330 

Test program(I made a little modification), from Section 8.16, APUE:

apue.h is merely a header wrapper
err_sys() is also a error handler wrapper, you can use printf temporarily.

#include "apue.h" #include <errno.h> #include <sys/time.h> #if defined(MACOS) #include <sys/syslimits.h> #elif defined(SOLARIS) #include <limits.h> #elif defined(BSD) #include <sys/param.h> #endif unsigned long long count; struct timeval end; void checktime(char *str) { struct timeval tv; gettimeofday(&tv, NULL); if (tv.tv_sec >= end.tv_sec && tv.tv_usec >= end.tv_usec) { printf("%s count = %lld\n", str, count); exit(0); } } int main(int argc, char *argv[]) { pid_t pid; char *s; int nzero, ret; int adj = 0; setbuf(stdout, NULL); #if defined(NZERO) nzero = NZERO; #elif defined(_SC_NZERO) nzero = sysconf(_SC_NZERO); #else #error NZERO undefined #endif printf("NZERO = %d\n", nzero); if (argc == 2) adj = strtol(argv[1], NULL, 10); gettimeofday(&end, NULL); end.tv_sec += 10; /* run for 10 seconds */ if ((pid = fork()) < 0) { err_sys("fork failed"); } else if (pid == 0) { /* child */ s = "child"; printf("current nice value in child is %d, adjusting by %d\n", nice(0), adj); errno = 0; if ((ret = nice(adj)) == -1 && errno != 0) err_sys("child set scheduling priority"); printf("now child nice value is %d\n", ret); } else { /* parent */ s = "parent"; printf("current nice value in parent is %d\n", nice(0)); } for(;;) { if (++count == 0) err_quit("%s counter wrap", s); checktime(s); } } 

Complete source code link: https://wandbox.org/permlink/8iryAZ48sIbaq27y

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.