Extending ByteBuffer class

二次信任 提交于 2019-11-29 05:52:30

You cant extend ByteBuffer and thanks God for.

You cant extend b/c there are no protected c-tors. Why thank god part? Well, having only 2 real subclasses ensures that the JVM can Heavily optimizes any code involving ByteBuffer.

Last, if you need to extend the class for real, edit the byte code, and just add protected attribute the c-tor and public attribute to DirectByteBuffer (and DirectByteBufferR). Extending the HeapBuffer serves no purposes whatsoever since you can access the underlying array anyways

use -Xbootclasspath/p and add your own classes there, extend in the package you need (outside java.nio). That's how it's done.

Another way is using sun.misc.Unsafe and do whatever you need w/ direct access to the memory after address().

I would want to do that for performance reasons - getInt for example has about 10 method invocations, as well as quite a few if's. Even if all checks are left, and only method calls are inlined and big/small endian checks are removed, tests that I've created show that it can be about 4 times faster.

Now the good part, use gdb and check the truly generated machine code, you'd be surprised how many checks would be removed.

I can't imagine why a person would want to extend the classes. They exist to allow good performance not just OO polymorph execution.


edit:

How to declare any class and bypass Java verifier

On Unsafe: Unsafe has 2 methods that bypass the verifier and if you have a class that extends ByteBuffer you can just call any of them. You need some hacked version (but that's super easy) of ByteBuffer w/ public access and protected c-tor just for the compiler. The methods are below. You can use 'em on your own risk. After you declare the class like that you can even use it w/ new keyword (provided there is a suitable 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);

You can disregard protection levels by using reflection, but that kinda defeats the performance goal in a big way.

You can NOT create a class in the java.nio package - doing so (and distributing the result in any way) violates Sun's Java license and could theoretically get you into legal troubles.

I don't think there's a way to do what you want to do without going native - but I also suspect that you're succumbing to the temptation of premature optimization. Assuming that your tests are correct (which microbenchmarks are often not): are you really sure that access to ByteBuffer is going to be the performance bottleneck in your actual application? It's kinda irrelevant whether ByteBuffer.get() could be 4 times faster when your app only spends 5% of its time there and 95% processing the data it's fetched.

Wanting to bypass all checks for the sake of (possibly purely theoretical) performance does not sound a good idea. The cardinal rule of performance tuning is "First make it work correctly, THEN make it work faster".

Edit: If, as stated in the comments, the app actually does spend 20-40% of its time in the ByteBuffer methods and the tests are correct, that means a speedup potential of 15-30% - significant, but IMO not worth starting to use JNI or messing with the API source. I'd try to exhaust all other options first:

  • Are you using the -server VM?
  • Could the app be modified to make fewer calls to ByteBuffer rather than trying to speed up those it does make?
  • Use a profiler to see where the calls are coming from - perhaps some are outright unnecessary
  • Maybe the algorithm can be modified, or you can use some sort of caching

ByteBuffer is abstract so, yes, you can extend it... but I think what you want to do is extend the class that is actually instantiated which you likely cannot. It could also be that the particular one that gets instantiated overrides that method to be more efficient than the one in ByteBuffer.

I would also say that you are likely wrong in general about all of that being needed - perhaps it isn't for what you are testing, but likely the code is there for a reason (perhaps on other platforms).

If you do believe that you are correct on it open a bug and see what they have to say.

If you want to add to the nio package you might try setting the boot classpath when you call Java. It should let you put your classes in before the rt.jar ones. Type java -X to see how to do that, you want the -Xbootclasspath/p switch.

+50 bounty for a way to circumvent the access restriction (tt cannot be done using reflection alone. Maybe there is a way using sun.misc.Unsafe etc.?)

Answer is: there is no way to circumvent all access restrictions in Java.

  • sun.misc.Unsafe works under the authority of security managers, so it won't help
  • Like Sarnum said:

ByteBuffer has package private abstract _set and _get methods, so you couldn't override it. And also all the constructors are package private, so you cannot call them.

  • Reflection allows you to bypass a lot of stuff, but only if the security manager allows it. There are many situations where you have no control on the security manager, it is imposed on you. If your code were to rely on fiddling with security managers, it would not be 'portable' or executable in all circumstances, so to speak.

The bottom line of the question is that trying to override byte buffer is not going to solve the issue.

There is no other option than implementing a class yourself, with the methods you need. Making methods final were you can will help the compiler in its effort to perform optimizations (reduce the need to generate code for runtime polymorphism & inlining).

The simplest way to get the Unsafe instances is via reflection. However if reflection is not available to you, you can create another instance. You can do this via JNI.

I tried in byte code, to create an instance WITHOUT calling a constructor, allowing you create an instance of an object with no accessible constructors. However, this id not work as I got a VerifyError for the byte code. The object has to have had a constructor called on it.


What I do is have a ParseBuffer which wraps a direct ByteBuffer. I use reflection to obtain the Unsafe reference and the address. To avoid running off the end of the buffer and killing the JVM, I allocate more pages than I need and as long as they are not touched no physical memory will be allocated to the application. This means I have far less bounds checks and only check at key points.

Using the debug version of the OpenJDK, you can see the Unsafe get/put methods turn into a single machine code instruction. However, this is not available in all JVM and may not get the same improvement on all platforms.

Using this approach I would say you can get about a 40% reduction in timings but comes at a risk which normal Java code does not have i.e. you can kill the JVM. The usecase I have is an object creation free XML parser and processor of the data contained using Unsafe compared with using a plain direct ByteBuffer. One of the tricks I use in the XML parser is to getShort() and getInt() to examine multiple bytes at once rather than examining each byte one at a time.

Using reflection to the the Unsafe class is an overhead you incurr once. Once you have the Unsafe instance, there is no overhead.

A Java Agent could modify ByteBuffer's bytecode and change the constructor's access modifier. Of course you'd need to install the agent at the JVM, and you still have to compile get your subclass to compile. If you're considering such optimizations then you must be up for it!

I've never attempted such low level manipulation. Hopefully ByteBuffer is not needed by the JVM before your agent can hook into it.

I am answering the question you WANT the answer to, not the one you asked. Your real question is "how can I make this go faster?" and the answer is "handle the integers an array at a time, and not singly."

If the bottleneck is truly the ByteBuffer.getInt() or ByteBuffer.getInt(location), then you do not need to extend the class, you can use the pre-existing IntBuffer class to grab data in bulk for more efficient processing.

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!
}

This is MUCH, MUCH faster than getting an int at a time. Iterating over the int[] array, which has set and known-good bounds, will also let your code JIT much tighter by eliminating bounds checks and the exceptions ByteBuffer can throw.

If you need further performance, you can tweak the code, or roll your own size-optimized byte[] to int[] conversion code. I was able to get some performance improvement using that in place of the IntBuffer methods with partial loop unrolling... but it's not suggested by any means.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!