有没有办法限制进程组中的绝对CPU时间(以CPU秒为单位)?
my-process
看起来是一个不错的选择,但如果cpu.cfs_period_us
分叉,那么进程组中的每个进程都会获得自己的限制。整个过程组可以每隔9秒钟使用任意一段时间。
similar question上接受的答案是使用cgroup但不解释如何。但是,还有其他答案(Limit total CPU usage with cgroups)说这在cgroup中是不可能的,并且只能限制相对cpu的使用(例如,每1秒0.2秒)。
Liran Funaro建议长时间使用ulimit
(https://stackoverflow.com/a/43660834/892961),但配额的参数最多可以为1秒。因此,即使是很长一段时间,我也不知道如何设置10秒或1小时的CPU时间限制。
如果.
和cgroups无法做到这一点,还有其他办法吗?
答案 0 :(得分:2)
你可以用cgroups来做。以root身份做:
# Create cgroup
cgcreate -g cpu:/limited
# set shares (cpu limit)
cgset -r cpu.shares=256 limited
# run your program
cgexec -g cpu:limited /my/hungry/program
或者,您可以使用可以定期冻结代码的cpulimit
程序。 cgroups是最先进的方法。
设置固定的cpu份额:
cgcreate -g cpu:/fixedlimit
# allow fix 25% cpu usage (1 cpu)
cgset -r cpu.cfs_quota_us=25000,cpu.cfs_period_us=100000 fixedlimit
cgexec -g cpu:fixedlimit /my/hungry/program
事实证明,目标是在测量时将运行时限制在特定秒数。设置所需的cgroup限制(为了获得公平的沙箱)后,您可以通过运行以下目标来实现此目标:
((time -p timeout 20 cgexec -g cpu:fixedlimit /program/to/test ) 2>&1) | grep user
20秒后程序将被停止,无论如何,我们可以解析用户时间(或系统或实时)来评估它的性能。
答案 1 :(得分:1)
这不是直接回答问题,而是指有关OP实际需求的讨论。
如果您的竞争者忽略了除CPU时间以外的所有内容,则可能存在根本缺陷。例如,可以简单地将结果缓存到主存储设备中。由于不计算存储访问时间,因此它可能具有最少的CPU周期,但实际性能较差。 完美的作法是简单地通过Internet将数据发送到另一台计算机,该计算机计算任务然后返回答案。这将以零周期完成任务。 您实际上是想测量“实时”时间,并将此过程分配给系统中最高优先级(或实际上秘密地运行)。
在检查学生的作业时,我们只是使用了一个不切实际的时间限制(例如,应将10分钟的程序设置为5分钟),然后在未及时完成的情况下终止该过程,并导致提交失败。
如果您想选择一个获胜者,则只需多次重新运行最佳竞争对手以确保其结果的有效性。
答案 2 :(得分:0)
我找到了一个适合我的解决方案。它仍然远非完美(在使用之前阅读警告)。我对bash脚本有些新意,所以欢迎任何关于此的评论。
#!/bin/bash
#
# This script tries to limit the CPU time of a process group similar to
# ulimit but counting the time spent in spawned processes against the
# limit. It works by creating a temporary cgroup to run the process in
# and checking on the used CPU time of that process group. Instead of
# polling in regular intervals, the monitoring process assumes that no
# time is lost to I/O (i.e., wall clock time = CPU time) and checks in
# after the time limit. It then updates its assumption by comparing the
# actual CPU usage to the time limit and waiting again. This is repeated
# until the CPU usage exceeds its limit or the monitored process
# terminates. Once the main process terminates, all remaining processes
# in the temporary cgroup are killed.
#
# NOTE: this script still has some major limitations.
# 1) The monitored process can exceed the limit by up to one second
# since every iteration of the monitoring process takes at least that
# long. It can exceed the limit by an additional second by ignoring
# the SIGXCPU signal sent when hitting the (soft) limit but this is
# configurable below.
# 2) It assumes there is only one CPU core. On a system with n cores
# waiting for t seconds gives the process n*t seconds on the CPU.
# This could be fixed by figuring out how many CPUs the process is
# allowed to use (using the cpuset cgroup) and dividing the remaining
# time by that. Since sleep has a resolution of 1 second, this would
# still introduce an error of up to n seconds.
set -e
if [ "$#" -lt 2 ]; then
echo "Usage: $(basename "$0") TIME_LIMIT_IN_S COMMAND [ ARG ... ]"
exit 1
fi
TIME_LIMIT=$1
shift
# To simulate a hard time limit, set KILL_WAIT to 0. If KILL_WAIT is
# non-zero, TIME_LIMIT is the soft limit and TIME_LIMIT + KILL_WAIT is
# the hard limit.
KILL_WAIT=1
# Update as necessary. The script needs permissions to create cgroups
# in the cpuacct hierarchy in a subgroup "timelimit". To create it use:
# sudo cgcreate -a $USER -t $USER -g cpuacct:timelimit
CGROUPS_ROOT=/sys/fs/cgroup
LOCAL_CPUACCT_GROUP=timelimit/timelimited_$$
LOCAL_CGROUP_TASKS=$CGROUPS_ROOT/cpuacct/$LOCAL_CPUACCT_GROUP/tasks
kill_monitored_cgroup() {
SIGNAL=$1
kill -$SIGNAL $(cat $LOCAL_CGROUP_TASKS) 2> /dev/null
}
get_cpu_usage() {
cgget -nv -r cpuacct.usage $LOCAL_CPUACCT_GROUP
}
# Create a cgroup to measure the CPU time of the monitored process.
cgcreate -a $USER -t $USER -g cpuacct:$LOCAL_CPUACCT_GROUP
# Start the monitored process. In case it fails, we still have to clean
# up, so we disable exiting on errors.
set +e
(
set -e
# In case the process doesn't fork a ulimit is more exact. If the
# process forks, the ulimit still applies to each child process.
ulimit -t $(($TIME_LIMIT + $KILL_WAIT))
ulimit -S -t $TIME_LIMIT
cgexec -g cpuacct:$LOCAL_CPUACCT_GROUP --sticky $@
)&
MONITORED_PID=$!
# Start the monitoring process
(
REMAINING_TIME=$TIME_LIMIT
while [ "$REMAINING_TIME" -gt "0" ]; do
# Wait $REMAINING_TIME seconds for the monitored process to
# terminate. On a single CPU the CPU time cannot exceed the
# wall clock time. It might be less, though. In that case, we
# will go through the loop again.
sleep $REMAINING_TIME
CPU_USAGE=$(get_cpu_usage)
REMAINING_TIME=$(($TIME_LIMIT - $CPU_USAGE / 1000000000))
done
# Time limit exceeded. Kill the monitored cgroup.
if [ "$KILL_WAIT" -gt "0" ]; then
kill_monitored_cgroup XCPU
sleep $KILL_WAIT
fi
kill_monitored_cgroup KILL
)&
MONITOR_PID=$!
# Wait for the monitored job to exit (either on its own or because it
# was killed by the monitor).
wait $MONITORED_PID
EXIT_CODE=$?
# Kill all remaining tasks in the monitored cgroup and the monitor.
kill_monitored_cgroup KILL
kill -KILL $MONITOR_PID 2> /dev/null
wait $MONITOR_PID 2>/dev/null
# Report actual CPU usage.
set -e
CPU_USAGE=$(get_cpu_usage)
echo "Total CPU usage: $(($CPU_USAGE / 1000000))ms"
# Clean up and exit with the return code of the monitored process.
cgdelete cpuacct:$LOCAL_CPUACCT_GROUP
exit $EXIT_CODE