Optimized performance for Double BufferMatrix product

This commit is contained in:
Alexander Nozik 2019-01-21 10:22:37 +03:00
parent a2a7ddcdda
commit a9e06c261a
15 changed files with 263 additions and 113 deletions

View File

@ -1,13 +1,14 @@
plugins { plugins {
id "java" id "java"
id "kotlin"
id "me.champeau.gradle.jmh" version "0.4.7" id "me.champeau.gradle.jmh" version "0.4.7"
id 'org.jetbrains.kotlin.jvm'
} }
dependencies { dependencies {
compile project(":kmath-core") api project(":kmath-core")
compile project(":kmath-coroutines") api project(":kmath-coroutines")
compile project(":kmath-commons") api project(":kmath-commons")
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
//jmh project(':kmath-core') //jmh project(':kmath-core')
} }
@ -16,3 +17,18 @@ jmh{
} }
jmhClasses.dependsOn(compileKotlin) jmhClasses.dependsOn(compileKotlin)
repositories {
maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
mavenCentral()
}
compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}

View File

@ -1,6 +1,5 @@
package scientifik.kmath.linear package scientifik.kmath.linear
import org.apache.commons.math3.linear.Array2DRowRealMatrix
import kotlin.random.Random import kotlin.random.Random
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
@ -28,17 +27,13 @@ fun main() {
println("[kmath] Inversion of $n matrices $dim x $dim finished in $inverseTime millis") println("[kmath] Inversion of $n matrices $dim x $dim finished in $inverseTime millis")
//commons //commons-math
val cmSolver = CMSolver val cmSolver = CMSolver
val commonsMatrix = Array2DRowRealMatrix(dim, dim)
matrix.elements().forEach { (index, value) -> commonsMatrix.setEntry(index[0], index[1], value) }
val commonsTime = measureTimeMillis { val commonsTime = measureTimeMillis {
val cm = matrix.toCM() val cm = matrix.toCM() //avoid overhead on conversion
repeat(n) { repeat(n) {
//overhead on coversion could be mitigated
val res = cmSolver.inverse(cm) val res = cmSolver.inverse(cm)
} }
} }

View File

@ -0,0 +1,30 @@
package scientifik.kmath.linear
import kotlin.random.Random
import kotlin.system.measureTimeMillis
fun main() {
val random = Random(12224)
val dim = 1000
//creating invertible matrix
val matrix1 = Matrix.real(dim, dim) { i, j -> if (i <= j) random.nextDouble() else 0.0 }
val matrix2 = Matrix.real(dim, dim) { i, j -> if (i <= j) random.nextDouble() else 0.0 }
// //warmup
// matrix1 dot matrix2
val cmMatrix1 = matrix1.toCM()
val cmMatrix2 = matrix2.toCM()
val cmTime = measureTimeMillis {
cmMatrix1 dot cmMatrix2
}
println("CM implementation time: $cmTime")
val genericTime = measureTimeMillis {
val res = matrix1 dot matrix2
}
println("Generic implementation time: $genericTime")
}

View File

@ -28,7 +28,7 @@ allprojects {
apply(plugin = "com.jfrog.artifactory") apply(plugin = "com.jfrog.artifactory")
group = "scientifik" group = "scientifik"
version = "0.0.3-dev-2" version = "0.0.3-dev-3"
repositories { repositories {
maven("https://dl.bintray.com/kotlin/kotlin-eap") maven("https://dl.bintray.com/kotlin/kotlin-eap")

View File

@ -2,14 +2,11 @@ plugins {
kotlin("jvm") kotlin("jvm")
} }
description = "Commons math binding for kmath"
dependencies { dependencies {
api(project(":kmath-core")) api(project(":kmath-core"))
api("org.apache.commons:commons-math3:3.6.1") api("org.apache.commons:commons-math3:3.6.1")
testImplementation("org.jetbrains.kotlin:kotlin-test") testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit") testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
} }
//dependencies {
//// compile(project(":kmath-core"))
//// //compile project(":kmath-coroutines")
////}

View File

@ -55,7 +55,7 @@ object CMMatrixContext : MatrixContext<Double, RealField> {
return CMVector(ArrayRealVector(array)) return CMVector(ArrayRealVector(array))
} }
override fun Matrix<Double>.dot(other: Matrix<Double>): Matrix<Double> = this.toCM().dot(other.toCM()) override fun Matrix<Double>.dot(other: Matrix<Double>): Matrix<Double> = CMMatrix(this.toCM().origin.multiply(other.toCM().origin))
} }
operator fun CMMatrix.plus(other: CMMatrix): CMMatrix = CMMatrix(this.origin.add(other.origin)) operator fun CMMatrix.plus(other: CMMatrix): CMMatrix = CMMatrix(this.origin.add(other.origin))

View File

@ -3,14 +3,11 @@ plugins {
} }
kotlin { kotlin {
targets { jvm {
fromPreset(presets.jvm, 'jvm') compilations["main"].kotlinOptions.jvmTarget = "1.8"
fromPreset(presets.js, 'js')
// For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64
// For Linux, preset should be changed to e.g. presets.linuxX64
// For MacOS, preset should be changed to e.g. presets.macosX64
//fromPreset(presets.mingwX64, 'mingw')
} }
js()
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {

View File

@ -0,0 +1,97 @@
package scientifik.kmath.linear
import scientifik.kmath.operations.Ring
import scientifik.kmath.structures.*
/**
* Basic implementation of Matrix space based on [NDStructure]
*/
class BufferMatrixContext<T : Any, R : Ring<T>>(
override val elementContext: R,
private val bufferFactory: BufferFactory<T>
) : MatrixContext<T, R> {
override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T): BufferMatrix<T> {
val buffer = bufferFactory(rows * columns) { offset -> initializer(offset / columns, offset % columns) }
return BufferMatrix(rows, columns, buffer)
}
override fun point(size: Int, initializer: (Int) -> T): Point<T> = bufferFactory(size, initializer)
}
class BufferMatrix<T : Any>(
override val rowNum: Int,
override val colNum: Int,
val buffer: Buffer<out T>,
override val features: Set<MatrixFeature> = emptySet()
) : Matrix<T> {
init {
if (buffer.size != rowNum * colNum) {
error("Dimension mismatch for matrix structure")
}
}
override val shape: IntArray get() = intArrayOf(rowNum, colNum)
override fun get(index: IntArray): T = get(index[0], index[1])
override fun get(i: Int, j: Int): T = buffer[i * colNum + j]
override fun elements(): Sequence<Pair<IntArray, T>> = sequence {
for (i in 0 until rowNum) {
for (j in 0 until colNum) {
yield(intArrayOf(i, j) to get(i, j))
}
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
return when (other) {
is NDStructure<*> -> return NDStructure.equals(this, other)
else -> false
}
}
override fun hashCode(): Int {
var result = buffer.hashCode()
result = 31 * result + features.hashCode()
return result
}
override fun toString(): String {
return if (rowNum <= 5 && colNum <= 5) {
"Matrix(rowsNum = $rowNum, colNum = $colNum, features=$features)\n" +
rows.asSequence().joinToString(prefix = "(", postfix = ")", separator = "\n ") {
it.asSequence().joinToString(separator = "\t") { it.toString() }
}
} else {
"Matrix(rowsNum = $rowNum, colNum = $colNum, features=$features)"
}
}
}
/**
* Optimized dot product for real matrices
*/
infix fun BufferMatrix<Double>.dot(other: BufferMatrix<Double>): BufferMatrix<Double> {
if (this.colNum != other.rowNum) error("Matrix dot operation dimension mismatch: ($rowNum, $colNum) x (${other.rowNum}, ${other.colNum})")
val array = DoubleArray(this.rowNum * other.colNum)
val a = this.buffer.array
val b = other.buffer.array
for (i in (0 until rowNum)) {
for (j in (0 until other.colNum)) {
for (k in (0 until colNum)) {
array[i * other.colNum + j] += a[i * colNum + k] * b[k * other.colNum + j]
}
}
}
val buffer = DoubleBuffer(array)
return BufferMatrix(rowNum, other.colNum, buffer)
}

View File

@ -75,13 +75,13 @@ interface MatrixContext<T : Any, R : Ring<T>> {
/** /**
* Non-boxing double matrix * Non-boxing double matrix
*/ */
val real: MatrixContext<Double, RealField> = StructureMatrixContext(RealField, DoubleBufferFactory) val real = BufferMatrixContext(RealField, DoubleBufferFactory)
/** /**
* A structured matrix with custom buffer * A structured matrix with custom buffer
*/ */
fun <T : Any, R : Ring<T>> buffered(ring: R, bufferFactory: BufferFactory<T> = ::boxing): MatrixContext<T, R> = fun <T : Any, R : Ring<T>> buffered(ring: R, bufferFactory: BufferFactory<T> = ::boxing): MatrixContext<T, R> =
StructureMatrixContext(ring, bufferFactory) BufferMatrixContext(ring, bufferFactory)
/** /**
* Automatic buffered matrix, unboxed if it is possible * Automatic buffered matrix, unboxed if it is possible
@ -152,11 +152,10 @@ interface Matrix<T : Any> : NDStructure<T> {
* Build a square matrix from given elements. * Build a square matrix from given elements.
*/ */
fun <T : Any> build(vararg elements: T): Matrix<T> { fun <T : Any> build(vararg elements: T): Matrix<T> {
val buffer = elements.asBuffer()
val size: Int = sqrt(elements.size.toDouble()).toInt() val size: Int = sqrt(elements.size.toDouble()).toInt()
if (size * size != elements.size) error("The number of elements ${elements.size} is not a full square") if (size * size != elements.size) error("The number of elements ${elements.size} is not a full square")
val structure = Mutable2DStructure(size, size, buffer) val buffer = elements.asBuffer()
return StructureMatrix(structure) return BufferMatrix(size, size, buffer)
} }
} }
} }

View File

@ -1,74 +0,0 @@
package scientifik.kmath.linear
import scientifik.kmath.operations.Ring
import scientifik.kmath.structures.*
/**
* Basic implementation of Matrix space based on [NDStructure]
*/
class StructureMatrixContext<T : Any, R : Ring<T>>(
override val elementContext: R,
private val bufferFactory: BufferFactory<T>
) : MatrixContext<T, R> {
override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T): Matrix<T> {
val structure =
ndStructure(intArrayOf(rows, columns), bufferFactory) { index -> initializer(index[0], index[1]) }
return StructureMatrix(structure)
}
override fun point(size: Int, initializer: (Int) -> T): Point<T> = bufferFactory(size, initializer)
}
class StructureMatrix<T : Any>(
val structure: NDStructure<out T>,
override val features: Set<MatrixFeature> = emptySet()
) : Matrix<T> {
init {
if (structure.shape.size != 2) {
error("Dimension mismatch for matrix structure")
}
}
override val rowNum: Int
get() = structure.shape[0]
override val colNum: Int
get() = structure.shape[1]
override val shape: IntArray get() = structure.shape
override fun get(index: IntArray): T = structure[index]
override fun get(i: Int, j: Int): T = structure[i, j]
override fun elements(): Sequence<Pair<IntArray, T>> = structure.elements()
override fun equals(other: Any?): Boolean {
if (this === other) return true
return when (other) {
is NDStructure<*> -> return NDStructure.equals(this, other)
else -> false
}
}
override fun hashCode(): Int {
var result = structure.hashCode()
result = 31 * result + features.hashCode()
return result
}
override fun toString(): String {
return if (rowNum <= 5 && colNum <= 5) {
"Matrix(rowsNum = $rowNum, colNum = $colNum, features=$features)\n" +
rows.asSequence().joinToString(prefix = "(", postfix = ")", separator = "\n ") {
it.asSequence().joinToString(separator = "\t") { it.toString() }
}
} else {
"Matrix(rowsNum = $rowNum, colNum = $colNum, features=$features)"
}
}
}

View File

@ -97,7 +97,7 @@ interface MutableBuffer<T> : Buffer<T> {
} }
inline class ListBuffer<T>(private val list: List<T>) : Buffer<T> { inline class ListBuffer<T>(val list: List<T>) : Buffer<T> {
override val size: Int override val size: Int
get() = list.size get() = list.size
@ -109,7 +109,7 @@ inline class ListBuffer<T>(private val list: List<T>) : Buffer<T> {
fun <T> List<T>.asBuffer() = ListBuffer(this) fun <T> List<T>.asBuffer() = ListBuffer(this)
inline class MutableListBuffer<T>(private val list: MutableList<T>) : MutableBuffer<T> { inline class MutableListBuffer<T>(val list: MutableList<T>) : MutableBuffer<T> {
override val size: Int override val size: Int
get() = list.size get() = list.size
@ -142,7 +142,7 @@ class ArrayBuffer<T>(private val array: Array<T>) : MutableBuffer<T> {
fun <T> Array<T>.asBuffer() = ArrayBuffer(this) fun <T> Array<T>.asBuffer() = ArrayBuffer(this)
inline class DoubleBuffer(private val array: DoubleArray) : MutableBuffer<Double> { inline class DoubleBuffer(val array: DoubleArray) : MutableBuffer<Double> {
override val size: Int get() = array.size override val size: Int get() = array.size
override fun get(index: Int): Double = array[index] override fun get(index: Int): Double = array[index]
@ -157,9 +157,19 @@ inline class DoubleBuffer(private val array: DoubleArray) : MutableBuffer<Double
} }
/**
* Transform buffer of doubles into array for high performance operations
*/
val Buffer<out Double>.array: DoubleArray
get() = if (this is DoubleBuffer) {
array
} else {
DoubleArray(size) { get(it) }
}
fun DoubleArray.asBuffer() = DoubleBuffer(this) fun DoubleArray.asBuffer() = DoubleBuffer(this)
inline class ShortBuffer(private val array: ShortArray) : MutableBuffer<Short> { inline class ShortBuffer(val array: ShortArray) : MutableBuffer<Short> {
override val size: Int get() = array.size override val size: Int get() = array.size
override fun get(index: Int): Short = array[index] override fun get(index: Int): Short = array[index]
@ -176,7 +186,7 @@ inline class ShortBuffer(private val array: ShortArray) : MutableBuffer<Short> {
fun ShortArray.asBuffer() = ShortBuffer(this) fun ShortArray.asBuffer() = ShortBuffer(this)
inline class IntBuffer(private val array: IntArray) : MutableBuffer<Int> { inline class IntBuffer(val array: IntArray) : MutableBuffer<Int> {
override val size: Int get() = array.size override val size: Int get() = array.size
override fun get(index: Int): Int = array[index] override fun get(index: Int): Int = array[index]
@ -193,7 +203,7 @@ inline class IntBuffer(private val array: IntArray) : MutableBuffer<Int> {
fun IntArray.asBuffer() = IntBuffer(this) fun IntArray.asBuffer() = IntBuffer(this)
inline class LongBuffer(private val array: LongArray) : MutableBuffer<Long> { inline class LongBuffer(val array: LongArray) : MutableBuffer<Long> {
override val size: Int get() = array.size override val size: Int get() = array.size
override fun get(index: Int): Long = array[index] override fun get(index: Int): Long = array[index]
@ -210,7 +220,7 @@ inline class LongBuffer(private val array: LongArray) : MutableBuffer<Long> {
fun LongArray.asBuffer() = LongBuffer(this) fun LongArray.asBuffer() = LongBuffer(this)
inline class ReadOnlyBuffer<T>(private val buffer: MutableBuffer<T>) : Buffer<T> { inline class ReadOnlyBuffer<T>(val buffer: MutableBuffer<T>) : Buffer<T> {
override val size: Int get() = buffer.size override val size: Int get() = buffer.size
override fun get(index: Int): T = buffer.get(index) override fun get(index: Int): T = buffer.get(index)

View File

@ -24,7 +24,7 @@ class MatrixTest {
fun testTranspose() { fun testTranspose() {
val matrix = MatrixContext.real.one(3, 3) val matrix = MatrixContext.real.one(3, 3)
val transposed = matrix.transpose() val transposed = matrix.transpose()
assertEquals((matrix as StructureMatrix).structure, (transposed as StructureMatrix).structure) assertEquals((matrix as BufferMatrix).buffer, (transposed as BufferMatrix).buffer)
assertEquals(matrix, transposed) assertEquals(matrix, transposed)
} }

View File

@ -0,0 +1,53 @@
plugins {
id("kotlin-multiplatform")
}
repositories {
maven("http://dl.bintray.com/kyonifer/maven")
}
kotlin {
jvm {
compilations["main"].kotlinOptions.jvmTarget = "1.8"
}
js()
sourceSets {
val commonMain by getting {
dependencies {
api(project(":kmath-core"))
implementation("com.kyonifer:koma-core-api-common:0.12")
implementation("org.jetbrains.kotlin:kotlin-stdlib-common")
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
}
val jvmMain by getting {
dependencies {
implementation(kotlin("stdlib-jdk8"))
}
}
val jvmTest by getting {
dependencies {
implementation(kotlin("test"))
implementation(kotlin("test-junit"))
implementation("com.kyonifer:koma-core-ejml:0.12")
}
}
val jsMain by getting {
dependencies {
implementation(kotlin("stdlib-js"))
}
}
val jsTest by getting {
dependencies {
implementation(kotlin("test-js"))
}
}
}
}

View File

@ -0,0 +1,27 @@
package scientifik.kmath.linear
import scientifik.kmath.operations.Ring
class KomaMatrixContext<T: Any, R: Ring<T>> : MatrixContext<T,R> {
override val elementContext: R
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T): Matrix<T> {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun point(size: Int, initializer: (Int) -> T): Point<T> {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
inline class KomaMatrix<T : Any>(val matrix: koma.matrix.Matrix<T>) : Matrix<T> {
override val rowNum: Int get() = matrix.numRows()
override val colNum: Int get() = matrix.numCols()
override val features: Set<MatrixFeature> get() = emptySet()
@Suppress("OVERRIDE_BY_INLINE")
override inline fun get(i: Int, j: Int): T = matrix.getGeneric(i, j)
}

View File

@ -2,6 +2,8 @@ pluginManagement {
repositories { repositories {
mavenCentral() mavenCentral()
maven("https://plugins.gradle.org/m2/") maven("https://plugins.gradle.org/m2/")
maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") }
maven { setUrl("https://plugins.gradle.org/m2/") }
} }
} }
@ -13,5 +15,6 @@ include(
":kmath-io", ":kmath-io",
":kmath-coroutines", ":kmath-coroutines",
":kmath-commons", ":kmath-commons",
":kmath-koma",
":benchmarks" ":benchmarks"
) )