有没有一种可控的方法可以让我的 C++ 程序在在线评委上变慢?

Is there a controllable way to slow down my C++ program on online judges?

我正在寻找一种可控的方式(易于设置延迟时间)来减慢我在在线评委上的C++解决方案。 (主要针对 UVa,g++ 4.8.2 -lm -lcrypt -O2 -std=c++11 -pipe

我试过以下代码:

{auto start=std::chrono::high_resolution_clock::now();
while (std::chrono::duration<double,std::milli>
    (std::chrono::high_resolution_clock::now()-start).count()<2000);
}

但是解决方案慢了大约1.6秒,而不是预期的2秒,我不知道为什么。

我也尝试了std::this_thread::sleep_forusleep()来自<unistd.h>,但是这些几乎没有影响在线判断的运行时间。

对于std::this_thread::sleep_for,我试过:

std::this_thread::sleep_for(std::chrono::milliseconds(2600));

之所以要这样做,是因为我的老师经常给这些在线评委布置问题,而我们的作业评分员会将我们的解决方案提交给那些在线评委,以检查他们是否可以获得 AC (Accepted)。结果,我的解决方案将在排名系统中被计入两次,我认为这对后来的用户不公平,尤其是当我的解决方案排在排名榜首时。所以我更愿意放慢我的解决方案,以减少对其他用户的影响,然后再将其提交到作业评分系统。

我已经为你想要的东西写了一个小测试用例,但我无法重现,std::this_thread::sleep_for() 没有给出预期的结果。

#include <thread>
#include <chrono>

int main() {
        std::this_thread::sleep_for(std::chrono::seconds(2));
        return 0;
}

当 运行 和 time ./slow 时,这将给我(使用 g++ -o slow slow.cpp -std=c++11 编译):

real    0m2.001s
user    0m0.000s
sys     0m0.000s

因此给出了大约 2 秒的预期时间。

请注意,std::this_thread::sleep_for 将阻塞当前线程 至少 给定的持续时间。所以它可能比预期的阻塞时间长几毫秒。见 here:

Blocks the execution of the current thread for at least the specified sleep_duration.

A steady clock is used to measure the duration. This function may block for longer than sleep_duration due to scheduling or resource contention delays.

现代在线判断系统通常有time limit(计算进程系统时间)和real time limit(计算实际经过的时间)的区别。因此,您的代码在 2 sec 期间从进程时间获得 1.6 sec,因为您的进程在服务器上获得大约 80% cpu

在线判断系统通常不依赖于实际执行时间。取而代之的是,他们测量用户 CPU 时间或用户 + 系统(取决于实现)。

这意味着你应该消耗2秒的CPU时间而不是执行2秒的实时时间。没有独立于系统的方法可以做到这一点。如果 Linux 上的服务器是 运行,您可以尝试此解决方案:How do I get the total CPU usage of an application from /proc/pid/stat?。但是如果在线判断系统足够聪明,它可能会阻止这些动作。

如果您想在给定的时间内暂停执行程序,那么 std::this_thread::sleep_for 是可行的方法。但是请注意,它 确实 让您的线程休眠。也就是说,它在休眠时放弃 CPU。如果基准环境测量的是 CPU 时间而不是墙上时间,那么睡眠将无济于事。相反,您要做的是给 CPU 一些无用的工作。 (不)幸运的是,编译器非常善于消除无用的工作,因此您必须小心。

您可以使用 time (1) 实用程序来测量 CPU 和您的程序消耗的时间。

此程序休眠两秒。

#include <chrono>
#include <thread>

int
main()
{
  std::this_thread::sleep_for(std::chrono::seconds {2});
}
$ g++ -o wall -std=c++14 -Wall -Wextra -Werror -pedantic wall.cxx -pthread
$ time ./wall

real    0m2.003s
user    0m0.000s
sys     0m0.000s

如您所见,经过的“实际”时间几乎恰好是两秒,但 CPU 时间(详见用户模式和内核使用的 CPU 时间)可以忽略不计。

这个程序浪费了两秒钟的 CPU 时间。

#include <ctime>

int
main()
{
  const auto t0 = std::clock();
  while ((std::clock() - t0) / CLOCKS_PER_SEC < 2)
    continue;
}
$ g++ -o cpu1 -std=c++14 -Wall -Wextra -Werror -pedantic cpu1.cxx
$ time ./cpu1

real    0m2.003s
user    0m0.530s
sys     0m1.470s

同样,总的(“实际”)执行时间是两秒,但这次,我们还在用户模式下花费了大约半秒,在内核模式下花费了一个半秒(由于许多调用 clock).

您可以通过在用户模式下做更多工作来改变这一点。例如,我们可以做一些愚蠢的循环,而不是立即再次调用 std::clock

#include <ctime>

int
main()
{
  const auto t0 = std::clock();
  while ((std::clock() - t0) / CLOCKS_PER_SEC < 2)
    {
      int dummy;
      volatile int * pdummy = &dummy;
      for (int i = 0; i < 1'000'000; ++i)
        *pdummy = i;
    }
}
$ g++ -o cpu2 -std=c++14 -Wall -Wextra -Werror -pedantic cpu2.cxx
$ time ./cpu2

real    0m2.005s
user    0m2.003s
sys     0m0.000s

这一次,几乎所有 CPU 周期都浪费在了用户模式下。如果您的计算机需要太长的时间来进行一百万次迭代,您可能需要修改这个幻数。