Java高速、多线程虚拟内存的实现

发表于:2014-7-07 09:19

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:Alex    来源:51Testing软件测试网采编

  线程
  一个极其强大且简单易用的方法就是线程。但是普通的Java IO简直就是线程的噩梦。两个线程无法在不引起冲突的情况下同时访问相同的数据流或RandomAccessFile 。虽然可以使用非阻塞IO,但是这样做会增加代码的复杂性并对原有的代码造成侵入。
  与其他的内存线程一样,内存映射文件也是由操作系统来处理。可以根据读写需要,在同一时刻尽可能多的使用线程。我的测试代码有128个线程,而且工作得很好(虽然机器发热比较大)。唯一重要的技巧是复用MappedByteBuffer对象,避免自身位置状态引发问题。
  现在可以执行下面的测试:
@Test
public void readWriteCycleThreaded() throws IOException {
final MapperCore mapper = new MapperCore("/tmp/MemoryMap", BIG_SIZE);
final AtomicInteger fails = new AtomicInteger();
final AtomicInteger done = new AtomicInteger();
Runnable r = new Runnable() {
public void run() {
try {
// Set to 0 for sequential test
long off = (long) ((BIG_SIZE - 1024) * Math.random());
System.out.println("Running new thread");
byte[] bOut = new byte[1024];
double counts = 10000000;
for (long i = 0; i < counts; ++i) {
ByteBuffer buf = ByteBuffer.wrap(bOut);
long pos = (long) (((BIG_SIZE - 1024) * (i / counts)) + off)
% (BIG_SIZE - 1024);
// Align with 8 byte boundary
pos = pos / 8;
pos = pos * 8;
for (int j = 0; j < 128; ++j) {
buf.putLong(pos + j * 8);
}
mapper.put(pos, bOut);
byte[] bIn = mapper.get(pos, 1024);
buf = ByteBuffer.wrap(bIn);
for (int j = 0; j < 128; ++j) {
long val = buf.getLong();
if (val != pos + j * 8) {
throw new RuntimeException("Error at " + (pos + j * 8) + " was " + val);
}
}
}
System.out.println("Thread Complete");
} catch (Throwable e) {
e.printStackTrace();
fails.incrementAndGet();
} finally {
done.incrementAndGet();
}
}
};
int nThreads = 128;
for (int i = 0; i < nThreads; ++i) {
new Thread(r).start();
}
while (done.intValue() != nThreads) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// ignore
}
}
if (fails.intValue() != 0) {
throw new RuntimeException("It failed " + fails.intValue());
}
}
  我曾尝试进行其他形式的IO,但是只要像上面那样运行128个线程,性能都不如上面的方法。我在四核、超线程I7 Retina MacBook Pro上尝试过。代码运行时会启动128个线程,超出CPU的最大负载(800%),直到操作系统检测到该进程的内存不足。在这个时候,系统开始对内存映射文件的读写进行分页。为实现这一目标,内核会占用一定的CPU,Java进程的性能会下降到650~750%。Java无需关心读取、写入、同步或类似的东西——操作系统会负责处理。
  结果会有所不同
  如果读取和写入点不是连续而是随机的,性能下降有所区别(带有交换时会达到750%,否则会达到250%)。我相信这种方式可能更适合处理少量的大数据对象,而不适用于大量的小数据对象。对于后者,可能的处理办法是预先将大量小数据对象加载到缓存中,再将其映射到虚拟内存。
  应用程序
  到目前为止,我使用的技术都是虚拟内存系统。在示例中,一旦与虚拟内存交互完成,就会删除底层文件。但是,这种方法可以很容易地进行数据持久化。
  例如,视频编辑是一个非常具有挑战性的工程问题。一般来说,有两个有效的方法:无损耗存储整个视频,并编辑存储的信息;或根据需要重新生成视频。因为RAM的制约,后一种方法越来越普遍。然而,视频是线性的——这是一种理想的数据类型,可用来存储非常大的映射虚拟内存。由于在视频算法上取得的进步,可以将它作为原始字节数组访问。操作系统会根据需要将磁盘到虚拟内存的缓冲区进行分页处理。
  另一个同样有效的应用场景是替代文档服务中过度设计的RAM缓存解决方案。想想看,我们有一个几TB的中等规模的文档库。它可能包含图片、短片和PDF文件。有一种常见的快速访问磁盘的方法,使用文件的RAM缓存弱引用或软引用。但是,这会对JVM垃圾收集器产生重大影响,并且增加操作难度。如果将整个文档映射到虚拟内存,可以更加简单地完成同样的工作。操作系统会根据需要将数据读入内存。更重要的是,操作系统将尽量保持RAM中最近被访问的内存页。这意味着内存映射文件就像RAM缓存一样,不会对Java或JVM垃圾收集器产生任何影响。
  最后,内存映射文件在科学计算和建模等应用中非常有效。在用来处理代表真实世界系统的计算模型时,经常需要大量的数据才能正常工作。在我的音频处理系统Sonic Field中,通过混合和处理单一声波,可以模拟真实世界中的音频效果。例如,创建原始音频副本是为模拟从硬表面反射的声波,并将反射回来的声波与原声波混合。这种方法需要大量的存储空间,这时就可以把音频信号放在虚拟内存中(也是这项工作的最初动机)。
22/2<12
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号