我正在测试在Java和C#的32核心服务器上产生许多运行相同功能的线程。我使用该函数的1000次迭代运行该应用程序,该函数使用线程池在1,2,4,8,16或32个线程中进行批处理。
在1,2,4,8和16个并发线程中,Java的速度至少是C#的两倍。但是,随着线程数量的增加,这种差距逐渐缩小,C#的平均运行时间缩短了32个线程,但是Java偶尔需要2000毫秒(而两种语言通常要运行400毫秒)。随着线程迭代所花费的时间激增,Java开始变得越来越糟。
编辑,这是Windows Server 2008。
EDIT2我更改了以下代码,以显示使用Executor Service线程池。我还安装了Java7。
我在热点VM中设置了以下优化:
-XX:+ UseConcMarkSweepGC -Xmx 6000
但是它仍然没有使事情变得更好。代码之间的唯一区别是im使用以下线程池,而对于C#版本,我们使用:
http://www.c++odeproject.com/Articles/7933/Smart-Thread-Pool
有没有办法使Java更优化? Perhaos您可以解释为什么我看到这种性能的大幅下降?
有没有更有效的Java线程池?
(请注意,我并不是说要更改测试功能)
import java.io.DataOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
public class PoolDemo {
static long FastestMemory = 2000000;
static long SlowestMemory = 0;
static long TotalTime;
static int[] FileArray;
static DataOutputStream outs;
static FileOutputStream fout;
static Byte myByte = 0;
public static void main(String[] args) throws InterruptedException, FileNotFoundException {
int Iterations = Integer.parseInt(args[0]);
int ThreadSize = Integer.parseInt(args[1]);
FileArray = new int[Iterations];
fout = new FileOutputStream("server_testing.csv");
// fixed pool, unlimited queue
ExecutorService service = Executors.newFixedThreadPool(ThreadSize);
ThreadPoolExecutor executor = (ThreadPoolExecutor) service;
for(int i = 0; i<Iterations; i++) {
Task t = new Task(i);
executor.execute(t);
}
for(int j=0; j<FileArray.length; j++){
new PrintStream(fout).println(FileArray[j] + ",");
}
}
private static class Task implements Runnable {
private int ID;
public Task(int index) {
this.ID = index;
}
public void run() {
long Start = System.currentTimeMillis();
int Size1 = 100000;
int Size2 = 2 * Size1;
int Size3 = Size1;
byte[] list1 = new byte[Size1];
byte[] list2 = new byte[Size2];
byte[] list3 = new byte[Size3];
for(int i=0; i<Size1; i++){
list1[i] = myByte;
}
for (int i = 0; i < Size2; i=i+2)
{
list2[i] = myByte;
}
for (int i = 0; i < Size3; i++)
{
byte temp = list1[i];
byte temp2 = list2[i];
list3[i] = temp;
list2[i] = temp;
list1[i] = temp2;
}
long Finish = System.currentTimeMillis();
long Duration = Finish - Start;
TotalTime += Duration;
FileArray[this.ID] = (int)Duration;
System.out.println("Individual Time " + this.ID + " \t: " + (Duration) + " ms");
if(Duration < FastestMemory){
FastestMemory = Duration;
}
if (Duration > SlowestMemory)
{
SlowestMemory = Duration;
}
}
}
}
解决方案如下:
概要
以下是原始响应,更新1和更新2。更新1讨论了如何通过使用并发结构来处理围绕测试统计变量的竞争条件。 Update 2是处理竞态条件问题的简单得多的方法。希望我再也没有更新-抱歉,响应时间太长,但是多线程编程很复杂!
原始回应
The only difference between the code is that im using the below threadpool
我想说那是绝对巨大的差异。当它们的线程池实现是完全不同的代码块(写在用户空间中)时,很难比较这两种语言的性能。线程池的实现可能会对性能产生巨大影响。
您应该考虑使用Java自己的内置线程池。请参阅
ThreadPoolExec++utor及其组成的整个
java.util.c++oncurrent软件包。
Exec++utors类为池提供了方便的静态工厂方法,并且是一个很好的高级接口(interface)。您需要的只是JDK 1.5+,尽管更新,更好。其他张贴者提到的fork / join解决方案也是该软件包的一部分-如前所述,它们要求1.7+。
更新1-通过使用并发结构解决竞争条件
围绕
FastestMemory
,
SlowestMemory
和
TotalTime
的设置,您具有竞争条件。对于前两个,您要进行
<
和
>
测试,然后进行多个步骤的设置。这不是原子的。当然,另一个线程可能会在测试和设置之间更新这些值。
+=
的
TotalTime
设置也是非原子的:测试并变相设置。
这是一些建议的修复程序。
TotalTime
这里的目标是
+=
的线程安全的原子
TotalTime
。
// At the top of everything
import java.util.concurrent.atomic.AtomicLong;
...
// In PoolDemo
static AtomicLong TotalTime = new AtomicLong();
...
// In Task, where you currently do the TotalTime += piece
TotalTime.addAndGet (Duration);
FastestMemory / SlowestMemory
此处的目标是在原子步骤中分别测试和更新
FastestMemory
和
SlowestMemory
,因此没有线程可以在测试步骤和更新步骤之间滑入以引起竞争状态。
最简单的方法:
使用类本身作为监视器,保护变量的测试和设置。我们需要一个包含变量的监视器,以确保同步的可见性(感谢@ A.H。来捕获它。)我们必须使用类本身,因为所有内容都是
static
。
// In Task
synchronized (PoolDemo.class) {
if (Duration < FastestMemory) {
FastestMemory = Duration;
}
if (Duration > SlowestMemory) {
SlowestMemory = Duration;
}
}
中间方法:
您可能不喜欢将整个类都用作监视器,也可能不喜欢通过使用该类公开监视器,等等。您可以创建一个单独的监视器,该监视器本身不包含
FastestMemory
和
SlowestMemory
,但是您将遇到同步可见性问题。您可以使用
volatile
关键字解决此问题。
// In PoolDemo
static Integer _monitor = new Integer(1);
static volatile long FastestMemory = 2000000;
static volatile long SlowestMemory = 0;
...
// In Task
synchronized (PoolDemo._monitor) {
if (Duration < FastestMemory) {
FastestMemory = Duration;
}
if (Duration > SlowestMemory) {
SlowestMemory = Duration;
}
}
高级方法:
在这里,我们使用
java.util.concurrent.atomic
类代替监视器。在激烈的竞争中,此方法的性能应优于
synchronized
方法。试试看。
// At the top of everything
import java.util.concurrent.atomic.AtomicLong;
. . . .
// In PoolDemo
static AtomicLong FastestMemory = new AtomicLong(2000000);
static AtomicLong SlowestMemory = new AtomicLong(0);
. . . . .
// In Task
long temp = FastestMemory.get();
while (Duration < temp) {
if (!FastestMemory.compareAndSet (temp, Duration)) {
temp = FastestMemory.get();
}
}
temp = SlowestMemory.get();
while (Duration > temp) {
if (!SlowestMemory.compareAndSet (temp, Duration)) {
temp = SlowestMemory.get();
}
}
让我知道之后发生了什么。它可能无法解决您的问题,但是围绕跟踪您的表现的非常变量之间的竞争状况实在是太危险了,无法忽略。
我最初将此更新发布为评论,但将其移至此处,以便有空间显示代码。此更新经过了几次迭代-感谢
A.H.捕获了我在较早版本中遇到的错误。此更新中的所有内容都将取代注释中的任何内容。
最后但并非最不重要的一点是,涵盖所有这些 Material 的出色资源是
Java Conc++urrency in Practice,这是有关Java并发性的最佳书籍,也是总体上最好的Java书籍之一。
更新2-以更简单的方式解决比赛条件
我最近注意到,除非添加
executorService.shutdown()
,否则您当前的代码将永远不会终止。也就是说,必须终止位于该池中的非守护程序线程,否则主线程将永远不会退出。这使我想到,既然我们必须等待所有线程退出,为什么不比较它们完成之后的持续时间,从而完全绕过
FastestMemory
等的并发更新?这比较简单,可能会更快。没有更多的锁定或CAS开销了,无论如何,您已经在结束时进行了
FileArray
的迭代。
我们可以利用的另一件事是,并发更新
FileArray
是绝对安全的,因为每个线程都在写入一个单独的单元,并且在写入过程中没有读取
FileArray
。
这样,您可以进行以下更改:
// In PoolDemo
// This part is the same, just so you know where we are
for(int i = 0; i<Iterations; i++) {
Task t = new Task(i);
executor.execute(t);
}
// CHANGES BEGIN HERE
// Will block till all tasks finish. Required regardless.
executor.shutdown();
executor.awaitTermination(10, TimeUnit.SECONDS);
for(int j=0; j<FileArray.length; j++){
long duration = FileArray[j];
TotalTime += duration;
if (duration < FastestMemory) {
FastestMemory = duration;
}
if (duration > SlowestMemory) {
SlowestMemory = duration;
}
new PrintStream(fout).println(FileArray[j] + ",");
}
. . .
// In Task
// Ending of Task.run() now looks like this
long Finish = System.currentTimeMillis();
long Duration = Finish - Start;
FileArray[this.ID] = (int)Duration;
System.out.println("Individual Time " + this.ID + " \t: " + (Duration) + " ms");
也尝试一下这种方法。
您绝对应该检查C#代码是否存在类似的竞争状况。