通过快速Java和文件序列化加快速度

从Java的第一个版本开始,每天都有许多开发人员试图至少达到与C / C ++一样好的性能。 JVM供应商正在通过实现一些新的JIT算法来尽力而为,但仍有许多工作要做,尤其是在我们如何使用Java方面。

例如,对象<->文件序列化有很多优势,尤其是在易于存储的对象的读写中。 我将尝试阐明该主题。

所有测试都在下面显示的简单对象上执行:

public class TestObject implements Serializable {
  private long longVariable;
  private long[] longArray;
  private String stringObject;
  private String secondStringObject; //just for testing nulls

  /* getters and setters */
}

为了更简洁,我将仅显示write方法(尽管另一种方法也非常相似)。 完整的源代码可在我的GitHub(http://github.com/jkubrynski/serialization-tests)上找到。

最标准的Java序列化(我们都从这里开始)如下所示:

public void testWriteBuffered(TestObject test, String fileName) throws IOException {
  ObjectOutputStream objectOutputStream = null;
  try {
    FileOutputStream fos = new FileOutputStream(fileName);
    BufferedOutputStream bos = new BufferedOutputStream(fos);
    objectOutputStream = new ObjectOutputStream(bos);
    objectOutputStream.writeObject(test);
  } finally {
    if (objectOutputStream != null) {
      objectOutputStream.close();
    }
  }
}

加快标准序列化的最简单方法是使用RandomAccessFile对象:

public void testWriteBuffered(TestObject test, String fileName) throws IOException {
  ObjectOutputStream objectOutputStream = null;
  try {
    RandomAccessFile raf = new RandomAccessFile(fileName, "rw");
    FileOutputStream fos = new FileOutputStream(raf.getFD());
    objectOutputStream = new ObjectOutputStream(fos);
    objectOutputStream.writeObject(test);
  } finally {
    if (objectOutputStream != null) {
      objectOutputStream.close();
    }      
}

更复杂的技术是使用Kryo框架。 新旧版本之间的差异很大。 我都检查了。 因为性能比较没有发现任何明显的不同,所以我将重点介绍第二个版本,因为它更加用户友好,甚至更快。

private static Kryo kryo = new Kryo(); // version 2.x

public void testWriteBuffered(TestObject test, String fileName) throws IOException {
  Output output = null;
  try {
    RandomAccessFile raf = new RandomAccessFile(fileName, "rw");
    output = new Output(new FileOutputStream(raf.getFD()), MAX_BUFFER_SIZE);
    kryo.writeObject(output, test);
  } finally {
    if (output != null) {
      output.close();
    }
  }
}

最后一个选择是受Martin Thompson文章启发的解决方案。 它显示了如何以C ++方式和Java处理内存

public void testWriteBuffered(TestObject test, String fileName) throws IOException {
  RandomAccessFile raf = null;
  try {
    MemoryBuffer memoryBuffer = new MemoryBuffer(MAX_BUFFER_SIZE);
    raf = new RandomAccessFile(fileName, "rw");
    test.write(memoryBuffer);
    raf.write(memoryBuffer.getBuffer());
  } catch (IOException e) {
    if (raf != null) {
      raf.close();
    }
  }
}

TestObject的写入方法如下所示:

public void write(MemoryBuffer unsafeBuffer) {
  unsafeBuffer.putLong(longVariable);
  unsafeBuffer.putLongArray(longArray);
  // we support nulls
  boolean objectExists = stringObject != null;
  unsafeBuffer.putBoolean(objectExists);
  if (objectExists) {
    unsafeBuffer.putCharArray(stringObject.toCharArray());
  }
  objectExists = secondStringObject != null;
  unsafeBuffer.putBoolean(objectExists);
  if (objectExists) {
    unsafeBuffer.putCharArray(secondStringObject.toCharArray());
  }
}

直接内存缓冲区类(简称,只是为了展示这个主意):

public class MemoryBuffer {
  // getting Unsafe by reflection
  public static final Unsafe unsafe = UnsafeUtil.getUnsafe();

  private final byte[] buffer;

  private static final long byteArrayOffset = unsafe.arrayBaseOffset(byte[].class);
  private static final long longArrayOffset = unsafe.arrayBaseOffset(long[].class);
  // other offsets 

  private static final int SIZE_OF_LONG = 8;
  // other sizes 

  private long pos = 0;

  public MemoryBuffer(int bufferSize) {
    this.buffer = new byte[bufferSize];
  }

  public final byte[] getBuffer() {
    return buffer;
  }

  public final void putLong(long value) {
    unsafe.putLong(buffer, byteArrayOffset + pos, value);
    pos += SIZE_OF_LONG;
  }

  public final long getLong() {
    long result = unsafe.getLong(buffer, byteArrayOffset + pos);
    pos += SIZE_OF_LONG;
    return result;
  }

  public final void putLongArray(final long[] values) {
    putInt(values.length);
    long bytesToCopy = values.length << 3;
    unsafe.copyMemory(values, longArrayOffset, buffer, byteArrayOffset + pos, bytesToCopy);
    pos += bytesToCopy;
  }

  public final long[] getLongArray() {
    int arraySize = getInt();
    long[] values = new long[arraySize];
    long bytesToCopy = values.length << 3;
    unsafe.copyMemory(buffer, byteArrayOffset + pos, values, longArrayOffset, bytesToCopy);
    pos += bytesToCopy;
    return values;
  }

  /* other methods */
}

卡尺运行多个小时的结果如下所示:

全程旅行[ns] 标准偏差[ns]
标准 207307 2362
英国皇家空军的标准 42661 733
KRYO 1.x 12027 112
KRYO 2.x 11479 259
不安全 8554 91

最后我们可以得出一些结论:

  • 不安全的序列化比java.io.Serializable的标准用法快23倍以上
  • 使用RandomAccessFile可以将标准缓冲序列化速度提高近4倍
  • Kryo动态序列化比手工实现的直接缓冲区慢约35%。

最后,我们可以看到,仍然没有金锤。 对于我们很多人来说,获得3000 ns(0.003ms)的值不值得为我们要与文件序列化的每个对象编写自定义实现。 对于标准解决方案,我们主要选择Kryo。 然而,在低延迟系统中,100ns似乎是永恒的,选择将完全不同。

参考:来自Java(B)Log博客的JCG合作伙伴 Jakub Kubrynski的快速Java和文件序列化加速。

翻译自: https://www.javacodegeeks.com/2013/09/speed-up-with-fast-java-and-file-serialization.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值