一点背景知识 - 我正在运行以下设置:
- i5 8300H(4核8线程)
- 32 GB 内存
- Ubuntu 19.10
- GCC 9.2.1、C++17 标准
我有一个线程管理器——本质上是一个对象,你可以将一些数据传递给它,你给它一个可调用的对象,然后你可以并行运行任务,并且线程管理器能够使线程超时(如果某些任务挂起,因为我正在做的事情可能就是这种情况),分批给他们数据,等等。
此行为的伪代码如下:
function do_tasks(task, data, batch_size, timeout, threads, output_streams):
assert arguments_are_valid()
failed_tasks = []
while(true):
if data.size() == 0:
break
for thread in threads:
if thread.running():
stop_thread(thread)
if thread.results.size() == 0:
failed_tasks <- failed_tasks + thread.given_data
else:
data <- data + thread.given_data(data.begin() + thread.results.size(), thread.given_data.end())
start_thread(thread, task, take_data(data, min(batch_size, data.size()))
wait_for_threads_completed_or_timeout(threads, timeout)
return failed_tasks
我没有使用任何奇异的东西,这一切都是使用普通的 std::thread、std::list、std::future 和 std::promise 完成的。
长话短说,你给线程它的数据。当您评估线程所做的事情时,如果整个批次失败(即没有解决任何数据元素),则整个批次将被转移到一个 failed_tasks 容器中,该容器稍后会被返回。这些失败的批次随后会通过运行 batch_size 为 1 的任务来解决(因此,当任务超时时,确实需要手动检查),但这部分并不重要。如果至少有 1 个数据元素已解析,则将未解析的部分传输回数据容器。这会一直运行,直到所有数据元素都被解析或标记为 failed_tasks。
现在,通常,假设我在 7 个线程上的 100000 个元素上运行它。发生的情况是,我第一次运行它时,多达 2000 个元素超时。第二次也有类似的情况,500-2000 个元素超时。但这是奇怪的部分 - 运行几次后,我得到了预期的行为,大约 2-5 个任务失败。
查看正在运行的函数,它平均单线程每秒可以处理 10500 个数据元素。它的最小运行时间不到一纳秒,而观察到的最大运行时间是几毫秒(它将数据与正则表达式匹配,并且存在或多或少充当 DoS 攻击的序列,因此会大大减慢执行速度) . 在 7 个线程上运行通常可以平均每秒处理 70000 个数据元素,因此效率约为 95%。但是,当最初的几次运行发生时,这会下降到每秒 55000 个数据元素,这大约是 75% 的效率,性能显着下降。现在,性能并不是那么关键(我需要每秒处理 20000 个数据元素,一个任务 2 个线程就足够了),
我读过这个:
但似乎该行为是由 JIT 解释器引起的,这是 C++ 在编译时所没有的。我知道 std::thread 开销,但怀疑它不是那么大。我在这里所经历的类似于热身,但我从未听说过线程有热身期。即使我更改数据(每次运行,不同的数据集),这种行为也是一致的,所以我怀疑没有缓存可以加速它。
实现可能是正确的,它已经过审查和正式测试。代码主要是 C 和 C++ 并且正在积极维护,所以我怀疑这不是错误。但是我在互联网上找不到其他人有同样的问题,所以这让我想知道我们是否缺少任何东西。
有人知道为什么会发生这种热身吗?
编辑:工作是这样执行的:
for(ull i = 0; i != batch_size && future.wait_for(nanoseconds(0)) == future_status::timeout; ++i)
{
//do stuff
}
线程运行的函数接收一个未来,线程可以在对下一个数据元素运行任务之前检查该未来,这里称为未来。