/*********************************************************************************************************************** * Copyright (C) 2010-2013 by the Stratosphere project (http://stratosphere.eu) * * Licensed 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 eu.stratosphere.pact.runtime.hash; import java.io.EOFException; import java.io.IOException; import java.util.ArrayList; import java.util.List; import eu.stratosphere.api.common.typeutils.TypeSerializer; import eu.stratosphere.core.memory.MemorySegment; import eu.stratosphere.core.memory.MemorySegmentSource; import eu.stratosphere.core.memory.SeekableDataInputView; import eu.stratosphere.nephele.services.memorymanager.AbstractPagedInputView; import eu.stratosphere.nephele.services.memorymanager.AbstractPagedOutputView; import eu.stratosphere.nephele.services.memorymanager.ListMemorySegmentSource; /** * In-memory partition with overflow buckets for {@link CompactingHashTable} * * @param T record type */ public class InMemoryPartition<T> { // --------------------------------- Table Structure Auxiliaries ------------------------------------ protected MemorySegment[] overflowSegments; // segments in which overflow buckets from the table structure are stored protected int numOverflowSegments; // the number of actual segments in the overflowSegments array protected int nextOverflowBucket; // the next free bucket in the current overflow segment // ------------------------------------- Type Accessors -------------------------------------------- private final TypeSerializer<T> serializer; // -------------------------------------- Record Buffers -------------------------------------------- private final ArrayList<MemorySegment> partitionPages; private final ListMemorySegmentSource availableMemory; private final WriteView writeView; private final ReadView readView; private long recordCounter; // number of records in this partition including garbage // ----------------------------------------- General ------------------------------------------------ private int partitionNumber; // the number of the partition private boolean compacted; // overwritten records since allocation or last full compaction // -------------------------------------------------------------------------------------------------- /** * Creates a new partition, in memory, with one buffer. * * @param serializer Serializer for T. * @param partitionNumber The number of the partition. * @param memSource memory pool * @param pageSize segment size in bytes * @param pageSizeInBits */ public InMemoryPartition(TypeSerializer<T> serializer, int partitionNumber, ListMemorySegmentSource memSource, int pageSize, int pageSizeInBits) { this.overflowSegments = new MemorySegment[2]; this.numOverflowSegments = 0; this.nextOverflowBucket = 0; this.serializer = serializer; this.partitionPages = new ArrayList<MemorySegment>(64); this.availableMemory = memSource; this.partitionNumber = partitionNumber; // add the first segment this.partitionPages.add(memSource.nextSegment()); // empty partitions have no garbage this.compacted = true; this.writeView = new WriteView(this.partitionPages, memSource, pageSize, pageSizeInBits); this.readView = new ReadView(this.partitionPages, pageSize, pageSizeInBits); } // -------------------------------------------------------------------------------------------------- /** * Gets the partition number of this partition. * * @return This partition's number. */ public int getPartitionNumber() { return this.partitionNumber; } /** * overwrites partition number and should only be used on compaction partition * @param number new partition */ public void setPartitionNumber(int number) { this.partitionNumber = number; } /** * * @return number of segments owned by partition */ public int getBlockCount() { return this.partitionPages.size(); } /** * number of records in partition including garbage * * @return number record count */ public long getRecordCount() { return this.recordCounter; } /** * sets record counter to zero and should only be used on compaction partition */ public void resetRecordCounter() { this.recordCounter = 0L; } /** * @return true if garbage exists in partition */ public boolean isCompacted() { return this.compacted; } /** * sets compaction status (should only be set <code>true</code> directly after compaction and <code>false</code> when garbage was created) * * @param compacted compaction status */ public void setCompaction(boolean compacted) { this.compacted = compacted; } // -------------------------------------------------------------------------------------------------- /** * Inserts the given object into the current buffer. This method returns a pointer that * can be used to address the written record in this partition. * * @param record The object to be written to the partition. * @return A pointer to the object in the partition. * @throws IOException Thrown when the write failed. */ public final long appendRecord(T record) throws IOException { long pointer = this.writeView.getCurrentPointer(); try { this.serializer.serialize(record, this.writeView); this.recordCounter++; return pointer; } catch (EOFException e) { // we ran out of pages. // first, reset the pages and then we need to trigger a compaction //int oldCurrentBuffer = this.writeView.resetTo(pointer); //for (int bufNum = this.partitionPages.size() - 1; bufNum > oldCurrentBuffer; bufNum--) { // this.availableMemory.addMemorySegment(this.partitionPages.remove(bufNum)); //} throw e; } } public void readRecordAt(long pointer, T record) throws IOException { this.readView.setReadPosition(pointer); this.serializer.deserialize(record, this.readView); } /** * UNSAFE!! overwrites record * causes inconsistency or data loss for overwriting everything but records of the exact same size * * @param pointer pointer to start of record * @param record record to overwrite old one with * @throws IOException */ @Deprecated public void overwriteRecordAt(long pointer, T record) throws IOException { long tmpPointer = this.writeView.getCurrentPointer(); this.writeView.resetTo(pointer); this.serializer.serialize(record, this.writeView); this.writeView.resetTo(tmpPointer); } /** * releases all of the partition's segments (pages and overflow buckets) * * @param target memory pool to release segments to */ public void clearAllMemory(List<MemorySegment> target) { // return the overflow segments if (this.overflowSegments != null) { for (int k = 0; k < this.numOverflowSegments; k++) { target.add(this.overflowSegments[k]); } } // return the partition buffers target.addAll(this.partitionPages); this.partitionPages.clear(); } /** * attempts to allocate specified number of segments and should only be used by compaction partition * fails silently if not enough segments are available since next compaction could still succeed * * @param numberOfSegments allocation count */ public void allocateSegments(int numberOfSegments) { while(getBlockCount() < numberOfSegments) { MemorySegment next = this.availableMemory.nextSegment(); if(next != null) { this.partitionPages.add(next); } else { return; } } } public void releaseSegments(int maxSegmentNumber, ArrayList<MemorySegment> target) { while(getBlockCount() > maxSegmentNumber) { target.add(partitionPages.remove(partitionPages.size()-1)); } } @Override public String toString() { return String.format("Partition %d - %d records, %d partition blocks, %d bucket overflow blocks", getPartitionNumber(), getRecordCount(), getBlockCount(), this.numOverflowSegments); } // ============================================================================================ private static final class WriteView extends AbstractPagedOutputView { private final ArrayList<MemorySegment> pages; private final MemorySegmentSource memSource; private final int sizeBits; private final int sizeMask; private int currentPageNumber; private int segmentNumberOffset; private WriteView(ArrayList<MemorySegment> pages, MemorySegmentSource memSource, int pageSize, int pageSizeBits) { super(pages.get(0), pageSize, 0); this.pages = pages; this.memSource = memSource; this.sizeBits = pageSizeBits; this.sizeMask = pageSize - 1; } @Override protected MemorySegment nextSegment(MemorySegment current, int bytesUsed) throws IOException { MemorySegment next = this.memSource.nextSegment(); if(next == null) { throw new EOFException(); } this.pages.add(next); this.currentPageNumber++; return next; } private long getCurrentPointer() { return (((long) this.currentPageNumber) << this.sizeBits) + getCurrentPositionInSegment(); } private int resetTo(long pointer) { final int pageNum = (int) (pointer >>> this.sizeBits); final int offset = (int) (pointer & this.sizeMask); this.currentPageNumber = pageNum; int posInArray = pageNum - this.segmentNumberOffset; seekOutput(this.pages.get(posInArray), offset); return posInArray; } public void setSegmentNumberOffset(int offset) { this.segmentNumberOffset = offset; } } private static final class ReadView extends AbstractPagedInputView implements SeekableDataInputView { private final ArrayList<MemorySegment> segments; private final int segmentSizeBits; private final int segmentSizeMask; private int currentSegmentIndex; private int segmentNumberOffset; public ReadView(ArrayList<MemorySegment> segments, int segmentSize, int segmentSizeBits) { super(segments.get(0), segmentSize, 0); if ((segmentSize & (segmentSize - 1)) != 0) { throw new IllegalArgumentException("Segment size must be a power of 2!"); } this.segments = segments; this.segmentSizeBits = segmentSizeBits; this.segmentSizeMask = segmentSize - 1; } @Override protected MemorySegment nextSegment(MemorySegment current) throws EOFException { if (++this.currentSegmentIndex < this.segments.size()) { return this.segments.get(this.currentSegmentIndex); } else { throw new EOFException(); } } @Override protected int getLimitForSegment(MemorySegment segment) { return this.segmentSizeMask + 1; } @Override public void setReadPosition(long position) { final int bufferNum = ((int) (position >>> this.segmentSizeBits)) - this.segmentNumberOffset; final int offset = (int) (position & this.segmentSizeMask); this.currentSegmentIndex = bufferNum; seekInput(this.segments.get(bufferNum), offset, this.segmentSizeMask + 1); } public void setSegmentNumberOffset(int offset) { this.segmentNumberOffset = offset; } } }