/*
* 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.sysml.runtime.compress;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.Arrays;
import java.util.Iterator;
import org.apache.sysml.runtime.DMLRuntimeException;
import org.apache.sysml.runtime.compress.utils.LinearAlgebraUtils;
import org.apache.sysml.runtime.functionobjects.Builtin;
import org.apache.sysml.runtime.functionobjects.KahanFunction;
import org.apache.sysml.runtime.functionobjects.KahanPlus;
import org.apache.sysml.runtime.functionobjects.KahanPlusSq;
import org.apache.sysml.runtime.functionobjects.ReduceAll;
import org.apache.sysml.runtime.functionobjects.ReduceCol;
import org.apache.sysml.runtime.functionobjects.ReduceRow;
import org.apache.sysml.runtime.functionobjects.Builtin.BuiltinCode;
import org.apache.sysml.runtime.matrix.data.MatrixBlock;
import org.apache.sysml.runtime.matrix.operators.AggregateUnaryOperator;
/**
* Base class for column groups encoded with various types of bitmap encoding.
*
*
* NOTES:
* * OLE: separate storage segment length and bitmaps led to a 30% improvement
* but not applied because more difficult to support both data layouts at the
* same time (distributed/local as well as w/ and w/o low-level opt)
*/
public abstract class ColGroupOffset extends ColGroupValue
{
private static final long serialVersionUID = -1635828933479403125L;
protected static final boolean CREATE_SKIPLIST = true;
protected static final int READ_CACHE_BLKSZ = 2 * BitmapEncoder.BITMAP_BLOCK_SZ;
public static final int WRITE_CACHE_BLKSZ = 2 * BitmapEncoder.BITMAP_BLOCK_SZ;
public static boolean ALLOW_CACHE_CONSCIOUS_ROWSUMS = true;
/** Bitmaps, one per uncompressed value in {@link #_values}. */
protected int[] _ptr; //bitmap offsets per value
protected char[] _data; //linearized bitmaps (variable length)
protected boolean _zeros; //contains zero values
protected int[] _skiplist;
public ColGroupOffset() {
super();
}
/**
* Main constructor. Stores the headers for the individual bitmaps.
*
* @param colIndices
* indices (within the block) of the columns included in this
* column
* @param numRows
* total number of rows in the parent block
* @param ubm
* Uncompressed bitmap representation of the block
*/
public ColGroupOffset(int[] colIndices, int numRows, UncompressedBitmap ubm) {
super(colIndices, numRows, ubm);
_zeros = (ubm.getNumOffsets() < numRows);
}
/**
* Constructor for subclass methods that need to create shallow copies
*
* @param colIndices
* raw column index information
* @param numRows
* number of rows in the block
* @param zeros
* indicator if column group contains zero values
* @param values
* set of distinct values for the block (associated bitmaps are
* kept in the subclass)
*/
protected ColGroupOffset(int[] colIndices, int numRows, boolean zeros, double[] values) {
super(colIndices, numRows, values);
_zeros = zeros;
}
protected final int len(int k) {
return _ptr[k+1] - _ptr[k];
}
protected void createCompressedBitmaps(int numVals, int totalLen, char[][] lbitmaps) {
// compact bitmaps to linearized representation
_ptr = new int[numVals+1];
_data = new char[totalLen];
for( int i=0, off=0; i<numVals; i++ ) {
int len = lbitmaps[i].length;
_ptr[i] = off;
System.arraycopy(lbitmaps[i], 0, _data, off, len);
off += len;
}
_ptr[numVals] = totalLen;
}
@Override
public long estimateInMemorySize() {
long size = super.estimateInMemorySize();
// adding bitmaps size
size += 16; //array references
if (_data != null) {
size += 32 + _ptr.length * 4; // offsets
size += 32 + _data.length * 2; // bitmaps
}
return size;
}
//generic decompression for OLE/RLE, to be overwritten for performance
@Override
public void decompressToBlock(MatrixBlock target, int rl, int ru)
{
final int numCols = getNumCols();
final int numVals = getNumValues();
int[] colIndices = getColIndices();
// Run through the bitmaps for this column group
for (int i = 0; i < numVals; i++) {
Iterator<Integer> decoder = getDecodeIterator(i);
int valOff = i*numCols;
while (decoder.hasNext()) {
int row = decoder.next();
if( row<rl ) continue;
if( row>ru ) break;
for (int colIx = 0; colIx < numCols; colIx++)
target.appendValue(row, colIndices[colIx], _values[valOff+colIx]);
}
}
}
//generic decompression for OLE/RLE, to be overwritten for performance
@Override
public void decompressToBlock(MatrixBlock target, int[] colIndexTargets)
{
final int numCols = getNumCols();
final int numVals = getNumValues();
// Run through the bitmaps for this column group
for (int i = 0; i < numVals; i++) {
Iterator<Integer> decoder = getDecodeIterator(i);
int valOff = i*numCols;
while (decoder.hasNext()) {
int row = decoder.next();
for (int colIx = 0; colIx < numCols; colIx++) {
int origMatrixColIx = getColIndex(colIx);
int targetColIx = colIndexTargets[origMatrixColIx];
target.quickSetValue(row, targetColIx, _values[valOff+colIx]);
}
}
}
}
//generic decompression for OLE/RLE, to be overwritten for performance
@Override
public void decompressToBlock(MatrixBlock target, int colpos)
{
final int numCols = getNumCols();
final int numVals = getNumValues();
// Run through the bitmaps for this column group
for (int i = 0; i < numVals; i++) {
Iterator<Integer> decoder = getDecodeIterator(i);
int valOff = i*numCols;
while (decoder.hasNext()) {
int row = decoder.next();
target.quickSetValue(row, 0, _values[valOff+colpos]);
}
}
}
//generic get for OLE/RLE, to be overwritten for performance
//potential: skip scan (segment length agg and run length) instead of decode
@Override
public double get(int r, int c) {
//find local column index
int ix = Arrays.binarySearch(_colIndexes, c);
if( ix < 0 )
throw new RuntimeException("Column index "+c+" not in bitmap group.");
//find row index in value offset lists via scan
final int numCols = getNumCols();
final int numVals = getNumValues();
for (int i = 0; i < numVals; i++) {
Iterator<Integer> decoder = getDecodeIterator(i);
int valOff = i*numCols;
while (decoder.hasNext()) {
int row = decoder.next();
if( row == r )
return _values[valOff+ix];
else if( row > r )
break; //current value
}
}
return 0;
}
protected final void sumAllValues(double[] b, double[] c)
{
final int numVals = getNumValues();
final int numCols = getNumCols();
//vectMultiplyAdd over cols instead of dotProduct over vals because
//usually more values than columns
for( int i=0, off=0; i<numCols; i++, off+=numVals )
LinearAlgebraUtils.vectMultiplyAdd(b[i], _values, c, off, 0, numVals);
}
protected final double mxxValues(int bitmapIx, Builtin builtin)
{
final int numCols = getNumCols();
final int valOff = bitmapIx * numCols;
double val = Double.MAX_VALUE * ((builtin.getBuiltinCode()==BuiltinCode.MAX)?-1:1);
for( int i = 0; i < numCols; i++ )
val = builtin.execute2(val, _values[valOff+i]);
return val;
}
public char[] getBitmaps() {
return _data;
}
public int[] getBitmapOffsets() {
return _ptr;
}
public boolean hasZeros() {
return _zeros;
}
/**
* @param k
* index of a specific compressed bitmap (stored in subclass,
* index same as {@link #getValues})
* @return an object for iterating over the row offsets in this bitmap. Only
* valid until the next call to this method. May be reused across
* calls.
*/
public abstract Iterator<Integer> getDecodeIterator(int k);
//TODO getDecodeIterator(int k, int rl, int ru)
/**
* Utility function of sparse-unsafe operations.
*
* @param ind row indicator vector of non zeros
* @return offsets
* @throws DMLRuntimeException if DMLRuntimeException occurs
*/
protected int[] computeOffsets(boolean[] ind)
throws DMLRuntimeException
{
//determine number of offsets
int numOffsets = 0;
for( int i=0; i<ind.length; i++ )
numOffsets += ind[i] ? 1 : 0;
//create offset lists
int[] ret = new int[numOffsets];
for( int i=0, pos=0; i<ind.length; i++ )
if( ind[i] )
ret[pos++] = i;
return ret;
}
@Override
public void readFields(DataInput in)
throws IOException
{
_numRows = in.readInt();
int numCols = in.readInt();
int numVals = in.readInt();
_zeros = in.readBoolean();
//read col indices
_colIndexes = new int[ numCols ];
for( int i=0; i<numCols; i++ )
_colIndexes[i] = in.readInt();
//read distinct values
_values = new double[numVals*numCols];
for( int i=0; i<numVals*numCols; i++ )
_values[i] = in.readDouble();
//read bitmaps
int totalLen = in.readInt();
_ptr = new int[numVals+1];
_data = new char[totalLen];
for( int i=0, off=0; i<numVals; i++ ) {
int len = in.readInt();
_ptr[i] = off;
for( int j=0; j<len; j++ )
_data[off+j] = in.readChar();
off += len;
}
_ptr[numVals] = totalLen;
}
@Override
public void write(DataOutput out)
throws IOException
{
int numCols = getNumCols();
int numVals = getNumValues();
out.writeInt(_numRows);
out.writeInt(numCols);
out.writeInt(numVals);
out.writeBoolean(_zeros);
//write col indices
for( int i=0; i<_colIndexes.length; i++ )
out.writeInt( _colIndexes[i] );
//write distinct values
for( int i=0; i<_values.length; i++ )
out.writeDouble(_values[i]);
//write bitmaps (lens and data, offset later recreated)
int totalLen = 0;
for( int i=0; i<numVals; i++ )
totalLen += len(i);
out.writeInt(totalLen);
for( int i=0; i<numVals; i++ ) {
int len = len(i);
int off = _ptr[i];
out.writeInt(len);
for( int j=0; j<len; j++ )
out.writeChar(_data[off+j]);
}
}
@Override
public long getExactSizeOnDisk() {
long ret = 13; //header
//col indices
ret += 4 * _colIndexes.length;
//distinct values (groups of values)
ret += 8 * _values.length;
//actual bitmaps
ret += 4; //total length
for( int i=0; i<getNumValues(); i++ )
ret += 4 + 2 * len(i);
return ret;
}
@Override
public void unaryAggregateOperations(AggregateUnaryOperator op, MatrixBlock result, int rl, int ru)
throws DMLRuntimeException
{
//sum and sumsq (reduceall/reducerow over tuples and counts)
if( op.aggOp.increOp.fn instanceof KahanPlus || op.aggOp.increOp.fn instanceof KahanPlusSq )
{
KahanFunction kplus = (op.aggOp.increOp.fn instanceof KahanPlus) ?
KahanPlus.getKahanPlusFnObject() : KahanPlusSq.getKahanPlusSqFnObject();
if( op.indexFn instanceof ReduceAll )
computeSum(result, kplus);
else if( op.indexFn instanceof ReduceCol )
computeRowSums(result, kplus, rl, ru);
else if( op.indexFn instanceof ReduceRow )
computeColSums(result, kplus);
}
//min and max (reduceall/reducerow over tuples only)
else if(op.aggOp.increOp.fn instanceof Builtin
&& (((Builtin)op.aggOp.increOp.fn).getBuiltinCode()==BuiltinCode.MAX
|| ((Builtin)op.aggOp.increOp.fn).getBuiltinCode()==BuiltinCode.MIN))
{
Builtin builtin = (Builtin) op.aggOp.increOp.fn;
if( op.indexFn instanceof ReduceAll )
computeMxx(result, builtin, _zeros);
else if( op.indexFn instanceof ReduceCol )
computeRowMxx(result, builtin, rl, ru);
else if( op.indexFn instanceof ReduceRow )
computeColMxx(result, builtin, _zeros);
}
}
protected abstract void computeSum(MatrixBlock result, KahanFunction kplus);
protected abstract void computeRowSums(MatrixBlock result, KahanFunction kplus, int rl, int ru);
protected abstract void computeColSums(MatrixBlock result, KahanFunction kplus);
protected abstract void computeRowMxx(MatrixBlock result, Builtin builtin, int rl, int ru);
}