Многоядерная программа Java с собственным кодом

Я использую собственную библиотеку C++ внутри программы Java. Программа Java написана для использования многоядерных систем, но она не масштабируется: наилучшая скорость достигается при наличии примерно 6 ядер, т. е. добавление большего количества ядер замедляет ее. Мои тесты показывают, что вызов самого нативного кода вызывает проблему, поэтому я хочу убедиться, что разные потоки обращаются к разным экземплярам нативной библиотеки, и, следовательно, удалить любую скрытую (память) зависимость между параллельными задачами. Другими словами, вместо статического блока

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

Я хочу, чтобы несколько экземпляров библиотеки загружались динамически для каждого потока. Главный вопрос заключается в том, возможно ли это вообще. А потом как это сделать!

Примечания: - У меня есть реализации fork/join Java 7, а также 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 не известно статически)


person Mahdi    schedule 20.08.2012    source источник
comment
Извините, я пропустил вопрос? Разум прояснить это   -  person David Kroukamp    schedule 20.08.2012
comment
PS: Как я уже сказал, код — это всего лишь тест. Так что не ищите никакой логики в том, как генерируются задачи! Дело в том, что вы просто много раз вызываете нативный код.   -  person Mahdi    schedule 20.08.2012
comment
@David: я обновил вопрос. Теперь стало яснее?   -  person Mahdi    schedule 20.08.2012
comment
Вопрос действительно должен быть: почему родной вызов тормозит? Конечно, с помощью JNI должна быть возможность написать полностью реентерабельный нативный код.   -  person biziclop    schedule 20.08.2012
comment
@biziclop: Это хороший вопрос, но у меня нет доступа к коду нативной библиотеки.   -  person Mahdi    schedule 20.08.2012
comment
@Mahdi Махди Ах, тогда это действительно проблема. Я думал, ты тоже написал родной бит.   -  person biziclop    schedule 20.08.2012
comment
Вам нужно использовать вывод нативного вызова в остальной части задачи?   -  person biziclop    schedule 20.08.2012
comment
Создать новую JVM для каждого числа, кратного 6, и использовать удаленных актеров Akka для объединения результатов?   -  person Viktor Klang    schedule 20.08.2012
comment
Звучит как проблема с памятью для меня. Сколько данных обрабатывает нативный вызов и программа в целом?   -  person Hristo Iliev    schedule 20.08.2012


Ответы (2)


В javadoc для System.loadLibrary указано, что это то же самое, что и вызов Runtime.getRuntime().loadLibrary(name). Документ Javadoc для этого loadLibrary (http://docs.oracle.com/javase/7/docs/api/java/lang/System.html#loadLibrary(java.lang.String) ) указывает, что "Если этот метод вызывается более более одного раза с одним и тем же именем библиотеки, второй и последующие вызовы игнорируются», поэтому кажется, что вы не можете загрузить одну и ту же библиотеку более одного раза. Что касается того, чтобы обмануть JVM, заставив ее думать, что существует несколько экземпляров, я не могу вам помочь.

person mikeythemissile    schedule 20.08.2012
comment
Означает ли это, что OP может сделать N копий библиотеки с разными именами (например, жесткая ссылка в * nix), чтобы обойти эту оптимизацию однократной загрузки с помощью Java? - person Dat Chu; 20.08.2012
comment
Теоретически да, но только если JVM использует имя библиотеки в качестве проверки и не проверяет фактическое содержимое библиотеки. - person mikeythemissile; 20.08.2012
comment
Но тогда как я могу ссылаться на разные экземпляры? Я имею в виду, что загруженные классы будут иметь одинаковые имена и пакеты, верно? Что вообще произойдет, если вы загрузите две динамические библиотеки, содержащие классы с одинаковыми именами и пакетами? - person Mahdi; 20.08.2012

Вы должны убедиться, что у вас нет узкого места на каких-либо общих ресурсах. например скажем, у вас есть 6 ядер с гиперпоточностью, вы можете обнаружить, что 12 потоков оптимальны, или вы можете обнаружить, что 6 потоков оптимальны (и каждый поток имеет выделенное ядро)

Если у вас есть тяжелая процедура с плавающей запятой, вполне вероятно, что гиперпоточность будет медленнее, чем быстрее.

Если вы используете весь кеш, попытка использовать больше может замедлить работу вашей системы. Если вы используете ограничение ЦП на пропускную способность основной памяти, попытка использовать большую пропускную способность может замедлить работу вашего компьютера.

Но тогда как я могу ссылаться на разные экземпляры? Я имею в виду, что загруженные классы будут иметь одинаковые имена и пакеты, верно? Что вообще произойдет, если вы загрузите две динамические библиотеки, содержащие классы с одинаковыми именами и пакетами?

Существует только один экземпляр, вы не можете загрузить DLL более одного раза. Если вы хотите создать другой набор данных для каждого потока, вам нужно сделать это извне библиотеки и передать его в библиотеку, чтобы каждый поток мог работать с разными данными.

person Peter Lawrey    schedule 20.08.2012
comment
Любые предложения, что я действительно могу сделать, чтобы улучшить масштабируемость? - person Mahdi; 21.08.2012
comment
Вам нужно понять, что у вас за бутылочное горлышко. Я предлагаю попробовать несколько простых микро-тестов, чтобы определить, каков полезный размер кэша вашей системы и какова пропускная способность вашего ЦП-памяти. Вы должны работать в рамках ограничений вашего оборудования, и полезно знать, что это такое. - person Peter Lawrey; 21.08.2012
comment
Я бы написал простую программу, которая не использует плавающую точку, кеш или память. Убедитесь, что вы можете масштабировать это для всех ваших процессоров, как вы могли ожидать. Затем введите плавающую точку (если вы ее используете) и т. д., пока не увидите ограничение, которое вы делаете здесь. - person Peter Lawrey; 21.08.2012
comment
Нативная библиотека на самом деле блаженство, которое представляет собой алгоритм канонизации графа. Поэтому я не ожидаю никаких операций с плавающей запятой. У меня есть простой игрушечный пример, который без проблем масштабируется. Я попытаюсь заставить его использовать больше памяти, чтобы посмотреть, как это повлияет на масштабируемость. Тем не менее, у меня нет доступа к нативному коду, поэтому я не совсем уверен, как этот эксперимент поможет мне решить проблему... Все равно спасибо за помощь... - person Mahdi; 21.08.2012
comment
Возможно, вам придется уменьшить доступную память для каждого потока. то есть, если ваше комбинированное использование памяти превышает размер вашего кеша, вы, вероятно, будете ограничены скоростью, с которой он может перетасовывать данные в и из кеша. - person Peter Lawrey; 21.08.2012