4

我正在开发一个基准应用程序,它使用用户定义的线程数来进行处理。我还在为基准测试结果开发可视化应用程序。

基准测试本身是用 C++ 编写的(并使用 pthreads 进行线程处理),而可视化工具是用 Python 编写的。

现在,我正在做的是stdout从基准到可视化器的管道。这样做的好处是能够使用一种工具,例如netcat在一台机器上运行基准测试并在另一台机器上运行可视化工具。

应用程序的高级图

关于基准的一点:

  • 它非常受 CPU 限制
  • 每个线程每隔 10 毫秒写入一次重要数据(即我需要用于可视化器的数据)。
  • 每个打印的数据是一行 5 到 20 个字符。
  • 如前所述,线程数是高度可变的(可以是 1、2、40 等)
  • 即使数据不被破坏很重要(例如,一个线程在printf/期间抢占另一个线程cout,导致打印的数据与另一个线程上的输出交错),写入正确完成并不是很重要命令。

关于最后一点的示例:

// Thread 1 prints "I'm one\n" at the 3 seconds mark
// thread 2 prints "I'm two\n" at the 4 seconds mark

// This is fine
I'm two
I'm one

// This is not
I'm I'm one
 two

在基准测试中,我从 切换到std::coutprintf因为它更接近write(2),以尽量减少不同线程输出之间交错的机会。

我担心stdout随着线程数量的增加,从多个线程写入会导致瓶颈。非常重要的是,基准的输出可视化部分对资源非常轻,以免扭曲结果。

我正在寻找一种有效的方法来让我的两个应用程序说话而不影响我的基准测试的性能而不是绝对必要的。有任何想法吗?你们中有人解决过这样的问题吗?任何更智能/更清洁的解决方案?

4

4 回答 4

3

写入标准输出不太可能成为任何现实世界问题的性能瓶颈。如果是这样,您要么记录太多,要么对一项任务进行基准测试,该任务太快以至于无法根据背景噪音进行测量。然而,这是一个线程安全错误。您对 printf 与 cout 的选择只是巫术——两者都不是线程安全的。如果您想在多线程环境中使用缓冲 I/O,您需要自己序列化调用(使用pthread_mutex_t,或使用信号量实现队列等...)。如果您想依靠系统调用原子性为您执行此操作(在内部,内核执行完全相同的序列化),您需要自己进行系统调用,而不是依赖 printf “接近”写入。

于 2012-08-29T17:08:23.057 回答
0

假设您有一个符合 POSIX 标准的标准库,则对 stdio 函数的每次调用相对于其他线程都是原子的,因此只要您通过一次printf调用打印出您的行,即使两个线程写一行,它们也不会混合在一起在同一时间。使用 C++进行的每次调用都是如此iostream::operator<<,但如果您编写类似cout << "xxx " << var << endl;的内容,那就是三个调用,而不是一个。

如果您想多次调用 stdio 函数并将其编写为一个单元,您可以使用flockfile(3)。例如:

flockfile(stdout);
printf("data: ");
print_struct(foo);  // a function that calls printf internally
printf("\n");
funlockfile(stdout);

这将导致打印从换行到换行的整个内容,data而不允许其他线程交错。它对 C++ iostreams 也很有用:

flockfile(stdout);
cout << "data: " << x << endl;
funlockfile(stdout);
于 2012-08-29T17:24:28.610 回答
0

首先,在我担心它之前,我会确保它是一个问题。如果写入仅每 10 或 20 毫秒一次,则它们很可能不会打扰任何事情。

否则:“写入”实际上包含两个操作:格式化输出,以及物理输出格式化的字节。第二个可能相当快,因为​​它只是将 5 到 20 个字符从您的进程复制到操作系统的问题。(操作系统将在您从write/WriteFile函数返回后进行物理写入。)如果您在本地格式化,使用std::ostrstream(已弃用,但应该可用) or snprintf,格式化为 local char[],然后在结果上调用 writeor WriteFile,您不需要不需要任何外部同步。

或者,您可以在单独的线程中完成所有写入,只需将请求(带有必要的数据)推送到队列(使用条件变量很容易实现)。

于 2012-08-29T17:13:15.607 回答
0

它们都可以将它们的输出行作为字符串推送到队列中,而另一个线程可以拉取它们并记录它们(单线程,缓冲输出,刷新频率较低)。

于 2012-08-29T17:08:05.900 回答