Optimize Real NDField

This commit is contained in:
Alexander Nozik 2021-01-23 22:16:27 +03:00
parent 0baec14059
commit 1cb41f4dc2
7 changed files with 77 additions and 92 deletions

View File

@ -4,8 +4,8 @@ import kotlinx.benchmark.Benchmark
import kscience.kmath.commons.linear.CMMatrixContext
import kscience.kmath.ejml.EjmlMatrixContext
import kscience.kmath.linear.BufferMatrixContext
import kscience.kmath.linear.Matrix
import kscience.kmath.linear.RealMatrixContext
import kscience.kmath.nd.Matrix
import kscience.kmath.operations.RealField
import kscience.kmath.operations.invoke
import kscience.kmath.structures.Buffer

View File

@ -1,11 +1,9 @@
package kscience.kmath.benchmarks
import kscience.kmath.nd.AbstractNDBuffer
import kscience.kmath.nd.NDField
import kscience.kmath.nd.RealNDField
import kscience.kmath.nd.*
import kscience.kmath.operations.RealField
import kscience.kmath.operations.invoke
import kscience.kmath.structures.*
import kscience.kmath.structures.Buffer
import org.openjdk.jmh.annotations.Benchmark
import org.openjdk.jmh.annotations.Scope
import org.openjdk.jmh.annotations.State
@ -14,22 +12,16 @@ import org.openjdk.jmh.annotations.State
internal class NDFieldBenchmark {
@Benchmark
fun autoFieldAdd() {
bufferedField {
var res: AbstractNDBuffer<Double> = one
autoField {
var res: NDStructure<Double> = one
repeat(n) { res += one }
}
}
@Benchmark
fun autoElementAdd() {
var res = genericField.one
repeat(n) { res += 1.0 }
}
@Benchmark
fun specializedFieldAdd() {
specializedField {
var res: AbstractNDBuffer<Double> = one
var res: NDStructure<Double> = one
repeat(n) { res += 1.0 }
}
}
@ -38,16 +30,16 @@ internal class NDFieldBenchmark {
@Benchmark
fun boxingFieldAdd() {
genericField {
var res: AbstractNDBuffer<Double> = one
repeat(n) { res += one }
var res: NDStructure<Double> = one
repeat(n) { res += 1.0 }
}
}
companion object {
const val dim: Int = 1000
const val n: Int = 100
val bufferedField: BufferedNDField<Double, RealField> = NDField.auto(RealField, dim, dim)
val specializedField: RealNDField = NDField.real(dim, dim)
val genericField: BoxingNDField<Double, RealField> = NDField.boxing(RealField, dim, dim)
val autoField = NDAlgebra.auto(RealField, dim, dim)
val specializedField: RealNDField = NDAlgebra.real(dim, dim)
val genericField = NDAlgebra.field(RealField, Buffer.Companion::boxing, dim, dim)
}
}

View File

@ -1,10 +1,8 @@
package kscience.kmath.benchmarks
import kscience.kmath.nd.NDField
import kscience.kmath.nd.RealNDField
import kscience.kmath.nd.*
import kscience.kmath.operations.RealField
import kscience.kmath.operations.invoke
import kscience.kmath.structures.BufferedNDField
import kscience.kmath.viktor.ViktorNDField
import org.jetbrains.bio.viktor.F64Array
import org.openjdk.jmh.annotations.Benchmark
@ -17,14 +15,14 @@ internal class ViktorBenchmark {
final val n: Int = 100
// automatically build context most suited for given type.
final val autoField: BufferedNDField<Double, RealField> = NDField.auto(RealField, dim, dim)
final val realField: RealNDField = NDField.real(dim, dim)
final val autoField: BufferedNDField<Double, RealField> = NDAlgebra.auto(RealField, dim, dim)
final val realField: RealNDField = NDAlgebra.real(dim, dim)
final val viktorField: ViktorNDField = ViktorNDField(intArrayOf(dim, dim))
@Benchmark
fun automaticFieldAddition() {
autoField {
var res = one
var res: NDStructure<Double> = one
repeat(n) { res += one }
}
}

View File

@ -1,10 +1,7 @@
package kscience.kmath.structures
import kotlinx.coroutines.GlobalScope
import kscience.kmath.nd.NDAlgebra
import kscience.kmath.nd.NDStructure
import kscience.kmath.nd.field
import kscience.kmath.nd.real
import kscience.kmath.nd.*
import kscience.kmath.nd4j.Nd4jArrayField
import kscience.kmath.operations.RealField
import kscience.kmath.operations.invoke
@ -26,11 +23,11 @@ fun main() {
val n = 1000
// automatically build context most suited for given type.
val autoField = NDAlgebra.field(RealField, Buffer.Companion::auto, dim, dim)
val autoField = NDAlgebra.auto(RealField, dim, dim)
// specialized nd-field for Double. It works as generic Double field as well
val specializedField = NDAlgebra.real(dim, dim)
//A generic boxing field. It should be used for objects, not primitives.
val boxingField = NDAlgebra.field(RealField, Buffer.Companion::boxing, dim, dim)
val boxingField = NDAlgebra.field(RealField, Buffer.Companion::boxing, dim, dim)
// Nd4j specialized field.
val nd4jField = Nd4jArrayField.real(dim, dim)
@ -41,9 +38,9 @@ fun main() {
}
}
measureAndPrint("Element addition") {
measureAndPrint("Boxing addition") {
boxingField {
var res: NDStructure<Double> = one
var res: NDStructure<Double> = one
repeat(n) { res += 1.0 }
}
}
@ -57,7 +54,7 @@ fun main() {
measureAndPrint("Nd4j specialized addition") {
nd4jField {
var res:NDStructure<Double> = one
var res: NDStructure<Double> = one
repeat(n) { res += 1.0 }
}
}
@ -73,14 +70,4 @@ fun main() {
res.elements().forEach { it.second }
}
measureAndPrint("Generic addition") {
//genericField.run(action)
boxingField {
var res: NDStructure<Double> = one
repeat(n) {
res += 1.0 // couldn't avoid using `one` due to resolution ambiguity }
}
}
}
}

View File

@ -2,6 +2,7 @@ package kscience.kmath.nd
import kscience.kmath.nd.*
import kscience.kmath.operations.*
import kscience.kmath.structures.Buffer
import kscience.kmath.structures.BufferFactory
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
@ -118,6 +119,11 @@ public fun <T, A : Field<T>> NDAlgebra.Companion.field(
vararg shape: Int,
): BufferedNDField<T, A> = BufferedNDField(shape, field, bufferFactory)
public inline fun <reified T : Any, A : Field<T>> NDAlgebra.Companion.auto(
field: A,
vararg shape: Int,
): BufferedNDField<T, A> = BufferedNDField(shape, field, Buffer.Companion::auto)
public inline fun <T, A : Field<T>, R> A.ndField(
noinline bufferFactory: BufferFactory<T>,
vararg shape: Int,

View File

@ -10,7 +10,7 @@ import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
@OptIn(UnstableKMathAPI::class)
public open class RealNDField(
public class RealNDField(
shape: IntArray,
) : BufferedNDField<Double, RealField>(shape, RealField, Buffer.Companion::real),
RingWithNumbers<NDStructure<Double>>,
@ -23,51 +23,52 @@ public open class RealNDField(
val d = value.toDouble() // minimize conversions
return produce { d }
}
//
// @Suppress("OVERRIDE_BY_INLINE")
// override inline fun map(
// arg: AbstractNDBuffer<Double>,
// transform: RealField.(Double) -> Double,
// ): RealNDElement {
// check(arg)
// val array = RealBuffer(arg.strides.linearSize) { offset -> RealField.transform(arg.buffer[offset]) }
// return BufferedNDFieldElement(this, array)
// }
//
// @Suppress("OVERRIDE_BY_INLINE")
// override inline fun produce(initializer: RealField.(IntArray) -> Double): RealNDElement {
// val array = RealBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) }
// return BufferedNDFieldElement(this, array)
// }
//
// @Suppress("OVERRIDE_BY_INLINE")
// override inline fun mapIndexed(
// arg: AbstractNDBuffer<Double>,
// transform: RealField.(index: IntArray, Double) -> Double,
// ): RealNDElement {
// check(arg)
// return BufferedNDFieldElement(
// this,
// RealBuffer(arg.strides.linearSize) { offset ->
// elementContext.transform(
// arg.strides.index(offset),
// arg.buffer[offset]
// )
// })
// }
//
// @Suppress("OVERRIDE_BY_INLINE")
// override inline fun combine(
// a: AbstractNDBuffer<Double>,
// b: AbstractNDBuffer<Double>,
// transform: RealField.(Double, Double) -> Double,
// ): RealNDElement {
// check(a, b)
// val buffer = RealBuffer(strides.linearSize) { offset ->
// elementContext.transform(a.buffer[offset], b.buffer[offset])
// }
// return BufferedNDFieldElement(this, buffer)
// }
@Suppress("OVERRIDE_BY_INLINE")
override inline fun map(
arg: NDStructure<Double>,
transform: RealField.(Double) -> Double,
): NDBuffer<Double> {
val argAsBuffer = arg.ndBuffer
val buffer = RealBuffer(strides.linearSize) { offset -> RealField.transform(argAsBuffer.buffer[offset]) }
return NDBuffer(strides, buffer)
}
@Suppress("OVERRIDE_BY_INLINE")
override inline fun produce(initializer: RealField.(IntArray) -> Double): NDBuffer<Double> {
val buffer = RealBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) }
return NDBuffer(strides, buffer)
}
@Suppress("OVERRIDE_BY_INLINE")
override inline fun mapIndexed(
arg: NDStructure<Double>,
transform: RealField.(index: IntArray, Double) -> Double,
): NDBuffer<Double> {
val argAsBuffer = arg.ndBuffer
return NDBuffer(
strides,
RealBuffer(strides.linearSize) { offset ->
elementContext.transform(
strides.index(offset),
argAsBuffer.buffer[offset]
)
})
}
@Suppress("OVERRIDE_BY_INLINE")
override inline fun combine(
a: NDStructure<Double>,
b: NDStructure<Double>,
transform: RealField.(Double, Double) -> Double,
): NDBuffer<Double> {
val aBuffer = a.ndBuffer
val bBuffer = b.ndBuffer
val buffer = RealBuffer(strides.linearSize) { offset ->
elementContext.transform(aBuffer.buffer[offset], bBuffer.buffer[offset])
}
return NDBuffer(strides, buffer)
}
override fun power(arg: NDStructure<Double>, pow: Number): NDBuffer<Double> = map(arg) { power(it, pow) }

View File

@ -1,5 +1,6 @@
package kscience.kmath.nd
import kscience.kmath.linear.Matrix
import kscience.kmath.structures.Buffer
import kscience.kmath.structures.VirtualBuffer
@ -57,9 +58,9 @@ public interface Structure2D<T> : NDStructure<T> {
rows: Int,
columns: Int,
crossinline init: (i: Int, j: Int) -> Double,
): NDBuffer<Double> = NDAlgebra.real(rows, columns).produceInline { (i, j) ->
): Matrix<Double> = NDAlgebra.real(rows, columns).produceInline { (i, j) ->
init(i, j)
}
}.as2D()
}
}