blob: fd0b3906bcd82489712eb00c2ac09b41dc66fd01 [file]
/*
* 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.sysds.runtime.data;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import org.apache.sysds.utils.MemoryEstimates;
/**
* SparseBlock implementation that realizes a 'modified compressed sparse column' representation, where each compressed
* column is stored as a separate SparseRow object which provides flexibility for unsorted column appends without the
* need for global reshifting of values/indexes but it incurs additional memory overhead per column for object/array
* headers per column which also slows down memory-bound operations due to higher memory bandwidth requirements.
*
* TODO implement row interface of sparse blocks (can be slow but must be correct;
* additionally, we can expose the column API for efficient use in specific operations)
*/
public class SparseBlockMCSC extends SparseBlock {
private static final long serialVersionUID = 112364695245614881L;
private SparseRow[] _columns = null;
private int _clenInferred = -1;
private int _rlen = -1;
public SparseBlockMCSC(SparseBlock sblock, int clen) {
_clenInferred = clen;
_rlen = sblock.numRows();
initialize(sblock);
}
public SparseBlockMCSC(SparseBlock sblock) {
_rlen = sblock.numRows();
initialize(sblock);
}
private void initialize(SparseBlock sblock) {
int clen = 0;
if(sblock instanceof SparseBlockMCSC) {
SparseRow[] originalColumns = ((SparseBlockMCSC) sblock)._columns;
_columns = new SparseRow[originalColumns.length];
for(int i = 0; i < _columns.length; i++) {
if(originalColumns[i] != null)
_columns[i] = originalColumns[i].copy(true);
}
}
else if(sblock instanceof SparseBlockMCSR) {
SparseRow[] originalRows = ((SparseBlockMCSR) sblock).getRows();
Map<Integer, Integer> columnSizes = new HashMap<>();
if(_clenInferred == -1) {
for(SparseRow row : originalRows) {
if(row != null && !row.isEmpty()) {
for(int i = 0; i < row.size(); i++) {
int rowIndex = row.indexes()[i];
columnSizes.put(rowIndex, columnSizes.getOrDefault(rowIndex, 0) + 1);
}
}
}
clen = columnSizes.keySet().stream().max(Integer::compare).orElseThrow(NoSuchElementException::new);
_columns = new SparseRow[clen + 1];
}
else {
_columns = new SparseRow[_clenInferred];
}
for(int i = 0; i < _columns.length; i++) {
int columnSize = columnSizes.getOrDefault(i, -1);
if(columnSize == -1) {
continue;
}
else if(columnSize == 1) {
_columns[i] = new SparseRowScalar();
}
else { //columnSize > 1
_columns[i] = new SparseRowVector(columnSize);
}
}
int[] rowIndexes = null;
double[] values = null;
int rowPosition = 0;
for(SparseRow row : originalRows) {
if(row != null && !row.isEmpty()) {
rowIndexes = row.indexes();
values = row.values();
for(int i = 0; i < row.size(); i++) {
int rowIndex = rowIndexes[i];
double currentValue = values[i];
_columns[rowIndex].set(rowPosition, currentValue);
}
}
rowPosition++;
}
}
// general case SparseBlock
else {
HashMap<Integer, Integer> columnSizes = new HashMap<>();
int[] columnIndexes = sblock.indexes(0);
for(int col : columnIndexes) {
columnSizes.put(col, columnSizes.getOrDefault(col, 0) + 1);
}
clen = columnSizes.keySet().stream().max(Integer::compare).orElseThrow(NoSuchElementException::new);
if(_clenInferred == -1)
_columns = new SparseRow[clen + 1];
else
_columns = new SparseRow[_clenInferred];
for(int i = 0; i < _columns.length; i++) {
int columnSize = columnSizes.getOrDefault(i, -1);
if(columnSize == -1) {
continue;
}
else if(columnSize == 1) {
_columns[i] = new SparseRowScalar();
}
else { //columnSize > 1
_columns[i] = new SparseRowVector(columnSize);
}
}
double[] vals = sblock.values(0);
int[] cols = sblock.indexes(0);
int row = 0;
int i = 0;
while(i < vals.length) {
int rowSize = sblock.size(row);
for(int j = i; j < i + rowSize; j++) {
_columns[cols[j]].set(row, vals[j]);
}
i += rowSize;
row++;
}
}
}
public SparseBlockMCSC(SparseRow[] cols, boolean deep, int rlen) {
_rlen = rlen;
if(deep) {
_columns = new SparseRow[cols.length];
for(int i = 0; i < _columns.length; i++) {
_columns[i] = (cols[i].size() == 1) ? new SparseRowScalar(cols[i].indexes()[0],
cols[i].values()[0]) : new SparseRowVector(cols[i]);
}
}
else {
_columns = cols;
}
}
public SparseBlockMCSC(int clen) {
_columns = new SparseRow[clen];
}
public SparseBlockMCSC(int rlen, int clen) {
_rlen = rlen;
_columns = new SparseRow[clen];
}
/**
* Get the estimated in-memory size of the sparse block in MCSC with the given dimensions w/o accounting for
* overallocation.
*
* @param nrows number of rows
* @param ncols number of columns
* @param sparsity sparsity ratio
* @return memory estimate
*/
public static long estimateSizeInMemory(long nrows, long ncols, double sparsity) {
double nnz = Math.ceil(sparsity * nrows * ncols);
double clen = Math.min(nrows, nnz); // num sparse column objects
double rnnz = Math.max(SparseRowVector.initialCapacity, nnz / clen);
// Each sparse column has a fixed overhead of 16B (object) + 12B (3 ints),
// 24B (int array), 24B (double array), i.e., in total 76B
// Each non-zero value requires 12B for the row-index/value pair.
// Overheads for arrays, objects, and references refer to 64bit JVMs
// If nnz < columns we have guaranteed also empty columns.
double size = 16; //object
size += MemoryEstimates.objectArrayCost(ncols); //references
long sparseColSize = 16; // object
sparseColSize += 2 * 4; // 2 integers + padding
sparseColSize += MemoryEstimates.intArrayCost(0);
sparseColSize += MemoryEstimates.doubleArrayCost(0);
sparseColSize += 12 * Math.max(1, rnnz); //avoid bias by down cast for ultra-sparse
size += clen * sparseColSize; //sparse columns
// robustness for long overflows
return (long) Math.min(size, Long.MAX_VALUE);
}
@Override
public long getExactSizeInMemory() {
double size = 16; //object
size += MemoryEstimates.objectArrayCost(_columns.length); //references
for(SparseRow sc : _columns) {
if(sc == null)
continue;
long sparseColSize = 16; // object
if(sc instanceof SparseRowScalar) {
sparseColSize += 12;
}
else { //SparseRowVector
sparseColSize += 2 * 4; // 2 integers
sparseColSize += MemoryEstimates.intArrayCost(0);
sparseColSize += MemoryEstimates.doubleArrayCost(0);
sparseColSize += 12 * ((SparseRowVector) sc).capacity();
}
size += sparseColSize; //sparse columns
}
// robustness for long overflows
return (long) Math.min(size, Long.MAX_VALUE);
}
///////////////////
//SparseBlock implementation
@Override
public void allocate(int r) {
for(int i = 0; i < _columns.length; i++) {
if(!isAllocatedCol(i))
_columns[i] = new SparseRowVector();
}
}
public void allocateCol(int c) {
if(!isAllocatedCol(c)) {
_columns[c] = new SparseRowVector();
}
}
@Override
public void allocate(int r, int nnz) {
allocate(r);
}
public void allocateCol(int c, int nnz) {
if(!isAllocated(c)) {
_columns[c] = (nnz == 1) ? new SparseRowScalar() : new SparseRowVector(nnz);
}
}
@Override
public void allocate(int r, int ennz, int maxnnz) {
allocate(r);
}
public void allocateCol(int c, int ennz, int maxnnz) {
if(!isAllocated(c)) {
_columns[c] = (ennz == 1) ? new SparseRowScalar() : new SparseRowVector(ennz, maxnnz);
}
}
@Override
public void compact(int r) {
for(int i = 0; i < _columns.length; i++) {
compactCol(i);
}
}
public void compactCol(int c) {
if(isAllocated(c)) {
if(_columns[c] instanceof SparseRowVector && _columns[c].size() > SparseBlock.INIT_CAPACITY &&
_columns[c].size() * SparseBlock.RESIZE_FACTOR1 < ((SparseRowVector) _columns[c]).capacity()) {
((SparseRowVector) _columns[c]).compact();
}
else if(_columns[c] instanceof SparseRowScalar) {
SparseRowScalar s = (SparseRowScalar) _columns[c];
if(s.getValue() == 0)
_columns[c] = null;
}
}
}
@Override
public int numRows() {
return _rlen;
}
public int numCols() {
return _columns.length;
}
@Override
public boolean isThreadSafe() {
return true;
}
@Override
public boolean isContiguous() {
return false;
}
@Override
public boolean isAllocated(int r) {
for(SparseRow col : _columns)
if(col == null)
return false;
return true;
}
public boolean isAllocatedCol(int c) {
return _columns[c] != null;
}
@Override
public void reset() {
for(SparseRow col : _columns) {
if(col != null) {
col.reset(col.size(), Integer.MAX_VALUE);
}
}
}
@Override
public void reset(int ennz, int maxnnz) {
for(SparseRow col : _columns) {
if(col != null) {
col.reset(ennz, maxnnz);
}
}
}
@Override
public void reset(int r, int ennz, int maxnnz) {
for(int i = 0; i < _columns.length; i++) {
if(isAllocatedCol(i)) {
if(_columns[i] instanceof SparseRowScalar && _columns[i].indexes()[0] == r)
_columns[i].set(r, 0);
else if(_columns[i] instanceof SparseRowVector)
_columns[i].set(r, 0);
}
}
}
public void resetCol(int c, int ennz, int maxnnz) {
if(isAllocatedCol(c)) {
_columns[c].reset(ennz, maxnnz);
}
}
@Override
public long size() {
long nnz = 0;
for(SparseRow col : _columns) {
if(col != null) {
nnz += col.size();
}
}
return nnz;
}
@Override
public int size(int r) {
int nnz = 0;
for(int i = 0; i < _columns.length; i++) {
if(isAllocatedCol(i))
nnz += (_columns[i].get(r) != 0) ? 1 : 0;
}
return nnz;
}
public int sizeCol(int c) {
//prior check with isEmpty(r) expected
return isAllocated(c) ? _columns[c].size() : 0;
}
@Override
public long size(int rl, int ru) {
long nnz = 0;
for(int i = 0; i < _columns.length; i++) {
if(isAllocatedCol(i)) {
for(int j = rl; j < ru; j++)
nnz += (_columns[i].get(j) != 0) ? 1 : 0;
}
}
return nnz;
}
public long sizeCol(int cl, int cu) {
long nnz = 0;
for(int i = cl; i < cu; i++) {
nnz += isAllocated(i) ? _columns[i].size() : 0;
}
return nnz;
}
@Override
public long size(int rl, int ru, int cl, int cu) {
long nnz = 0;
for(int i = cl; i < cu; i++) {
if(!isEmptyCol(i)) {
int start = posFIndexGTECol(rl, i);
int end = posFIndexLTECol(ru - 1, i);
nnz += (start != -1 && end != -1) ? (end - start + 1) : 0;
}
}
return nnz;
}
@Override
public boolean isEmpty(int r) {
for(int i = 0; i < _columns.length; i++) {
if(!isAllocatedCol(i))
continue;
else if(_columns[i].get(r) != 0)
return false;
}
return true;
}
public boolean isEmptyCol(int c) {
return _columns[c] == null || _columns[c].isEmpty();
}
@Override
public boolean checkValidity(int rlen, int clen, long nnz, boolean strict) {
//1. Correct meta data
if(rlen < 0 || clen < 0)
throw new RuntimeException("Invalid block dimensions: (" + rlen + ", " + clen + ").");
//2. Correct array lengths
if(size() < nnz)
throw new RuntimeException("Incorrect size: " + size() + " (expected: " + nnz + ").");
//3. Sorted column indices per row
for(int i = 0; i < clen; i++) {
if(isEmpty(i))
continue;
int apos = pos(i);
int alen = size(i);
int[] aix = indexes(i);
double[] avals = values(i);
for(int k = apos + 1; k < apos + alen; k++) {
if(aix[k - 1] >= aix[k] | aix[k - 1] < 0) {
throw new RuntimeException(
"Wrong sparse column ordering, at column=" + i + ", pos=" + k + " with row indexes " +
aix[k - 1] + ">=" + aix[k]);
}
if(avals[k] == 0) {
throw new RuntimeException(
"The values are expected to be non zeros " + "but zero at column: " + i + ", row pos: " + k);
}
}
}
//4. A capacity that is no larger than nnz times resize factor
for(int i = 0; i < clen; i++) {
long max_size = (long) Math.max(nnz * RESIZE_FACTOR1, INIT_CAPACITY);
if(!isEmpty(i) && values(i).length > max_size) {
throw new RuntimeException(
"The capacity is larger than nnz times a resize factor(=2). " + "Actual length = " +
values(i).length + ", should not exceed " + max_size);
}
}
return true;
}
@Override
public int[] indexes(int r) {
//prior check with isEmpty(r) expected
int nnz = size(r);
int[] idx = new int[nnz];
int index = 0;
for(int i = 0; i < _columns.length; i++) {
if(isAllocatedCol(i) && _columns[i].get(r) != 0) {
idx[index] = i;
index++;
}
}
return idx;
}
public int[] indexesCol(int c) {
//prior check with isEmpty(c) expected
return _columns[c].indexes();
}
@Override
public double[] values(int r) {
//prior check with isEmpty(r) expected
int nnz = size(r);
double[] vals = new double[nnz];
int index = 0;
for(int i = 0; i < _columns.length; i++) {
if(isAllocatedCol(i) && _columns[i].get(r) != 0) {
vals[index] = _columns[i].get(r);
index++;
}
}
return vals;
}
public double[] valuesCol(int c) {
//prior check with isEmpty(c) expected
return _columns[c].values();
}
@Override
public int pos(int r) {
//arrays per row (always start 0)
return 0;
}
@Override
public boolean set(int r, int c, double v) {
if(!isAllocatedCol(c)) {
_columns[c] = new SparseRowScalar();
}
else if(_columns[c] instanceof SparseRowScalar && !_columns[c].isEmpty()) {
_columns[c] = new SparseRowVector(_columns[c]);
}
return _columns[c].set(r, v);
}
@Override
public void set(int r, SparseRow row, boolean deep) {
reset(r, 1, 1);
int nnz = row.size();
for(int i = 0; i < nnz; i++) {
set(r, row.indexes()[i], row.values()[i]);
}
}
public void setCol(int c, SparseRow col, boolean deep) {
//copy values into existing column to avoid allocation
if(isAllocatedCol(c) && _columns[c] instanceof SparseRowVector &&
((SparseRowVector) _columns[c]).capacity() >= col.size() && deep) {
((SparseRowVector) _columns[c]).copy(col);
//set new sparse column (incl allocation if required)
}
else {
_columns[c] = (deep && col != null) ? new SparseRowVector(col) : col;
}
}
@Override
public boolean add(int r, int c, double v) {
if(!isAllocatedCol(c)) {
_columns[c] = new SparseRowScalar();
}
else if(_columns[c] instanceof SparseRowScalar && !_columns[c].isEmpty()) {
SparseRowScalar s = (SparseRowScalar) _columns[c];
if(s.getIndex() == r) {
return s.set(s.getIndex(), v + s.getValue());
}
else {
_columns[c] = new SparseRowVector(_columns[c]);
}
}
return _columns[c].add(r, v);
}
@Override
public void append(int r, int c, double v) {
if(v == 0) {
return;
}
else if(_columns[c] == null) {
_columns[c] = new SparseRowScalar(r, v);
}
else {
_columns[c] = _columns[c].append(r, v);
}
}
@Override
public void setIndexRange(int r, int cl, int cu, double[] v, int vix, int vlen) {
int idx = vix;
for(int i = cl; i < cu; i++) {
set(r, i, v[idx]);
idx++;
}
}
public void setIndexRangeCol(int c, int rl, int ru, double[] v, int vix, int vlen) {
if(!isAllocatedCol(c)) {
_columns[c] = new SparseRowVector();
}
else if(_columns[c] instanceof SparseRowScalar) {
_columns[c] = new SparseRowVector(_columns[c]);
}
((SparseRowVector) _columns[c]).setIndexRange(rl, ru - 1, v, vix, vlen);
}
@Override
public void setIndexRange(int r, int cl, int cu, double[] v, int[] vix, int vpos, int vlen) {
for(int i = vpos; i < (vpos + vlen); i++) {
set(r, vix[i], v[i]);
}
}
public void setIndexRangeCol(int c, int rl, int ru, double[] v, int[] vix, int vpos, int vlen) {
if(!isAllocatedCol(c)) {
_columns[c] = new SparseRowVector();
}
else if(_columns[c] instanceof SparseRowScalar) {
_columns[c] = new SparseRowVector(_columns[c]);
}
//different sparse row semantics: upper bound inclusive
((SparseRowVector) _columns[c]).setIndexRange(rl, ru - 1, v, vix, vpos, vlen);
}
@Override
public void deleteIndexRange(int r, int cl, int cu) {
for(int i = cl; i < cu; i++) {
if(isAllocatedCol(i)) {
if(_columns[i] instanceof SparseRowScalar && _columns[i].indexes()[0] == r)
_columns[i].set(r, 0);
else if(_columns[i] instanceof SparseRowVector)
_columns[i].set(r, 0);
}
}
}
public void deleteIndexRangeCol(int c, int rl, int ru) {
//prior check with isEmpty(c) expected
//different sparse row semantics: upper bound inclusive
if(_columns[c] instanceof SparseRowScalar) {
_columns[c] = new SparseRowVector(_columns[c]);
}
((SparseRowVector) _columns[c]).deleteIndexRange(rl, ru - 1);
}
@Override
public void sort() {
for(SparseRow col : _columns) {
if(col != null && !col.isEmpty()) {
col.sort();
}
}
}
@Override
public void sort(int r) {
//prior check with isEmpty(c) expected
sort();
}
public void sortCol(int c) {
//prior check with isEmpty(c) expected
_columns[c].sort();
}
@Override
public double get(int r, int c) {
if(!isAllocatedCol(c)) {
return 0;
}
return _columns[c].get(r);
}
@Override
public SparseRow get(int r) {
SparseRow row = (size(r) == 1) ? new SparseRowScalar() : new SparseRowVector(size(r));
double v = 0;
for(int i = 0; i < _columns.length; i++) {
v = get(r, i);
if(v != 0)
row.set(i, v);
}
return row;
}
public SparseRow getCol(int c) {
return _columns[c];
}
@Override
public int posFIndexLTE(int r, int c) {
//prior check with isEmpty(r) expected
SparseRow row = get(r);
return ((SparseRowVector) row).searchIndexesFirstLTE(c);
}
public int posFIndexLTECol(int r, int c) {
//prior check with isEmpty(c) expected
if(_columns[c] instanceof SparseRowScalar) {
_columns[c] = new SparseRowVector(_columns[c]);
}
return ((SparseRowVector) _columns[c]).searchIndexesFirstLTE(r);
}
@Override
public int posFIndexGTE(int r, int c) {
SparseRow row = get(r);
return row.searchIndexesFirstGTE(c);
}
public int posFIndexGTECol(int r, int c) {
return _columns[c].searchIndexesFirstGTE(r);
}
@Override
public int posFIndexGT(int r, int c) {
SparseRow row = get(r);
return row.searchIndexesFirstGT(c);
}
public int posFIndexGTCol(int r, int c) {
return _columns[c].searchIndexesFirstGT(r);
}
@Override
public Iterator<Integer> getNonEmptyRowsIterator(int rl, int ru) {
return new NonEmptyRowsIteratorMCSC(rl, ru);
}
public class NonEmptyRowsIteratorMCSC implements Iterator<Integer> {
private int _rpos;
private final int _ru;
public NonEmptyRowsIteratorMCSC(int rl, int ru) {
_rpos = rl;
_ru = ru;
}
@Override
public boolean hasNext() {
while(_rpos < _ru && isEmpty(_rpos))
_rpos++;
return _rpos < _ru;
}
@Override
public Integer next() {
return _rpos++;
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
final int nCol = numCols();
sb.append("SparseBlockMCSC: clen=");
sb.append(nCol);
sb.append(", nnz=");
sb.append(size());
sb.append("\n");
final int colDigits = (int) Math.max(Math.ceil(Math.log10(nCol)), 1);
for(int i = 0; i < nCol; i++) {
if(isEmptyCol(i))
continue;
sb.append(String.format("%0" + colDigits + "d %s\n", i, _columns[i].toString()));
}
return sb.toString();
}
/**
* Helper function for MCSC
*
* @return the underlying array of columns {@link SparseRow}
*/
public SparseRow[] getCols() {
return _columns;
}
/**
* Helper function for MCSC
*
* @return the corresponding array of rows {@link SparseRow}
*/
public SparseRow[] getRows() {
SparseRow[] rows = new SparseRow[numRows()];
for(int i = 0; i < numRows(); i++) {
rows[i] = get(i);
}
return rows;
}
public Iterator<Integer> getNonEmptyColumnsIterator(int cl, int cu) {
return new NonEmptyColumnsIteratorMCSC(cl, cu);
}
public class NonEmptyColumnsIteratorMCSC implements Iterator<Integer> {
private int _cpos;
private final int _cu;
public NonEmptyColumnsIteratorMCSC(int cl, int cu) {
_cpos = cl;
_cu = cu;
}
@Override
public boolean hasNext() {
while(_cpos < _cu && isEmptyCol(_cpos)) {
_cpos++;
}
return _cpos < _cu;
}
@Override
public Integer next() {
return _cpos++;
}
}
@SuppressWarnings("unused")
private class SparseNonEmptyColumnIterable implements Iterable<Integer> {
private final int _cl; //column lower
private final int _cu; //column upper
protected SparseNonEmptyColumnIterable(int cl, int cu) {
_cl = cl;
_cu = cu;
}
@Override
public Iterator<Integer> iterator() {
//use specialized non-empty row iterators of sparse blocks
return getNonEmptyColumnsIterator(_cl, _cu);
}
}
}