Расширение класса ByteBuffer - PullRequest
14 голосов
/ 09 марта 2009

Есть ли способ создать класс, который расширяет класс ByteBuffer?

Некоторые абстрактные методы из ByteBuffer являются частными пакетами, и если я создаю пакет java.nio, выдается исключение безопасности.

Я хотел бы сделать это по соображениям производительности - например, getInt имеет около 10 вызовов методов, а также довольно много операторов if. Даже если все проверки оставлены, и только вызовы методов являются встроенными, а проверки с прямым и обратным порядком байтов удалены, созданные мной тесты показывают, что они могут быть примерно в 4 раза быстрее.

Ответы [ 7 ]

13 голосов
/ 29 мая 2011

Вы не можете расширить ByteBuffer и благодарить Бога за.

Вы не можете продлить б / у, защищенных c-торов нет. Почему слава богу часть? Хорошо, наличие только 2 реальных подклассов гарантирует, что JVM может тяжело оптимизировать любой код, включающий ByteBuffer.

Наконец, если вам нужно расширить класс по-настоящему, отредактируйте байт-код и просто добавьте защищенный атрибут c-tor и открытый атрибут в DirectByteBuffer (и DirectByteBufferR). Расширение HeapBuffer не имеет никакой цели, поскольку вы в любом случае можете получить доступ к базовому массиву

используйте -Xbootclasspath/p и добавляйте туда свои собственные классы, расширяйте их в нужном вам пакете (за пределами java.nio). Вот как это делается.

Другой способ - использовать sun.misc.Unsafe и делать все, что вам нужно, с прямым доступом к памяти после address().

Я бы хотел сделать это для причины производительности - getInt для Пример имеет около 10 методов вызовы, а также немало если х. Даже если все чеки оставлены, и только вызовы методов являются встроенными и большие / маленькие порядковые чеки удалены, тесты, которые я создал, показывают, что это может быть примерно в 4 раза быстрее.

Теперь хорошая часть, используйте gdb и проверьте действительно сгенерированный машинный код, вы будете удивлены, сколько проверок будет удалено.

Я не могу себе представить, почему человек хочет продлить занятия. Они существуют для обеспечения хорошей производительности, а не только для выполнения OO полиморфа.


редактирование:

Как объявить любой класс и обойти Java-верификатор

On Unsafe: Unsafe имеет 2 метода, которые обходят верификатор, и если у вас есть класс, расширяющий ByteBuffer, вы можете просто вызвать любой из них. Вам нужна взломанная версия (но это очень просто) ByteBuffer с открытым доступом и защищенным c-tor только для компилятора. Методы ниже. Вы можете использовать их на свой страх и риск. После того, как вы объявите класс таким образом, вы даже сможете использовать его с новым ключевым словом (при условии, что есть подходящий c-tor)

public native Class defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);    
public native Class defineClass(String name, byte[] b, int off, int len);
9 голосов
/ 09 марта 2009

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

Вы НЕ МОЖЕТЕ создать класс в пакете java.nio - это (и распространение результата любым способом) нарушает лицензию Sun на Java и теоретически может привести к юридическим проблемам.

Я не думаю, что есть способ сделать то, что вы хотите сделать, не переходя на нативный язык, - но я также подозреваю, что вы уступаете искушению преждевременной оптимизации. Предполагая, что ваши тесты верны (какие микробенчмарки часто не являются): действительно ли вы уверены, что доступ к ByteBuffer станет узким местом производительности в вашем реальном приложении? Не имеет значения, может ли ByteBuffer.get () быть в 4 раза быстрее, если ваше приложение проводит там только 5% своего времени и 95% обрабатывает данные, которые оно получило.

Желание обойти все проверки ради (возможно, чисто теоретического) исполнения не является хорошей идеей. Основное правило настройки производительности: «Сначала сделай так, чтобы оно работало правильно, ТОГДА сделай так, чтобы оно работало быстрее».

Редактировать: Если, как указано в комментариях, приложение на самом деле тратит 20-40% своего времени в методах ByteBuffer и тесты верны, то есть потенциал ускорения 15-30 % - значимо, но IMO не стоит начинать использовать JNI или возиться с источником API. Сначала я бы попробовал исчерпать все остальные варианты:

  • Вы используете виртуальную машину -server?
  • Может ли приложение быть модифицировано так, чтобы меньше обращалось к ByteBuffer, а не пыталось ускорить те, которые оно делает?
  • Используйте профилировщик, чтобы увидеть, откуда поступают вызовы - возможно, некоторые из них совершенно не нужны
  • Возможно, алгоритм можно изменить, или вы можете использовать какое-то кеширование
2 голосов
/ 09 марта 2009

ByteBuffer является абстрактным, так что, да, вы можете расширить его ... но я думаю, что вы хотите сделать, это расширить класс, который фактически создан, что вы, вероятно, не можете. Также может случиться так, что конкретный экземпляр, который получает экземпляр, переопределяет этот метод, чтобы быть более эффективным, чем метод в ByteBuffer.

Я бы также сказал, что вы, вероятно, в целом ошибаетесь по поводу всего того, что нужно - возможно, это не для того, что вы тестируете, но, скорее всего, код существует по какой-то причине (возможно, на других платформах). *

Если вы считаете, что вы правы, откройте ошибку и посмотрите, что они скажут.

Если вы хотите добавить в пакет nio, вы можете попробовать установить загрузочный путь к классам при вызове Java. Это должно позволить вам поместить ваши классы перед rt.jar. Введите java -X, чтобы увидеть, как это сделать, вам нужен ключ -Xbootclasspath / p.

1 голос
/ 01 июня 2011

A Java Agent может изменить байт-код ByteBuffer и изменить модификатор доступа конструктора. Конечно, вам нужно установить агент на JVM, и вам все равно придется компилировать, чтобы ваш подкласс компилировался. Если вы рассматриваете такую ​​оптимизацию, вы должны быть готовы к этому!

Я никогда не пытался манипулировать таким низким уровнем. Надеемся, что JVM не нужен ByteBuffer, прежде чем ваш агент сможет подключиться к нему.

1 голос
/ 29 мая 2011

Самый простой способ получить небезопасные экземпляры - через отражение. Однако, если рефлексия вам недоступна, вы можете создать другой экземпляр. Вы можете сделать это через JNI.

Я попытался в байт-коде создать экземпляр БЕЗ вызова конструктора, что позволило бы создать экземпляр объекта без доступных конструкторов. Однако этот идентификатор не работает, так как я получил VerifyError для байтового кода. У объекта должен быть вызван конструктор.


Я использую ParseBuffer, который оборачивает прямой ByteBuffer. Я использую отражение, чтобы получить ссылку Unsafe и address. Чтобы избежать выхода за пределы буфера и уничтожения JVM, я выделяю больше страниц, чем мне нужно, и до тех пор, пока они не затронуты, никакая физическая память не будет выделена приложению. Это означает, что у меня гораздо меньше проверок границ, и я проверяю только в ключевых точках.

Используя отладочную версию OpenJDK, вы можете видеть, что небезопасные методы get / put превращаются в одну инструкцию машинного кода. Однако это доступно не во всех JVM и может не достичь одинакового улучшения на всех платформах.

Используя этот подход, я бы сказал, что вы можете получить сокращение времени примерно на 40%, но существует риск, что в обычном коде Java его нет, т. Е. Вы можете убить JVM. У меня есть сценарий использования, свободный для создания объектов XML-анализатор и обработчик данных, содержащихся в Unsafe, по сравнению с обычным прямым ByteBuffer. Один из приемов, которые я использую в синтаксическом анализаторе XML, состоит в том, чтобы getShort () и getInt () проверяли несколько байтов одновременно, а не проверяли каждый байт по одному.

Использование отражения для класса Unsafe - это накладные расходы, которые вы понесли однажды. Если у вас есть небезопасный экземпляр, накладных расходов нет.

1 голос
/ 28 мая 2011

+ 50 наград за способ обойти ограничение доступа (tt не может быть сделано с использованием только отражения. Может быть есть способ использовать sun.misc.Unsafe etc.?)

Ответ: нет способа обойти все ограничения доступа в Java.

  • sun.misc.Unsafe работает под руководством менеджеров безопасности, поэтому не поможет
  • Как Сарнум сказал:

ByteBuffer имеет приватный пакет абстрактные методы _set и _get, так что вы не мог отменить это. А также все Конструкторы являются частными, поэтому Вы не можете позвонить им.

  • Отражение позволяет вам обходить многие вещи, но только если это разрешает менеджер безопасности. Есть много ситуаций, когда вы не можете контролировать менеджер безопасности, он навязывается вам. Если бы ваш код полагался на работу с менеджерами безопасности, он не был бы «переносимым» или исполняемым, так сказать, при любых обстоятельствах.

Суть вопроса в том, что попытка переопределить байтовый буфер не решит проблему.

Нет другого выбора, кроме как реализовать класс самостоятельно с необходимыми вам методами. Окончательное завершение методов, в которых вы можете помочь компилятору в его усилиях по выполнению оптимизаций (уменьшите необходимость генерировать код для полиморфизма и вставки во время выполнения).

0 голосов
/ 01 июня 2011

Я отвечаю на вопрос, на который вы ХОТИТЕ ответить, а не тот, который вы задали. Ваш реальный вопрос: «Как я могу сделать это быстрее?» и ответ «обрабатывать целые числа в массиве за раз, а не в одиночку».

Если узким местом является действительно ByteBuffer.getInt () или ByteBuffer.getInt (location), то вам не нужно расширять класс, вы можете использовать существующий класс IntBuffer для массового сбора данных для более эффективной обработки .

int totalLength = numberOfIntsInBuffer;
ByteBuffer myBuffer = whateverMyBufferIsCalled;
int[] block = new int[1024];
IntBuffer intBuff = myBuffer.asIntBuffer();
int partialLength = totalLength/1024;

//Handle big blocks of 1024 ints at a time
try{
  for (int i = 0; i < partialLength; i++) {
     intBuff.get(block);
     // Do processing on ints, w00t!
  }

  partialLength = totalLength % 1024; //modulo to get remainder
  if (partialLength > 0) {
    intBuff.get(block,0,partialLength);
    //Do final processing on ints
  }
} catch BufferUnderFlowException bufo {
   //well, dang!
}

Это НАМНОГО, НАМНОГО быстрее, чем получать int одновременно. Итерирование по массиву int [], который имеет заданные и известные хорошие границы, также сделает ваш код JIT намного более жестким, исключив проверки границ и исключения, которые может генерировать ByteBuffer.

Если вам нужна дополнительная производительность, вы можете настроить код или свернуть свой код преобразования byte [] в int [] с оптимизированным размером. Мне удалось добиться некоторого улучшения производительности, используя его вместо методов IntBuffer с частичным развертыванием цикла ... но это никоим образом не предлагается.

...