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+.
你身边的环境竞争条件FastestMemory,SlowestMemory和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);
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)
中间方法:
你可以不喜欢服用全班的显示器,或者通过使用类,等等.你可以做一个单独的显示器本身不包含曝光监控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;
}
}
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书籍之一.
我最近注意到,除非你添加,否则你当前的代码永远不会终止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#代码是否有类似的竞争条件.
...但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内部.所以你的高度"并发"代码在这一点上会很好地序列化.只需将其删除并重新测试即可.