【问题标题】:Multicore Java Program with Native Code带有本机代码的多核 Java 程序
【发布时间】:2012-08-15 19:32:06
【问题描述】:

我在 Java 程序中使用本机 C++ 库。 Java 程序是为使用多核系统而编写的,但它不能扩展:最好的速度是使用大约 6 个内核,即添加更多内核会减慢速度。我的测试表明对本机代码的调用本身会导致问题,因此我想确保不同的线程访问本机库的不同实例,从而消除并行任务之间的任何隐藏(内存)依赖关系。 换句话说,而不是静态块

static {
    System.loadLibrary("theNativeLib");
}

我希望为每个线程动态加载库的多个实例。主要的问题是这是否可能。然后怎么做!

注意事项: - 我在 Java 7 fork/join 以及 Scala/akka 中都有实现。因此,感谢每个平台的任何帮助。 - 并行任务完全独立。事实上,每个任务可能会创建几个新任务然后终止;不再依赖!

这里是fork/join风格的测试程序,其中processNatively基本上就是一堆原生调用:

class Repeater extends RecursiveTask<Long> {
    final int n;
    final processor mol;

    public Repeater(final int m, final processor o) {
        n=m;
        mol = o;
    }
    @Override
    protected Long compute() {
        processNatively(mol);
        final List<RecursiveTask<Long>> tasks = new ArrayList<>();
        for (int i=n; i<9; i++) {
            tasks.add(new Repeater(n+1,mol));
        }

        long count = 1;
        for(final RecursiveTask<Long> task : invokeAll(tasks)) { 
            count += task.join(); 
        }
        return count;
    }
}
private final static ForkJoinPool forkJoinPool = new ForkJoinPool();

public void repeat(processor mol)
{
    final long middle = System.currentTimeMillis();     
    final long count = forkJoinPool.invoke(new Repeater(0, mol));
    System.out.println("Count is "+count);
    final long after = System.currentTimeMillis();      
    System.out.println("Time elapsed: "+(after-middle));
}

换一种说法: 如果我有 N 个线程使用本机库,如果每个线程都调用 System.loadLibrary("theNativeLib"); 会发生什么?动态地,而不是在静态块中调用它一次?他们会共享图书馆吗?如果是,我怎样才能欺骗 JVM 将其视为独立加载的 N 个不同的库? (N的值是静态未知的)

【问题讨论】:

  • 很抱歉我错过了这个问题?介意澄清一下
  • PS:正如我所说,代码只是一个测试。所以不要在任务的生成过程中寻找任何逻辑!关键是多次调用本机代码。
  • @David:我更新了这个问题。现在清楚了吗?
  • 真正的问题应该是:为什么本地调用会减慢它的速度?当然应该可以用 JNI 编写完全可重入的原生代码。
  • @biziclop:这是一个很好的问题,但我无法访问本机库的代码。

标签: java parallel-processing scalability multicore native-code


【解决方案1】:

您需要确保您没有任何共享资源的瓶颈。例如假设您有 6 个超线程内核,您可能会发现 12 个线程是最佳的,或者您可能会发现 6 个线程是最佳的(并且每个线程都有一个专用的内核)

如果您有一个繁重的浮点例程,那么超线程可能会更慢而不是更快。

如果您正在使用所有缓存,尝试使用更多缓存可能会降低系统速度。如果您正在使用 CPU 到主内存带宽的限制,尝试使用更多带宽可能会降低您的机器速度。

但是,我怎样才能引用不同的实例呢?我的意思是加载的类将具有相同的名称和包,对吗?如果您加载两个包含具有相同名称和包的类的动态库,通常会发生什么情况?

只有一个实例,您不能多次加载 DLL。如果您想为每个线程构建不同的数据集,您需要在库外部执行此操作并将其传递给库,以便每个线程可以处理不同的数据。

【讨论】:

  • 有什么建议可以提高可扩展性吗?
  • 你需要弄清楚你的瓶颈是什么。我建议尝试一些简单的微基准来计算系统的可用缓存大小和 CPU 内存带宽是多少。你必须在你的硬件限制范围内工作,并且很容易知道那是什么。
  • 我会编写一个不使用浮点、缓存或内存的简单程序。确保您可以按预期在所有 CPU 上扩展它。然后引入浮点(如果你正在使用它)等,直到你看到你在这里做的限制。
  • 原生库其实是“bliss”,是一种图规范化算法。所以我不希望那里有任何浮点运算。我有一个简单的玩具示例,可以毫无问题地扩展。我会尝试让它使用更多的内存来看看可伸缩性是如何受到影响的。不过,我无法访问本机代码,所以我不太确定这个实验将如何帮助我解决问题......无论如何,谢谢你的帮助......
  • 您可能需要减少每个线程的可用内存。即,如果您的组合内存使用量超过了缓存大小,您可能会受到缓存数据进出缓存速率的限制。
【解决方案2】:

System.loadLibrary 的 javadoc 声明它与调用 Runtime.getRuntime().loadLibrary(name) 相同。此loadLibrary (http://docs.oracle.com/javase/7/docs/api/java/lang/System.html#loadLibrary(java.lang.String)) 的 javadoc 声明“如果使用相同的库名称多次调用此方法,则忽略第二次和后续调用。”,因此您似乎无法加载同一个库不止一次。就让 JVM 认为有多个实例而言,我无能为力。

【讨论】:

  • 这是否意味着 OP 可以制作 N 个具有不同名称的库副本(例如,*nix 中的硬链接)以规避 Java 的这种单加载优化?
  • 理论上是的,但前提是 JVM 使用库名称作为检查,并且不检查库的实际内容。
  • 但是,我怎样才能引用不同的实例呢?我的意思是加载的类将具有相同的名称和包,对吗?如果您加载两个包含具有相同名称和包的类的动态库,一般会发生什么情况?
猜你喜欢
  • 2015-03-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多