17

有没有办法创建扩展 ByteBuffer 类的类?

ByteBuffer 中的一些抽象方法是包私有的,如果我创建包 java.nio,则会引发安全异常。

出于性能原因,我想这样做——例如 getInt 有大约 10 个方法调用,以及相当多的 if 方法。即使保留所有检查,并且仅内联方法调用并删除大/小端检查,我创建的测试表明它可以快 4 倍左右。

4

7 回答 7

15

你不能扩展 ByteBuffer 并感谢上帝。

你不能扩展 b/c 没有受保护的 c-tors。为什么要感谢上帝?好吧,只有 2 个真正的子类可以确保 JVM 可以大量优化任何涉及 ByteBuffer 的代码

最后,如果您需要真正扩展类,请编辑字节码,只需将受保护的属性 c-tor 和 public 属性添加到 DirectByteBuffer(和 DirectByteBufferR)。扩展 HeapBuffer 没有任何用处,因为无论如何您都可以访问底层数组

在那里使用-Xbootclasspath/p并添加您自己的类,在您需要的包中扩展(在 java.nio 之外)。这就是它的完成方式。

另一种方法是使用 sun.misc.Unsafe 并在address().

出于性能原因,我想这样做——例如 getInt 有大约 10 个方法调用,以及相当多的 if 方法。即使保留所有检查,并且仅内联方法调用并删除大/小端检查,我创建的测试表明它可以快 4 倍左右。

现在好的部分,使用 gdb 并检查真正生成的机器代码,您会惊讶地发现有多少检查会被删除。

我无法想象一个人为什么要扩展课程。它们的存在是为了实现良好的性能,而不仅仅是 OO 多态执行。


编辑:

如何声明任何类并绕过 Java 验证器

关于 Unsafe:Unsafe 有 2 个绕过验证器的方法,如果你有一个扩展 ByteBuffer 的类,你可以调用其中任何一个。您需要一些具有公共访问权限和受保护的 c-tor 的 ByteBuffer 版本(但这非常简单),仅供编译器使用。方法如下。您可以自行承担使用它们的风险。像这样声明类之后,您甚至可以使用 new 关键字来使用它(前提是有合适的 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);
于 2011-05-29T10:26:01.723 回答
10

您可以通过使用反射来忽略保护级别,但这在很大程度上违背了性能目标。

您不能在 java.nio 包中创建类 - 这样做(并以任何方式分发结果)违反了 Sun 的 Java 许可证,理论上可能会让您陷入法律麻烦。

我不认为有一种方法可以在不使用原生的情况下做你想做的事情——但我也怀疑你屈服于过早优化的诱惑。假设您的测试是正确的(哪些微基准测试通常不正确):您真的确定对 ByteBuffer 的访问将成为您实际应用程序中的性能瓶颈吗?当您的应用程序只花费 5% 的时间和 95% 的时间处理它获取的数据时,ByteBuffer.get() 是否可以快 4 倍有点无关紧要。

为了(可能纯粹是理论上的)性能而想要绕过所有检查听起来不是一个好主意。性能调优的基本规则是“首先让它正常工作,然后让它工作得更快”。

编辑:如果,如评论中所述,应用程序确实在 ByteBuffer 方法中花费了 20-40% 的时间并且测试是正确的,这意味着 15-30% 的加速潜力 - 显着,但 IMO 不值得开始使用 JNI 或弄乱 API 源。我会先尝试用尽所有其他选项:

  • 你在使用 -server 虚拟机吗?
  • 是否可以修改应用程序以减少对 ByteBuffer 的调用,而不是尝试加速它所做的调用?
  • 使用分析器查看呼叫的来源——也许有些是完全没有必要的
  • 也许可以修改算法,或者您可以使用某种缓存
于 2009-03-08T23:04:06.540 回答
2

ByteBuffer 是抽象的,所以,是的,你可以扩展它......但我认为你想要做的是扩展你可能无法实际实例化的类。也可能是被实例化的特定方法会覆盖该方法以比 ByteBuffer 中的方法更有效。

我还要说,对于所有需要的东西,你可能总体上是错误的——也许它不是为了你正在测试的东西,但很可能代码在那里是有原因的(也许在其他平台上)。

如果您确实认为自己是正确的,请打开一个错误并查看他们要说的内容。

如果要添加到 nio 包中,可以尝试在调用 Java 时设置引导类路径。它应该让你把你的类放在 rt.jar 之前。键入 java -X 以查看如何执行此操作,您需要 -Xbootclasspath/p 开关。

于 2009-03-08T23:03:06.777 回答
1

Java 代理可以修改 ByteBuffer的字节码并更改构造函数的访问修饰符。当然,您需要在 JVM 上安装代理,并且您仍然需要编译才能编译您的子类。如果您正在考虑进行此类优化,那么您必须做好准备!

我从未尝试过如此低级的操作。希望 JVM 在您的代理可以挂接到它之前不需要 ByteBuffer。

于 2011-06-01T06:11:39.537 回答
1

获取 Unsafe 实例的最简单方法是通过反射。但是,如果您无法使用反射,您可以创建另一个实例。您可以通过 JNI 执行此操作。

我尝试在字节码中创建一个实例而不调用构造函数,从而允许您创建一个没有可访问构造函数的对象实例。但是,这个 id 不起作用,因为我得到了字节码的 VerifyError。该对象必须有一个对其调用的构造函数。


我所做的是有一个 ParseBuffer,它包装了一个直接的 ByteBuffer。我使用反射来获取Unsafe参考和address. 为了避免耗尽缓冲区的末尾并杀死 JVM,我分配了比我需要的更多的页面,只要它们不被触及,就不会为应用程序分配物理内存。这意味着我的边界检查要少得多,只检查关键点。

使用 OpenJDK 的调试版本,您可以看到 Unsafe get/put 方法变成了一条机器代码指令。但是,这并非在所有 JVM 中都可用,并且可能不会在所有平台上获得相同的改进。

使用这种方法,我会说您可以减少大约 40% 的时间,但会面临普通 Java 代码所没有的风险,即您可以杀死 JVM。与使用普通直接 ByteBuffer 相比,我拥有的用例是一个对象创建免费的 XML 解析器和使用 Unsafe 包含的数据的处理器。我在 XML 解析器中使用的技巧之一是 getShort() 和 getInt() 一次检查多个字节,而不是一次检查每个字节。

对 Unsafe 类使用反射是一次开销。一旦有了 Unsafe 实例,就没有开销了。

于 2011-05-29T13:12:07.793 回答
1

+50 赏金以绕过访问限制(tt 不能单独使用反射来完成。也许有一种使用 sun.misc.Unsafe 等的方法?)

答案是:没有办法绕过 Java 中的所有访问限制。

  • sun.misc.Unsafe在安全经理的授权下工作,因此无济于事
  • 就像萨纳姆所说:

ByteBuffer 有包私有抽象 _set 和 _get 方法,所以你不能覆盖它。而且所有的构造函数都是包私有的,所以你不能调用它们。

  • 反射允许你绕过很多东西,但前提是安全管理器允许。在许多情况下,您无法控制安全管理器,它是强加给您的。如果您的代码依赖于对安全管理器的摆弄,那么它不会在所有情况下都是“可移植的”或可执行的,可以这么说。

问题的底线是试图覆盖字节缓冲区并不能解决问题。

除了使用您需要的方法自己实现一个类之外,别无选择。将方法设为最终方法将有助于编译器执行优化(减少为运行时多态性和内联生成代码的需要)。

于 2011-05-27T20:03:26.420 回答
-1

我正在回答您想要回答的问题,而不是您提出的问题。你真正的问题是“我怎样才能让它更快?” 答案是“一次处理一个数组的整数,而不是单独处理”。

如果瓶颈确实是 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 快得多。通过消除边界检查和 ByteBuffer 可能抛出的异常,遍历已设置且已知良好边界的 int[] 数组还将使您的代码 JIT 更加紧密。

如果您需要进一步的性能,您可以调整代码,或滚动您自己的大小优化 byte[] 到 int[] 转换代码。使用它来代替具有部分循环展开的 IntBuffer 方法,我能够获得一些性能改进......但无论如何都不建议这样做。

于 2011-06-01T17:01:05.177 回答