MarginaliaSearch/code/libraries/random-write-funnel/java/nu/marginalia/rwf/RandomWriteFunnel.java

149 lines
4.3 KiB
Java
Raw Normal View History

2023-03-06 17:32:13 +00:00
package nu.marginalia.rwf;
2022-05-19 15:45:26 +00:00
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.ByteChannel;
2022-05-19 15:45:26 +00:00
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
/** For managing random writes on SSDs.
* Because SSDs do not deal well with random small writes,
* see https://en.wikipedia.org/wiki/Write_amplification,
* it is beneficial to pigeonhole the writes first
* within the same general region
2022-05-19 15:45:26 +00:00
* */
public class RandomWriteFunnel implements AutoCloseable {
private static final Logger logger = LoggerFactory.getLogger(RandomWriteFunnel.class);
private final ArrayList<DataBin> bins;
private final Path tempDir;
2022-05-19 15:45:26 +00:00
private final int binSize;
RandomWriteFunnel(Path tempDir, int binSize) throws IOException {
2022-05-19 15:45:26 +00:00
this.binSize = binSize;
this.tempDir = tempDir;
2022-05-19 15:45:26 +00:00
bins = new ArrayList<>();
2022-05-19 15:45:26 +00:00
}
2023-03-04 12:19:01 +00:00
public void put(long address, long data) throws IOException {
2022-06-18 13:54:58 +00:00
int bin = (int)(address / binSize);
int offset = (int)(address%binSize);
if (bin >= bins.size()) {
grow(bin);
}
bins.get(bin).put(offset, data);
}
2023-03-04 12:19:01 +00:00
private void grow(int bin) throws IOException {
while (bins.size() <= bin) {
bins.add(new DataBin(tempDir, binSize));
}
2022-05-19 15:45:26 +00:00
}
public void write(ByteChannel o) throws IOException {
2022-05-19 15:45:26 +00:00
ByteBuffer buffer = ByteBuffer.allocateDirect(binSize*8);
// This is crucial for compatibility other construction methods:
buffer.order(ByteOrder.nativeOrder());
2022-06-18 13:54:58 +00:00
for (var bin : bins) {
2022-05-19 15:45:26 +00:00
buffer.clear();
bin.eval(buffer);
while (buffer.hasRemaining()) {
2022-06-18 13:54:58 +00:00
o.write(buffer);
2022-05-19 15:45:26 +00:00
}
}
}
@Override
public void close() throws IOException {
for (DataBin bin : bins) {
bin.close();
}
}
static class DataBin {
2022-05-19 15:45:26 +00:00
private final ByteBuffer buffer;
private final int size;
2022-05-19 15:45:26 +00:00
private final FileChannel channel;
private final File file;
DataBin(Path tempDir, int size) throws IOException {
buffer = ByteBuffer.allocateDirect(360_000);
2022-05-19 15:45:26 +00:00
this.size = size;
file = Files.createTempFile(tempDir, "scatter-writer", ".dat").toFile();
channel = (FileChannel) Files.newByteChannel(file.toPath(), StandardOpenOption.WRITE, StandardOpenOption.READ);
2022-05-19 15:45:26 +00:00
}
void put(int address, long data) throws IOException {
2022-06-18 13:54:58 +00:00
if (buffer.remaining() < 12) {
2022-05-19 15:45:26 +00:00
flushBuffer();
}
2022-06-18 13:54:58 +00:00
buffer.putInt(address);
buffer.putLong(data);
2022-05-19 15:45:26 +00:00
}
private void flushBuffer() throws IOException {
if (buffer.position() == 0)
return;
buffer.flip();
2022-06-18 13:54:58 +00:00
while (buffer.hasRemaining())
channel.write(buffer);
2022-05-19 15:45:26 +00:00
buffer.clear();
}
private void eval(ByteBuffer dest) throws IOException {
flushBuffer();
channel.position(0);
buffer.clear();
dest.clear();
for (int i = 0; i < size; i++) {
dest.putLong(0L);
}
dest.position(0);
dest.limit(size*8);
while (channel.position() < channel.size()) {
int rb = channel.read(buffer);
if (rb < 0) {
break;
}
buffer.flip();
while (buffer.limit() - buffer.position() >= 12) {
2022-06-18 13:54:58 +00:00
int addr = 8 * buffer.getInt();
2022-05-19 15:45:26 +00:00
long data = buffer.getLong();
2022-06-18 13:54:58 +00:00
try {
dest.putLong(addr, data);
}
catch (IndexOutOfBoundsException ex) {
2022-06-22 10:57:58 +00:00
logger.info("Bad poke[{}]={}, this happens if an RWF is allocated with insufficient size", addr, data);
2022-06-18 13:54:58 +00:00
}
2022-05-19 15:45:26 +00:00
}
buffer.compact();
}
}
public void close() throws IOException {
channel.close();
file.delete();
}
}
}