【问题标题】:Multi threaded application on server slower than single threaded (unlike in JUnit tests)服务器上的多线程应用程序比单线程慢(与 JUnit 测试不同)
【发布时间】:2011-04-08 11:11:29
【问题描述】:

我已将我的应用程序从单线程例程切换到多线程例程。

这在 JUnit 测试中工作得很好。当使用10 线程运行它时,测试需要195 ms 才能完成,而当仅使用一个线程运行它时,应用程序需要406 ms 才能完成。所以显然有性能优势。

但是当在服务器上运行它时,应用程序现在需要比单线程时更长的时间。

基本上,我的应用程序读取 csv 文件中的一行,将其中一个值放入一个集合中,然后将该行打印到另一个文件中。 JUnit 测试中输入文件的大小约为35 行长,服务器上的文件大小约为6 000 000 行长。

放置这些值的集合是一个同步的HashSet,它可以包含Long 对象。

我正在使用 Java VisualVM 监控我的应用程序,但不幸的是我不知道要查找什么。

你对如何解决这个性能危机有什么建议吗?


P。 S.:大多数时候我的线程被标记为等待,但我不知道它们是否真的在等待,或者它们是否太快以至于 Java VisualVM 无​​法显示它。


为了进一步阐明我的例程:我单线程读取文件,但是一旦读取该行,我就会将生成的对象传递给 Runnable,将其放入一个集合中并将其打印到另一个文件中。同时读取下一行并将其传递给其他线程。


正如我在日志文件中看到的那样,线程正在做某事,而不仅仅是等待。但是有某些跳跃,比100 ms 更长的时间段没有发生任何事情。


其中一个跳跃:

2011-04-08 12:27:16,580 DEBUG [Thread-10]  runnables.Runner - 7070927
2011-04-08 12:27:16,580 DEBUG [Thread-10]  runnables.Runner - 9058759
2011-04-08 12:27:16,580 DEBUG [Thread-10]  runnables.Runner - 7030928
2011-04-08 12:27:16,580 DEBUG [Thread-10]  runnables.Runner - 15301035
2011-04-08 12:27:16,684 DEBUG [Thread-10]  runnables.Runner - 7700929
2011-04-08 12:27:16,684 DEBUG [Thread-10]  runnables.Runner - 17116545
2011-04-08 12:27:16,685 DEBUG [Thread-10]  runnables.Runner - 4933581
2011-04-08 12:27:16,685 DEBUG [Thread-10]  runnables.Runner - 2861116

注意:当时没有发生 GC。


正如在下面的评论中所写:我正在使用线程池。我的线程正在为同一个输出文件而战*。他们都写到synchronized 方法。


即使我将胎面池的大小减少到一个,性能仍然很糟糕。与以前的实现相比没有什么。那不就排除了 IO 依赖或者线程切换之类的东西吗?


我现在修改了我的代码,所以在Runnable 内几乎什么都没做。没有Set,没有写作。只有一个日志语句。我仍然得到那些jumps。 所以我排除了一些人提出的写作或Set问题。当只运行一个线程时,我也得到了这些空闲时间。所以线程切换似乎也不是问题。

【问题讨论】:

  • 您是否尝试过使用长文件运行单元测试?另外,目前还不清楚如何通过多线程读取文件。
  • 感谢您的提示。在问题底部查看我的更新。
  • 可能需要查看一些代码来帮助诊断这个。
  • 可能同步读取/写入文件和设置哈希集比没有它的一个线程完成的相同工作需要更长的时间。您的单元测试可能效果更好,因为输入/输出文件很小并且由操作系统缓存。
  • 您是使用一种线程池形式还是最终拥有 600 万个线程? thr 线程是否还会尝试将内容写入相同的输出文件或不同的输出文件(即它们是否在争夺该资源?)

标签: java multithreading performance debugging


【解决方案1】:

您的测试文件非常小,因此可能会被整个 I/O 堆栈中的任何预读层完全读取。这使得整个执行 CPU 受限。使用更多线程,您可以使用更多 CPU 并更快地完成任务。

真正的文件 OTOH 要长得多,所以问题变成了 IO-bound。 CPU 大部分时间都在等待读取数据。在单个线程上,没有争用,并且 IO 可能更线性;而多线程版本更有可能产生大量的磁盘寻道(到目前为止,您可以在当今的硬件上执行最慢的操作)

根据经验,如果您从磁盘或网络读取数据并且不对其进行繁重的处理,则最好使用单线程。

【讨论】:

  • 我正在读取文件单线程并创建每个 csv 行的对象。这个对象被传递给Runnable。然后开始多线程部分。所以我从不使用多个线程访问输入文件。
【解决方案2】:

您所指的“跳转”是线程之间的切换时间。因为总执行时间是有限的,所以一个线程的执行时间会随着线程数的增加而变短。如果您有很多线程,您的调度程序最终会切换线程并且没有线程做任何工作。从一个线程切换到另一个线程需要一定的固定时间。如果您的线程不使用多个内核并执行完全相同的操作,那么在将多线程与单线程进行比较时,您最终的速度会更差。

【讨论】:

    【解决方案3】:

    我不知道到底是什么问题,但似乎是由于 Executor 接口的错误实现造成的。

    我现在正在使用

    ExecutorService executor = Executors.newFixedThreadPool(nThreads);
    

    一切正常。

    • 单线程例程的持续时间: 17.12分钟
    • 10 线程例程的持续时间: 13.45分钟

    我发现了错误的代码:

    Thread.sleep(100);
    

    在线程队列已满时调用。

    【讨论】:

      猜你喜欢
      • 2017-05-31
      • 1970-01-01
      • 2012-09-05
      • 1970-01-01
      • 2011-02-07
      • 2017-11-17
      • 2020-08-15
      • 1970-01-01
      相关资源
      最近更新 更多