Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java多线程可伸缩性问题_Java_Multithreading_Jvm_Primitive Types_Visitor Pattern - Fatal编程技术网

Java多线程可伸缩性问题

Java多线程可伸缩性问题,java,multithreading,jvm,primitive-types,visitor-pattern,Java,Multithreading,Jvm,Primitive Types,Visitor Pattern,更多更新 正如所选答案中所解释的,问题在于JVM的垃圾收集算法。 JVM使用卡片标记算法跟踪对象字段中修改的引用。对于字段的每个引用赋值,它会将卡中的一个关联位标记为true——这会导致错误共享,从而阻止缩放。本文详细介绍了以下内容: 选项-XX:+UseCondCardMark(在Java1.7u40及更高版本中)缓解了这个问题,并使它几乎可以完美地扩展 更新 我发现(Park Eung-ju暗示)将一个对象赋给一个字段变量会产生不同。如果我删除该赋值,它将完美缩放。 我认为它可能与Java

更多更新

正如所选答案中所解释的,问题在于JVM的垃圾收集算法。 JVM使用卡片标记算法跟踪对象字段中修改的引用。对于字段的每个引用赋值,它会将卡中的一个关联位标记为true——这会导致错误共享,从而阻止缩放。本文详细介绍了以下内容:

选项-XX:+UseCondCardMark(在Java1.7u40及更高版本中)缓解了这个问题,并使它几乎可以完美地扩展


更新

我发现(Park Eung-ju暗示)将一个对象赋给一个字段变量会产生不同。如果我删除该赋值,它将完美缩放。 我认为它可能与Java内存模型有关——例如,对象引用必须指向有效地址才能显示,但我不能完全确定。在64位机器上,double和Object引用(可能)都有8个字节的大小,所以在我看来,分配double值和Object引用在同步方面应该是相同的

有人有合理的解释吗


这里有一个奇怪的Java多线程可伸缩性问题

我的代码只是迭代一个数组(使用访问者模式)来计算简单的浮点运算,并将结果分配给另一个数组。没有数据依赖关系,也没有同步,因此它应该线性扩展(2个线程快2倍,4个线程快4倍)

当使用基元(双)数组时,它的伸缩性非常好。使用对象类型(例如字符串)数组时,它根本不会缩放(即使字符串数组的值根本没有使用…)

以下是完整的源代码:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.CyclicBarrier;

class Table1 {
    public static final int SIZE1=200000000;
    public static final boolean OBJ_PARAM;

    static {
        String type=System.getProperty("arg.type");
        if ("double".equalsIgnoreCase(type)) {
            System.out.println("Using primitive (double) type arg");
            OBJ_PARAM = false;
        } else {
            System.out.println("Using object type arg");
            OBJ_PARAM = true;
        }
    }

    byte[] filled;
    int[] ivals;
    String[] strs;

    Table1(int size) {
        filled = new byte[size];
        ivals = new int[size];
        strs = new String[size];

        Arrays.fill(filled, (byte)1);
        Arrays.fill(ivals, 42);
        Arrays.fill(strs, "Strs");
    }

    public boolean iterate_range(int from, int to, MyVisitor v) {
        for (int i=from; i<to; i++) {
            if (filled[i]==1) {
                // XXX: Here we are passing double or String argument
                if (OBJ_PARAM) v.visit_obj(i, strs[i]);
                else v.visit(i, ivals[i]);
            }
        }
        return true;
    }
}

class HeadTable {
    byte[] filled;
    double[] dvals;
    boolean isEmpty;

    HeadTable(int size) {
        filled = new byte[size];
        dvals = new double[size];
        Arrays.fill(filled, (byte)0);

        isEmpty = true;
    }

    public boolean contains(int i, double d) {
        if (filled[i]==0) return false;

        if (dvals[i]==d) return true;
        return false;
    }

    public boolean contains(int i) {
        if (filled[i]==0) return false;

        return true;
    }
    public double groupby(int i) {
        assert filled[i]==1;
        return dvals[i];
    }
    public boolean insert(int i, double d) {
        if (filled[i]==1 && contains(i,d)) return false;

        if (isEmpty) isEmpty=false;
        filled[i]=1;
        dvals[i] = d;
        return true;
    }
    public boolean update(int i, double d) {
        assert filled[i]==1;
        dvals[i]=d;
        return true;
    }
}


class MyVisitor {
    public static final int NUM=128;

    int[] range = new int[2];
    Table1 table1;
    HeadTable head;
    double diff=0;
    int i;
    int iv;
    String sv;

    MyVisitor(Table1 _table1, HeadTable _head, int id) { 
        table1 = _table1;
        head = _head;
        int elems=Table1.SIZE1/NUM;
        range[0] = elems*id;
        range[1] = elems*(id+1);
    }

    public void run() {
        table1.iterate_range(range[0], range[1], this);
    }

    //YYY 1: with double argument, this function is called
    public boolean visit(int _i, int _v) {
        i = _i;
        iv = _v;
        insertDiff();
        return true;
    }

    //YYY 2: with String argument, this function is called
    public boolean visit_obj(int _i, Object _v) {
        i = _i;
        iv = 42;
        sv = (String)_v;
        insertDiff();
        return true;
    }

    public boolean insertDiff() {
        if (!head.contains(i)) {
            head.insert(i, diff);
            return true;
        }
        double old = head.groupby(i);
        double newval=Math.min(old, diff);
        head.update(i, newval);
        head.insert(i, diff);
        return true;
    }
}
public class ParTest1 {
    public static int THREAD_NUM=4;

    public static void main(String[] args) throws Exception {
        if (args.length>0) {
            THREAD_NUM = Integer.parseInt(args[0]);
            System.out.println("Setting THREAD_NUM:"+THREAD_NUM);
        }
        Table1 table1 = new Table1(Table1.SIZE1);
        HeadTable head = new HeadTable(Table1.SIZE1);

        MyVisitor[] visitors = new MyVisitor[MyVisitor.NUM];
        for (int i=0; i<visitors.length; i++) {
            visitors[i] = new MyVisitor(table1, head, i);
        }

        int taskPerThread = visitors.length / THREAD_NUM;
        MyThread[] threads = new MyThread[THREAD_NUM];
        CyclicBarrier barrier = new CyclicBarrier(THREAD_NUM+1);
        for (int i=0; i<THREAD_NUM; i++) {
            threads[i] = new MyThread(barrier);
            for (int j=taskPerThread*i; j<taskPerThread*(i+1); j++) {
                if (j>=visitors.length) break;
                threads[i].addVisitors(visitors[j]);
            }
        }

        Runtime r=Runtime.getRuntime();
        System.out.println("Force running gc");
        r.gc(); // running GC here (excluding GC effect)
        System.out.println("Running gc done");

        // not measuring 1st run (excluding JIT compilation effect)
        for (int i=0; i<THREAD_NUM; i++) {
            threads[i].start();
        }
        barrier.await();

        for (int i=0; i<10; i++) {
            MyThread.start = true;

            long s=System.currentTimeMillis();
            barrier.await();
            long e=System.currentTimeMillis();
            System.out.println("Iter "+i+"  Exec time:"+(e-s)/1000.0+"s");
        }
    }

}

class MyThread extends Thread {
    static volatile boolean start=true;
    static int tid=0;

    int id=0;
    ArrayList<MyVisitor> tasks;
    CyclicBarrier barrier;
    public MyThread(CyclicBarrier _barrier) {
        super("MyThread"+(tid++));

        barrier = _barrier;
        id=tid;
        tasks = new ArrayList(256);
    }

    void addVisitors(MyVisitor v) {
        tasks.add(v);
    }

    public void run() {
        while (true) {
            while (!start) { ; }

            for (int i=0; i<tasks.size(); i++) {
                MyVisitor v=tasks.get(i);
                v.run();
            }
            start = false;
            try { barrier.await();}
            catch (InterruptedException e) { break; }
            catch (Exception e) { throw new RuntimeException(e); }
        }
    }
}
import java.util.ArrayList;
导入java.util.array;
导入java.util.concurrent.CyclicBarrier;
类别表1{
公共静态最终整数大小1=200000000;
公共静态最终布尔对象参数;
静止的{
字符串类型=System.getProperty(“arg.type”);
如果(“双”。相等信号情况(类型)){
System.out.println(“使用原语(双)类型arg”);
OBJ_参数=假;
}否则{
System.out.println(“使用对象类型arg”);
OBJ_参数=真;
}
}
字节[]已填充;
int[]ivals;
字符串[]strs;
表1(整数大小){
填充=新字节[大小];
ivals=新整数[大小];
strs=新字符串[大小];
数组。填充(已填充,(字节)1);
数组。填充(IVAS,42);
数组。填充(STR,“STR”);
}
公共布尔迭代_范围(int-from,int-to,myv){
for(int i=from;i0){
THREAD_NUM=Integer.parseInt(args[0]);
System.out.println(“设置线程数:+线程数”);
}
表1表1=新表1(表1.尺寸1);
床头台床头=新床头台(表1.尺寸1);
MyVisitor[]访问者=新的MyVisitor[MyVisitor.NUM];

对于(int i=0;i这不是一个完整的答案,但可能会给您提供一个提示

我已经更改了你的密码

Table1(int size) {
filled = new byte[size];
ivals = new int[size];
strs = new String[size];

Arrays.fill(filled, (byte)1);
Arrays.fill(ivals, 42);
Arrays.fill(strs, "Strs");
}

更改后,使用对象类型数组的4个线程的运行时间减少了。

“sv=(String)\u v;”造成了差异。我还确认类型转换不是影响因素。仅访问\u v不能产生差异。为sv字段指定一些值会产生差异。但我无法解释原因。

根据

在Java编程语言内存模型中,对非易失性长值或双值的一次写入被视为两次单独的写入:一次写入一个32位的一半。这可能导致线程在一次写入中看到64位值的前32位,在另一次写入中看到第二个32位

易失性长值和双值的写入和读取始终是原子的

对引用的写入和读取始终是原子的,无论它们是作为32位值还是64位值实现的

分配引用始终是原子的, double不是原子的,除非定义为volatile

问题是sv可以被其他线程看到,并且它的赋值是原子的。
因此,使用ThreadLocal包装访问者的成员变量(i、iv、sv)将解决此问题。

HotSpot VM为引用类型putfield字节码生成以下程序集

mov ref, OFFSET_OF_THE_FIELD(this)  <- this puts the new value for field.

mov this, REGISTER_A
shr 0x9, REGISTER_A
movabs OFFSET_X, REGISTER_B
mov %r12b, (REGISTER_A, REGISTER_B, 1)
根据我的访客定义


然后,每个MyVisitor实例的间隔将足够大,不会共享卡片标记位,您将看到程序的规模。

请在问题中包含代码;到外部位置的链接可能会被删除。您可以执行配置文件以查看是否有大量GC活动吗?
(字符串)
第126行上的强制转换似乎也是一个潜在的问题。您能否尝试直接接受
字符串进行检查?我用GarbageCollectorMXBean测量了GC时间;这并不重要(我还强制在测量时间之前运行GC).类型转换不是问题--我在没有使用类型转换的情况下进行了测试,结果是一样的。对于包含代码来说,它太长了,无法包含。我将在有足够数量的答案后尝试包含它。请添加代码(片段)这里。不鼓励在外部源代码中使用代码。在问题中包括源代码。那么JVM是否不为内部字符串使用并发哈希映射?
mov ref, OFFSET_OF_THE_FIELD(this)  <- this puts the new value for field.

mov this, REGISTER_A
shr 0x9, REGISTER_A
movabs OFFSET_X, REGISTER_B
mov %r12b, (REGISTER_A, REGISTER_B, 1)
byte[] garbage = new byte[600];