![JAR search and dependency download from the Maven repository](/logo.png)
com.graphhopper.storage.UnsafeDataAccess Maven / Gradle / Ivy
Show all versions of graphhopper Show documentation
/*
* Licensed to GraphHopper GmbH under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*
* GraphHopper GmbH licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.graphhopper.storage;
import com.graphhopper.util.NotThreadSafe;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.Field;
import java.nio.ByteOrder;
/**
* This is a data structure which uses an unsafe access to native memory. The speed up compared to
* RAMDataAccess is roughly 10% due to index calculations and BitUtil overhead in RAMDataAccess.
* Notes:
*
* 1. Highly experimental. Still some bugs and access through file/MMAP should work at some point
*
* 2. Compared to MMAP no syncDAWrapper is need to make it read and write safe from multiple threads
*
* 3. Cannot be used on Android as no memory allocation methods are available there
*
* @author Peter Karich
*/
@NotThreadSafe
public class UnsafeDataAccess extends AbstractDataAccess
{
@SuppressWarnings("all")
static final sun.misc.Unsafe UNSAFE;
static
{
try
{
// On Android getting Unsafe fails as the field is named THE_ONE but Android has no memory allocation methods so it won't work nevertheless.
// On Android we need JNI+malloc https://github.com/libgdx/libgdx/blob/5945211a88570ced7eafce95c68f6f1f7124cd23/gdx/src/com/badlogic/gdx/utils/BufferUtils.java#L287
@SuppressWarnings("all")
Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
UNSAFE = (sun.misc.Unsafe) field.get(null);
} catch (Exception e)
{
throw new AssertionError(e);
}
}
private long address;
private long capacity;
UnsafeDataAccess( String name, String location, ByteOrder order )
{
super(name, location, order);
}
@Override
public UnsafeDataAccess create( long bytes )
{
// TODO use unsafe.pageSize() instead segmentSizeInBytes?
// e.g. on my system pageSize is only 4096
setSegmentSize(segmentSizeInBytes);
ensureCapacity(bytes);
return this;
}
@Override
public final boolean ensureCapacity( long bytes )
{
return ensureCapacity(bytes, true);
}
final boolean ensureCapacity( long bytes, boolean clearNewMem )
{
long oldCap = getCapacity();
long newBytes = bytes - oldCap;
if (newBytes <= 0)
return false;
// avoid frequent increase of allocation area, instead increase by segment size
int allSegments = (int) (bytes / segmentSizeInBytes);
if (bytes % segmentSizeInBytes != 0)
allSegments++;
capacity = allSegments * segmentSizeInBytes;
try
{
address = UNSAFE.reallocateMemory(address, capacity);
} catch (OutOfMemoryError err)
{
throw new OutOfMemoryError(err.getMessage() + " - problem when allocating new memory. Old capacity: "
+ oldCap + ", new bytes:" + newBytes + ", segmentSizeIntsPower:" + segmentSizePower);
}
if (clearNewMem)
UNSAFE.setMemory(address + oldCap, capacity - oldCap, (byte) 0);
return true;
}
@Override
public DataAccess copyTo( DataAccess da )
{
if (da instanceof UnsafeDataAccess)
{
// TODO unsafe.copyMemory(address, da.address, capacity);
// return this;
}
return super.copyTo(da);
}
@Override
public boolean loadExisting()
{
if (isClosed())
throw new IllegalStateException("already closed");
File file = new File(getFullName());
if (!file.exists() || file.length() == 0)
return false;
try
{
RandomAccessFile raFile = new RandomAccessFile(getFullName(), "r");
try
{
long byteCount = readHeader(raFile) - HEADER_OFFSET;
if (byteCount < 0)
return false;
raFile.seek(HEADER_OFFSET);
int segmentCount = (int) (byteCount / segmentSizeInBytes);
if (byteCount % segmentSizeInBytes != 0)
segmentCount++;
ensureCapacity(byteCount, false);
byte[] bytes = new byte[segmentSizeInBytes];
for (int s = 0; s < segmentCount; s++)
{
int read = raFile.read(bytes);
if (read <= 0)
throw new IllegalStateException("segment " + s + " is empty? " + toString());
// is there a faster method?
setBytes(s * segmentSizeInBytes, bytes, segmentSizeInBytes);
}
return true;
} finally
{
raFile.close();
}
} catch (IOException ex)
{
throw new RuntimeException("Problem while loading " + getFullName(), ex);
}
}
@Override
public void flush()
{
if (isClosed())
throw new IllegalStateException("already closed");
try
{
RandomAccessFile raFile = new RandomAccessFile(getFullName(), "rw");
try
{
long len = getCapacity();
writeHeader(raFile, len, segmentSizeInBytes);
raFile.seek(HEADER_OFFSET);
byte bytes[] = new byte[segmentSizeInBytes];
int segs = getSegments();
for (int s = 0; s < segs; s++)
{
getBytes(s * segmentSizeInBytes, bytes, segmentSizeInBytes);
raFile.write(bytes);
}
} finally
{
raFile.close();
}
} catch (Exception ex)
{
throw new RuntimeException("Couldn't store bytes to " + toString(), ex);
}
}
@Override
public void close()
{
super.close();
UNSAFE.freeMemory(address);
}
@Override
public final void setInt( long bytePos, int value )
{
UNSAFE.putInt(address + bytePos, value);
}
@Override
public final int getInt( long bytePos )
{
return UNSAFE.getInt(address + bytePos);
}
@Override
public short getShort( long bytePos )
{
return UNSAFE.getShort(address + bytePos);
}
@Override
public void setShort( long bytePos, short value )
{
UNSAFE.putShort(address + bytePos, value);
}
@Override
public final void setBytes( long bytePos, byte[] values, int length )
{
for (int offset = 0; offset < length; offset++)
{
UNSAFE.putByte(address + bytePos + offset, values[offset]);
}
}
@Override
public final void getBytes( long bytePos, byte[] values, int length )
{
assert length <= segmentSizeInBytes : "the length has to be smaller or equal to the segment size: " + length + " vs. " + segmentSizeInBytes;
for (int offset = 0; offset < length; offset++)
{
values[offset] = UNSAFE.getByte(address + bytePos + offset);
}
}
@Override
public final long getCapacity()
{
return capacity;
}
@Override
public final int getSegments()
{
return (int) (capacity / segmentSizeInBytes);
}
@Override
public final void trimTo( long bytes )
{
if (bytes > this.capacity)
throw new IllegalStateException("Use ensureCapacity to increase capacity!");
int allSegments = (int) (bytes / segmentSizeInBytes);
if (bytes % segmentSizeInBytes != 0)
allSegments++;
if (allSegments <= 0)
allSegments = 1;
capacity = allSegments * segmentSizeInBytes;
}
@Override
public DAType getType()
{
return DAType.UNSAFE_STORE;
}
}