All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.flink.runtime.operators.hash.InMemoryPartition Maven / Gradle / Ivy

There is a newer version: 1.13.6
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF 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 org.apache.flink.runtime.operators.hash;

import java.io.EOFException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.flink.api.common.typeutils.TypeSerializer;
import org.apache.flink.core.memory.MemorySegment;
import org.apache.flink.core.memory.MemorySegmentSource;
import org.apache.flink.core.memory.SeekableDataInputView;
import org.apache.flink.runtime.memory.AbstractPagedInputView;
import org.apache.flink.runtime.memory.AbstractPagedOutputView;
import org.apache.flink.runtime.memory.ListMemorySegmentSource;

/**
 * In-memory partition with overflow buckets for {@link CompactingHashTable}
 * 
 * @param  record type
 */
public class InMemoryPartition {
	
	// --------------------------------- 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 serializer;
	
	// -------------------------------------- Record Buffers --------------------------------------------
	
	private final ArrayList partitionPages;
	
	private final ListMemorySegmentSource availableMemory;
	
	private WriteView writeView;
	
	private 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
	
	private int pageSize;							// segment size in bytes
	
	private int pageSizeInBits;
	
	// --------------------------------------------------------------------------------------------------
	
	
	
	/**
	 * 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 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(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.pageSize = pageSize;
		
		this.pageSizeInBits = pageSizeInBits;
		
		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;
	}
	
	/**
	 * resets read and write views and should only be used on compaction partition
	 */
	public void resetRWViews() {
		this.writeView.resetTo(0L);
		this.readView.setReadPosition(0L);
	}
	
	public void pushDownPages() {
		this.writeView = new WriteView(this.partitionPages, availableMemory, pageSize, pageSizeInBits);
		this.readView = new ReadView(this.partitionPages, pageSize, pageSizeInBits);
	}
	
	/**
	 * resets overflow bucket counters and returns freed memory and should only be used for resizing
	 * 
	 * @return freed memory segments
	 */
	public ArrayList resetOverflowBuckets() {
		this.numOverflowSegments = 0;
		this.nextOverflowBucket = 0;
		
		ArrayList result = new ArrayList(this.overflowSegments.length);
		for(int i = 0; i < this.overflowSegments.length; i++) {
			if(this.overflowSegments[i] != null) {
				result.add(this.overflowSegments[i]);
			}
		}
		this.overflowSegments = new MemorySegment[2];
		return result;
	}
	
	/**
	 * @return true if garbage exists in partition
	 */
	public boolean isCompacted() {
		return this.compacted;
	}
	
	/**
	 * sets compaction status (should only be set true directly after compaction and false when garbage was created)
	 * 
	 * @param compacted compaction status
	 */
	public void setIsCompacted(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 T readRecordAt(long pointer, T reuse) throws IOException {
		this.readView.setReadPosition(pointer);
		return this.serializer.deserialize(reuse, this.readView);
	}

	public T readRecordAt(long pointer) throws IOException {
		this.readView.setReadPosition(pointer);
		return this.serializer.deserialize(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 Don't use this, overwrites record and causes inconsistency or data loss for
	 * overwriting everything but records of the exact same size
	 */
	@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 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;
			}
		}
	}
	
	@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 pages;
		
		private final MemorySegmentSource memSource;
		
		private final int sizeBits;
		
		private final int sizeMask;
		
		private int currentPageNumber;
		
		private int segmentNumberOffset;
		
		
		private WriteView(ArrayList 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;
			this.segmentNumberOffset = 0;
		}
		

		@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;
		}
		
		@SuppressWarnings("unused")
		public void setSegmentNumberOffset(int offset) {
			this.segmentNumberOffset = offset;
		}
	}
	
	
	private static final class ReadView extends AbstractPagedInputView implements SeekableDataInputView {

		private final ArrayList segments;

		private final int segmentSizeBits;
		
		private final int segmentSizeMask;
		
		private int currentSegmentIndex;
		
		private int segmentNumberOffset;
		
		
		public ReadView(ArrayList 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;
			this.segmentNumberOffset = 0;
		}

		@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);
		}
		
		@SuppressWarnings("unused")
		public void setSegmentNumberOffset(int offset) {
			this.segmentNumberOffset = offset;
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy