Java在许多内核上的扩展比C#差得多?

mez*_*hic 16 .net c# java eclipse jvm-hotspot

我正在测试在32核心服务器上为Java和C#运行相同功能的许多线程的产生.我使用函数的1000次迭代运行应用程序,使用线程池对1,2,4,8,16或32个线程进行批处理.

在1,2,4,8和16个并发线程中Java至少是C#的两倍.但是,随着线程数量的增加,间隙关闭,32个线程C#的平均运行时间几乎相同,但Java偶尔需要2000ms(而两种语言通常运行时间约为400ms).在每个线程迭代所花费的时间内,Java开始变得更糟.

编辑这是Windows Server 2008

EDIT2我已经使用Executor Service线程池更改了下面的代码.我还安装了Java 7.

我在热点VM中设置了以下优化:

-XX:+ UseConcMarkSweepGC -Xmx 6000

但它仍然没有让事情变得更好.代码之间的唯一区别是我使用下面的线程池和我们使用的C#版本:

http://www.codeproject.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;
        }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

spa*_*ead 19

摘要

下面是原始响应,更新1和更新2.更新1讨论了使用并发结构处理测试统计变量周围的竞争条件.更新2是处理竞争条件问题的一种更简单的方法.希望没有更多来自我的更新 - 抱歉响应的长度,但多线程编程很复杂!

原始回应

代码之间的唯一区别是我使用下面的线程池

我想说这是一个绝对巨大的差异.当两个语言的线程池实现完全不同的代码块(用户空间编写)时,很难比较两种语言的性能.线程池实现可能会对性能产生巨大影响.

您应该考虑使用Java自己的内置线程池.请参阅ThreadPoolExecutor以及它所属的整个java.util.concurrent包.该执行人类有游泳池方便的静态工厂方法,是一个很好的更高层次的接口.你需要的只是JDK 1.5+,虽然越新越好.其他海报提到的fork/join解决方案也是这个包的一部分 - 如上所述,它们需要1.7+.

更新1 - 使用并发结构解决竞争条件

你身边的环境竞争条件FastestMemory,SlowestMemoryTotalTime.对于前两个,您正在进行<>测试,然后在多个步骤中进行设置.这不是原子的; 当然,另一个线程有可能在测试和设置之间更新这些值.所述+=的设置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); 
Run Code Online (Sandbox Code Playgroud)

FastestMemory/SlowestMemory

这里的目标是测试和更新,FastestMemory并且SlowestMemory每个都在一个原子步骤中,因此没有线程可以在测试和更新步骤之间滑入以引起竞争条件.

最简单的方法:

使用类本身作为监视器来保护变量的测试和设置.我们需要一个包含变量的监视器,以保证同步可见性(感谢@AH捕获它.)我们必须使用类本身,因为一切都是static.

// In Task
synchronized (PoolDemo.class) {
    if (Duration < FastestMemory) {
        FastestMemory = Duration;
    }

    if (Duration > SlowestMemory) {
        SlowestMemory = Duration;
    }
}
Run Code Online (Sandbox Code Playgroud)

中间方法:

你可以不喜欢服用全班的显示器,或者通过使用类,等等.你可以做一个单独的显示器本身不包含曝光监控FastestMemorySlowestMemory,但后来你会遇到同步知名度的问题.您可以使用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;
    }
}
Run Code Online (Sandbox Code Playgroud)

高级方法:

这里我们使用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();
    }
}
Run Code Online (Sandbox Code Playgroud)

让我知道在此之后会发生什么.它可能无法解决您的问题,但跟踪您的性能的变量周围的竞争条件太危险而无法忽略.

我最初发布此更新作为评论,但在此处移动,以便我有空间显示代码.此更新已经过了几次迭代 - 感谢AH用于捕获我在早期版本中遇到的错误.此更新中的任何内容都将取代评论中的任何内容.

最后但并非最不重要的是,涵盖所有这些材料的优秀资源是Java Concurrency 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");
Run Code Online (Sandbox Code Playgroud)

也可以尝试这种方法.

你肯定应该检查你的C#代码是否有类似的竞争条件.


A.H*_*.H. 5

...但Java偶尔需要2000毫秒......

    byte[] list1 = new byte[Size1];
    byte[] list2 = new byte[Size2];
    byte[] list3 = new byte[Size3];
Run Code Online (Sandbox Code Playgroud)

hickups将是垃圾收集器清理你的阵列.如果你真的想调整一下,我建议你为数组使用某种缓存.

编辑

这个

   System.out.println("Individual Time " + this.ID + " \t: " + (Duration) + " ms");
Run Code Online (Sandbox Code Playgroud)

做一个或多个synchronized内部.所以你的高度"并发"代码在这一点上会很好地序列化.只需将其删除并重新测试即可.