From c3989159ab99002750d4eecea0fad0777859add8 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 12 Feb 2019 11:58:58 +0300 Subject: [PATCH] Complex buffer optimization --- benchmarks/build.gradle | 6 +- .../kmath/structures/ComplexNDBenchmark.kt | 36 +++++ .../kmath/structures/NDFieldBenchmark.kt | 2 +- build.gradle.kts | 6 +- kmath-core/build.gradle.kts | 4 +- .../scientifik/kmath/operations/Complex.kt | 29 ++-- .../kmath/structures/BoxingNDField.kt | 2 +- .../kmath/structures/BufferedNDAlgebra.kt | 2 - .../kmath/structures/RealBufferField.kt | 55 +++++++- .../kmath/structures/RealNDField.kt | 3 +- .../kmath/structures/ShortNDRing.kt | 3 +- .../scientifik/kmath/structures/BufferSpec.kt | 39 ++---- .../kmath/structures/ComplexBufferSpec.kt | 23 ++-- .../kmath/structures/ComplexNDField.kt | 125 ++++++++++++++++++ .../kmath/structures/ObjectBuffer.kt | 3 + .../kmath/structures/RealBufferSpec.kt | 13 +- 16 files changed, 286 insertions(+), 65 deletions(-) create mode 100644 benchmarks/src/main/kotlin/scientifik/kmath/structures/ComplexNDBenchmark.kt create mode 100644 kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexNDField.kt diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle index 78d902459..c57d84fe4 100644 --- a/benchmarks/build.gradle +++ b/benchmarks/build.gradle @@ -1,6 +1,6 @@ plugins { id "java" - id "me.champeau.gradle.jmh" version "0.4.7" + id "me.champeau.gradle.jmh" version "0.4.8" id 'org.jetbrains.kotlin.jvm' } @@ -15,8 +15,8 @@ dependencies { implementation project(":kmath-coroutines") implementation project(":kmath-commons") implementation project(":kmath-koma") - compile group: "com.kyonifer", name:"koma-core-ejml", version: "0.12" - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" + implementation group: "com.kyonifer", name:"koma-core-ejml", version: "0.12" + //compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8" //jmh project(':kmath-core') } diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/structures/ComplexNDBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/structures/ComplexNDBenchmark.kt new file mode 100644 index 000000000..90bc5a9b4 --- /dev/null +++ b/benchmarks/src/main/kotlin/scientifik/kmath/structures/ComplexNDBenchmark.kt @@ -0,0 +1,36 @@ +package scientifik.kmath.structures + +import scientifik.kmath.operations.Complex +import scientifik.kmath.operations.toComplex +import kotlin.system.measureTimeMillis + +fun main() { + val dim = 1000 + val n = 1000 + + val realField = NDField.real(intArrayOf(dim, dim)) + val complexField = NDField.complex(intArrayOf(dim, dim)) + + + val realTime = measureTimeMillis { + realField.run { + var res: NDBuffer = one + repeat(n) { + res += 1.0 + } + } + } + + println("Real addition completed in $realTime millis") + + val complexTime = measureTimeMillis { + complexField.run { + var res: NDBuffer = one + repeat(n) { + res += 1.0.toComplex() + } + } + } + + println("Complex addition completed in $complexTime millis") +} \ No newline at end of file diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt index a0cbd66ca..1e139e247 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt @@ -39,7 +39,7 @@ fun main(args: Array) { val specializedTime = measureTimeMillis { specializedField.run { - var res:NDBuffer = one + var res: NDBuffer = one repeat(n) { res += 1.0 } diff --git a/build.gradle.kts b/build.gradle.kts index 4d15bfe0d..3c1c669ba 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,8 +1,8 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension buildscript { - val kotlinVersion: String by rootProject.extra("1.3.20") - val ioVersion: String by rootProject.extra("0.1.2") + val kotlinVersion: String by rootProject.extra("1.3.21") + val ioVersion: String by rootProject.extra("0.1.4") val coroutinesVersion: String by rootProject.extra("1.1.1") val atomicfuVersion: String by rootProject.extra("0.12.1") @@ -27,7 +27,7 @@ allprojects { apply(plugin = "com.jfrog.artifactory") group = "scientifik" - version = "0.0.3-dev-5" + version = "0.0.3" repositories { //maven("https://dl.bintray.com/kotlin/kotlin-eap") diff --git a/kmath-core/build.gradle.kts b/kmath-core/build.gradle.kts index 65086d665..f6d10875c 100644 --- a/kmath-core/build.gradle.kts +++ b/kmath-core/build.gradle.kts @@ -2,9 +2,11 @@ plugins { kotlin("multiplatform") } +val ioVersion: String by rootProject.extra + kotlin { - jvm () + jvm() js() sourceSets { diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt index 20e8f47ec..a7d9ef7f4 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt @@ -1,9 +1,11 @@ package scientifik.kmath.operations +import kotlin.math.* + /** * A field for complex numbers */ -object ComplexField : Field { +object ComplexField : ExtendedField { override val zero: Complex = Complex(0.0, 0.0) override val one: Complex = Complex(1.0, 0.0) @@ -22,6 +24,17 @@ object ComplexField : Field { return Complex((a.re * b.re + a.im * b.im) / norm, (a.re * b.im - a.im * b.re) / norm) } + override fun sin(arg: Complex): Complex = i / 2 * (exp(-i * arg) - exp(i * arg)) + + override fun cos(arg: Complex): Complex = (exp(-i * arg) + exp(i * arg)) / 2 + + override fun power(arg: Complex, pow: Number): Complex = + arg.abs.pow(pow.toDouble()) * (cos(pow.toDouble() * arg.theta) + i * sin(pow.toDouble() * arg.theta)) + + override fun exp(arg: Complex): Complex = exp(arg.re) * (cos(arg.im) + i * sin(arg.im)) + + override fun ln(arg: Complex): Complex = ln(arg.abs) + i * atan2(arg.im, arg.re) + operator fun Double.plus(c: Complex) = add(this.toComplex(), c) operator fun Double.minus(c: Complex) = add(this.toComplex(), -c) @@ -41,20 +54,18 @@ data class Complex(val re: Double, val im: Double) : FieldElement>( override val strides: Strides = DefaultStrides(shape) - override fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer = + fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer = bufferFactory(size, initializer) override fun check(vararg elements: NDBuffer) { diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferedNDAlgebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferedNDAlgebra.kt index 03b5407f5..9742f3662 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferedNDAlgebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferedNDAlgebra.kt @@ -5,8 +5,6 @@ import scientifik.kmath.operations.* interface BufferedNDAlgebra: NDAlgebra>{ val strides: Strides - fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer - override fun check(vararg elements: NDBuffer) { if (!elements.all { it.strides == this.strides }) error("Strides mismatch") } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealBufferField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealBufferField.kt index f1df3a0f4..42e74a27f 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealBufferField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealBufferField.kt @@ -1,11 +1,12 @@ package scientifik.kmath.structures -import scientifik.kmath.operations.Field +import scientifik.kmath.operations.ExtendedField +import kotlin.math.* /** * A simple field over linear buffers of [Double] */ -class RealBufferField(val size: Int) : Field> { +class RealBufferField(val size: Int) : ExtendedField> { override val zero: Buffer = Buffer.DoubleBufferFactory(size) { 0.0 } override val one: Buffer = Buffer.DoubleBufferFactory(size) { 1.0 } @@ -56,4 +57,54 @@ class RealBufferField(val size: Int) : Field> { DoubleBuffer(DoubleArray(size) { a[it] / b[it] }) } } + + override fun sin(arg: Buffer): Buffer { + require(arg.size == size) { "The size of buffer is ${arg.size} but context requires $size " } + return if (arg is DoubleBuffer) { + val array = arg.array + DoubleBuffer(DoubleArray(size) { sin(array[it]) }) + } else { + DoubleBuffer(DoubleArray(size) { sin(arg[it]) }) + } + } + + override fun cos(arg: Buffer): Buffer { + require(arg.size == size) { "The size of buffer is ${arg.size} but context requires $size " } + return if (arg is DoubleBuffer) { + val array = arg.array + DoubleBuffer(DoubleArray(size) { cos(array[it]) }) + } else { + DoubleBuffer(DoubleArray(size) { cos(arg[it]) }) + } + } + + override fun power(arg: Buffer, pow: Number): Buffer { + require(arg.size == size) { "The size of buffer is ${arg.size} but context requires $size " } + return if (arg is DoubleBuffer) { + val array = arg.array + DoubleBuffer(DoubleArray(size) { array[it].pow(pow.toDouble()) }) + } else { + DoubleBuffer(DoubleArray(size) { arg[it].pow(pow.toDouble()) }) + } + } + + override fun exp(arg: Buffer): Buffer { + require(arg.size == size) { "The size of buffer is ${arg.size} but context requires $size " } + return if (arg is DoubleBuffer) { + val array = arg.array + DoubleBuffer(DoubleArray(size) { exp(array[it]) }) + } else { + DoubleBuffer(DoubleArray(size) { exp(arg[it]) }) + } + } + + override fun ln(arg: Buffer): Buffer { + require(arg.size == size) { "The size of buffer is ${arg.size} but context requires $size " } + return if (arg is DoubleBuffer) { + val array = arg.array + DoubleBuffer(DoubleArray(size) { ln(array[it]) }) + } else { + DoubleBuffer(DoubleArray(size) { ln(arg[it]) }) + } + } } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt index d652bb8a8..82a237817 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt @@ -15,8 +15,7 @@ class RealNDField(override val shape: IntArray) : override val zero by lazy { produce { zero } } override val one by lazy { produce { one } } - @Suppress("OVERRIDE_BY_INLINE") - override inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Double): Buffer = + inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Double): Buffer = DoubleBuffer(DoubleArray(size) { initializer(it) }) /** diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortNDRing.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortNDRing.kt index 09e93483d..6b09c91de 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortNDRing.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortNDRing.kt @@ -15,8 +15,7 @@ class ShortNDRing(override val shape: IntArray) : override val zero by lazy { produce { ShortRing.zero } } override val one by lazy { produce { ShortRing.one } } - @Suppress("OVERRIDE_BY_INLINE") - override inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Short): Buffer = + inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Short): Buffer = ShortBuffer(ShortArray(size) { initializer(it) }) /** diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/BufferSpec.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/BufferSpec.kt index 9f95360fd..65a69cbc2 100644 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/BufferSpec.kt +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/BufferSpec.kt @@ -7,8 +7,15 @@ import java.nio.ByteBuffer * A specification for serialization and deserialization objects to buffer */ interface BufferSpec { - fun fromBuffer(buffer: ByteBuffer): T - fun toBuffer(value: T): ByteBuffer + /** + * Read an object from buffer in current position + */ + fun ByteBuffer.readObject(): T + + /** + * Write object to [ByteBuffer] in current buffer position + */ + fun ByteBuffer.writeObject(value: T) } /** @@ -17,39 +24,21 @@ interface BufferSpec { interface FixedSizeBufferSpec : BufferSpec { val unitSize: Int - /** - * Read an object from buffer in current position - */ - fun ByteBuffer.readObject(): T { - val buffer = ByteArray(unitSize) - get(buffer) - return fromBuffer(ByteBuffer.wrap(buffer)) - } /** * Read an object from buffer in given index (not buffer position */ fun ByteBuffer.readObject(index: Int): T { - val dup = duplicate() - dup.position(index * unitSize) - return dup.readObject() + position(index * unitSize) + return readObject() } - /** - * Write object to [ByteBuffer] in current buffer position - */ - fun ByteBuffer.writeObject(obj: T) { - val buffer = toBuffer(obj).apply { rewind() } - assert(buffer.limit() == unitSize) - put(buffer) - } /** * Put an object in given index */ fun ByteBuffer.writeObject(index: Int, obj: T) { - val dup = duplicate() - dup.position(index * unitSize) - dup.writeObject(obj) + position(index * unitSize) + writeObject(obj) } -} \ No newline at end of file +} diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt index 635a90dbc..f8c93e754 100644 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt @@ -4,16 +4,20 @@ import scientifik.kmath.operations.Complex import scientifik.kmath.operations.ComplexField import java.nio.ByteBuffer +/** + * A serialization specification for complex numbers + */ object ComplexBufferSpec : FixedSizeBufferSpec { + override val unitSize: Int = 16 - override fun fromBuffer(buffer: ByteBuffer): Complex { - val re = buffer.getDouble(0) - val im = buffer.getDouble(8) + override fun ByteBuffer.readObject(): Complex { + val re = double + val im = double return Complex(re, im) } - override fun toBuffer(value: Complex): ByteBuffer = ByteBuffer.allocate(16).apply { + override fun ByteBuffer.writeObject(value: Complex) { putDouble(value.re) putDouble(value.im) } @@ -22,14 +26,13 @@ object ComplexBufferSpec : FixedSizeBufferSpec { /** * Create a read-only/mutable buffer which ignores boxing */ -fun Buffer.Companion.complex(size: Int): Buffer = - ObjectBuffer.create(ComplexBufferSpec, size) +fun Buffer.Companion.complex(size: Int, initializer: ((Int) -> Complex)? = null): Buffer = + ObjectBuffer.create(ComplexBufferSpec, size, initializer) -fun MutableBuffer.Companion.complex(size: Int) = - ObjectBuffer.create(ComplexBufferSpec, size) +fun MutableBuffer.Companion.complex(size: Int, initializer: ((Int) -> Complex)? = null) = + ObjectBuffer.create(ComplexBufferSpec, size, initializer) -fun NDField.Companion.complex(shape: IntArray) = - BoxingNDField(shape, ComplexField) { size, init -> ObjectBuffer.create(ComplexBufferSpec, size, init) } +fun NDField.Companion.complex(shape: IntArray) = ComplexNDField(shape) fun NDElement.Companion.complex(shape: IntArray, initializer: ComplexField.(IntArray) -> Complex) = NDField.complex(shape).produce(initializer) diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexNDField.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexNDField.kt new file mode 100644 index 000000000..190d8a46b --- /dev/null +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexNDField.kt @@ -0,0 +1,125 @@ +package scientifik.kmath.structures + +import scientifik.kmath.operations.Complex +import scientifik.kmath.operations.ComplexField +import scientifik.kmath.operations.FieldElement + +typealias ComplexNDElement = BufferedNDFieldElement + +/** + * An optimized nd-field for complex numbers + */ +class ComplexNDField(override val shape: IntArray) : + BufferedNDField, + ExtendedNDField> { + + override val strides: Strides = DefaultStrides(shape) + + override val elementContext: ComplexField get() = ComplexField + override val zero by lazy { produce { zero } } + override val one by lazy { produce { one } } + + inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Complex): Buffer = + Buffer.complex(size) { initializer(it) } + + /** + * Inline transform an NDStructure to another structure + */ + override fun map( + arg: NDBuffer, + transform: ComplexField.(Complex) -> Complex + ): ComplexNDElement { + check(arg) + val array = buildBuffer(arg.strides.linearSize) { offset -> ComplexField.transform(arg.buffer[offset]) } + return BufferedNDFieldElement(this, array) + } + + override fun produce(initializer: ComplexField.(IntArray) -> Complex): ComplexNDElement { + val array = buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) } + return BufferedNDFieldElement(this, array) + } + + override fun mapIndexed( + arg: NDBuffer, + transform: ComplexField.(index: IntArray, Complex) -> Complex + ): ComplexNDElement { + check(arg) + return BufferedNDFieldElement( + this, + buildBuffer(arg.strides.linearSize) { offset -> + elementContext.transform( + arg.strides.index(offset), + arg.buffer[offset] + ) + }) + } + + override fun combine( + a: NDBuffer, + b: NDBuffer, + transform: ComplexField.(Complex, Complex) -> Complex + ): ComplexNDElement { + check(a, b) + return BufferedNDFieldElement( + this, + buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) }) + } + + override fun NDBuffer.toElement(): FieldElement, *, out BufferedNDField> = + BufferedNDFieldElement(this@ComplexNDField, buffer) + + override fun power(arg: NDBuffer, pow: Number) = map(arg) { power(it, pow) } + + override fun exp(arg: NDBuffer) = map(arg) { exp(it) } + + override fun ln(arg: NDBuffer) = map(arg) { ln(it) } + + override fun sin(arg: NDBuffer) = map(arg) { sin(it) } + + override fun cos(arg: NDBuffer) = map(arg) { cos(it) } + +} + + +/** + * Fast element production using function inlining + */ +inline fun BufferedNDField.produceInline(crossinline initializer: ComplexField.(Int) -> Complex): ComplexNDElement { + val buffer = Buffer.complex(strides.linearSize) { offset -> ComplexField.initializer(offset) } + return BufferedNDFieldElement(this, buffer) +} + +/** + * Map one [ComplexNDElement] using function with indexes + */ +inline fun ComplexNDElement.mapIndexed(crossinline transform: ComplexField.(index: IntArray, Complex) -> Complex) = + context.produceInline { offset -> transform(strides.index(offset), buffer[offset]) } + +/** + * Map one [ComplexNDElement] using function without indexes + */ +inline fun ComplexNDElement.map(crossinline transform: ComplexField.(Complex) -> Complex): ComplexNDElement { + val buffer = Buffer.complex(strides.linearSize) { offset -> ComplexField.transform(buffer[offset]) } + return BufferedNDFieldElement(context, buffer) +} + +/** + * Element by element application of any operation on elements to the whole array. Just like in numpy + */ +operator fun Function1.invoke(ndElement: ComplexNDElement) = + ndElement.map { this@invoke(it) } + + +/* plus and minus */ + +/** + * Summation operation for [BufferedNDElement] and single element + */ +operator fun ComplexNDElement.plus(arg: Complex) = + map { it + arg } + +/** + * Subtraction operation between [BufferedNDElement] and single element + */ +operator fun ComplexNDElement.minus(arg: Complex) = + map { it - arg } \ No newline at end of file diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt index 8d7bece0c..422ed64ad 100644 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt @@ -2,6 +2,9 @@ package scientifik.kmath.structures import java.nio.ByteBuffer +/** + * A non-boxing buffer based on [ByteBuffer] storage + */ class ObjectBuffer(private val buffer: ByteBuffer, private val spec: FixedSizeBufferSpec) : MutableBuffer { override val size: Int diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/RealBufferSpec.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/RealBufferSpec.kt index 761cfc2db..fdd9e92b7 100644 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/RealBufferSpec.kt +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/RealBufferSpec.kt @@ -6,17 +6,22 @@ import java.nio.ByteBuffer object RealBufferSpec : FixedSizeBufferSpec { override val unitSize: Int = 8 - override fun fromBuffer(buffer: ByteBuffer): Real = Real(buffer.double) + override fun ByteBuffer.readObject(): Real = Real(double) - override fun toBuffer(value: Real): ByteBuffer = ByteBuffer.allocate(8).apply { putDouble(value.value) } + override fun ByteBuffer.writeObject(value: Real) { + putDouble(value.value) + } } object DoubleBufferSpec : FixedSizeBufferSpec { override val unitSize: Int = 8 - override fun fromBuffer(buffer: ByteBuffer): Double = buffer.double + override fun ByteBuffer.readObject() = double + + override fun ByteBuffer.writeObject(value: Double) { + putDouble(value) + } - override fun toBuffer(value: Double): ByteBuffer = ByteBuffer.allocate(8).apply { putDouble(value) } } fun Double.Companion.createBuffer(size: Int) = ObjectBuffer.create(DoubleBufferSpec, size)