Skip to content

Commit

Permalink
Optimize FeedBuffer.update (#164)
Browse files Browse the repository at this point in the history
  • Loading branch information
05nelsonm authored Jan 20, 2025
1 parent 37ea3c4 commit 69317fc
Show file tree
Hide file tree
Showing 9 changed files with 57 additions and 70 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ jobs:
uses: actions/upload-artifact@v4
if: ${{ always() }}
with:
name: test-report-${{ matrix.os }}-java${{ matrix.java-version }}
name: test-report-${{ matrix.os }}
path: '**/build/reports/tests/**'
retention-days: 1

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
**/
package io.matthewnelson.encoding.benchmarks

const val ENC_ITERATIONS_WARMUP = 5
const val ENC_ITERATIONS_MEASURE = 5
const val ENC_TIME_WARMUP = 2
const val ENC_TIME_MEASURE = 3
const val ITERATIONS_WARMUP = 5
const val ITERATIONS_MEASURE = 5
const val TIME_WARMUP = 2
const val TIME_MEASURE = 3
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
@file:Suppress("unused")

package io.matthewnelson.encoding.benchmarks

import io.matthewnelson.encoding.base16.Base16
Expand Down Expand Up @@ -56,8 +58,8 @@ abstract class EncoderDecoderBenchmarkBase {
@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(BenchmarkTimeUnit.NANOSECONDS)
@Warmup(iterations = ENC_ITERATIONS_WARMUP, time = ENC_TIME_WARMUP)
@Measurement(iterations = ENC_ITERATIONS_MEASURE, time = ENC_TIME_MEASURE)
@Warmup(iterations = ITERATIONS_WARMUP, time = TIME_WARMUP)
@Measurement(iterations = ITERATIONS_MEASURE, time = TIME_MEASURE)
open class Base16Benchmark: EncoderDecoderBenchmarkBase() {
// CHARS: 0123456789ABCDEF
@Param("3:0", "d:122")
Expand All @@ -68,8 +70,8 @@ open class Base16Benchmark: EncoderDecoderBenchmarkBase() {
@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(BenchmarkTimeUnit.NANOSECONDS)
@Warmup(iterations = ENC_ITERATIONS_WARMUP, time = ENC_TIME_WARMUP)
@Measurement(iterations = ENC_ITERATIONS_MEASURE, time = ENC_TIME_MEASURE)
@Warmup(iterations = ITERATIONS_WARMUP, time = TIME_WARMUP)
@Measurement(iterations = ITERATIONS_MEASURE, time = TIME_MEASURE)
open class Base32CrockfordBenchmark: EncoderDecoderBenchmarkBase() {
// CHARS: 0123456789ABCDEFGHJKMNPQRSTVWXYZ
@Param("3:-6", "x:115")
Expand All @@ -80,8 +82,8 @@ open class Base32CrockfordBenchmark: EncoderDecoderBenchmarkBase() {
@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(BenchmarkTimeUnit.NANOSECONDS)
@Warmup(iterations = ENC_ITERATIONS_WARMUP, time = ENC_TIME_WARMUP)
@Measurement(iterations = ENC_ITERATIONS_MEASURE, time = ENC_TIME_MEASURE)
@Warmup(iterations = ITERATIONS_WARMUP, time = TIME_WARMUP)
@Measurement(iterations = ITERATIONS_MEASURE, time = TIME_MEASURE)
open class Base32DefaultBenchmark: EncoderDecoderBenchmarkBase() {
// CHARS: ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
@Param("C:-123", "w:15")
Expand All @@ -92,8 +94,8 @@ open class Base32DefaultBenchmark: EncoderDecoderBenchmarkBase() {
@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(BenchmarkTimeUnit.NANOSECONDS)
@Warmup(iterations = ENC_ITERATIONS_WARMUP, time = ENC_TIME_WARMUP)
@Measurement(iterations = ENC_ITERATIONS_MEASURE, time = ENC_TIME_MEASURE)
@Warmup(iterations = ITERATIONS_WARMUP, time = TIME_WARMUP)
@Measurement(iterations = ITERATIONS_MEASURE, time = TIME_MEASURE)
open class Base32HexBenchmark: EncoderDecoderBenchmarkBase() {
// CHARS: 0123456789ABCDEFGHIJKLMNOPQRSTUV
@Param("A:-12", "r:42")
Expand All @@ -104,8 +106,8 @@ open class Base32HexBenchmark: EncoderDecoderBenchmarkBase() {
@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(BenchmarkTimeUnit.NANOSECONDS)
@Warmup(iterations = ENC_ITERATIONS_WARMUP, time = ENC_TIME_WARMUP)
@Measurement(iterations = ENC_ITERATIONS_MEASURE, time = ENC_TIME_MEASURE)
@Warmup(iterations = ITERATIONS_WARMUP, time = TIME_WARMUP)
@Measurement(iterations = ITERATIONS_MEASURE, time = TIME_MEASURE)
open class Base64Benchmark: EncoderDecoderBenchmarkBase() {
// CHARS: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
@Param("2:84", "w:22")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
@file:Suppress("unused")

package io.matthewnelson.encoding.benchmarks

import io.matthewnelson.encoding.core.util.FeedBuffer
import kotlinx.benchmark.*

@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 5, time = 2)
@OutputTimeUnit(BenchmarkTimeUnit.NANOSECONDS)
@Warmup(iterations = ITERATIONS_WARMUP, time = TIME_WARMUP)
@Measurement(iterations = ITERATIONS_MEASURE, time = TIME_MEASURE)
open class FeedBufferBenchmark {

private val buffer = object : FeedBuffer(
Expand All @@ -32,7 +34,5 @@ open class FeedBufferBenchmark {
) {}

@Benchmark
fun update() {
repeat(buffer.blockSize) { buffer.update(it) }
}
fun update() { buffer.update(42) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public sealed class Decoder<C: EncoderDecoder.Config>(public val config: C) {
* @see [EncoderDecoder.Feed.doFinal]
* */
public abstract inner class Feed: EncoderDecoder.Feed<C>(config) {

private var isClosed = false
private var isPaddingSet = false

Expand Down Expand Up @@ -138,11 +139,12 @@ public sealed class Decoder<C: EncoderDecoder.Config>(public val config: C) {

public final override fun close() { isClosed = true }
public final override fun isClosed(): Boolean = isClosed
/** @suppress */
public final override fun toString(): String = "${this@Decoder}.Decoder.Feed@${hashCode()}"

@Throws(EncodingException::class)
protected abstract fun consumeProtected(input: Char)

/** @suppress */
public final override fun toString(): String = "${this@Decoder}.Decoder.Feed@${hashCode()}"
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public sealed class Encoder<C: EncoderDecoder.Config>(config: C): Decoder<C>(con
* @see [EncoderDecoder.Feed.doFinal]
* */
public abstract inner class Feed: EncoderDecoder.Feed<C>(config) {

private var isClosed = false

/**
Expand Down Expand Up @@ -124,11 +125,12 @@ public sealed class Encoder<C: EncoderDecoder.Config>(config: C): Decoder<C>(con

public final override fun close() { isClosed = true }
public final override fun isClosed(): Boolean = isClosed
/** @suppress */
public final override fun toString(): String = "${this@Encoder}.Encoder.Feed@${hashCode()}"

protected abstract fun consumeProtected(input: Byte)
protected abstract override fun doFinalProtected()

/** @suppress */
public final override fun toString(): String = "${this@Encoder}.Encoder.Feed@${hashCode()}"
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,10 @@ public abstract class EncoderDecoder<C: EncoderDecoder.Config>(config: C): Encod
}

if (lineBreakInterval > 0) {
var lineBreakCount: Float = (outSize / lineBreakInterval) - 1F
var lineBreakCount: Double = (outSize / lineBreakInterval) - 1.0

if (lineBreakCount > 0F) {
if (lineBreakCount.rem(1) > 0F) {
if (lineBreakCount > 0.0) {
if (lineBreakCount.rem(1.0) > 0.0) {
lineBreakCount++
}

Expand Down Expand Up @@ -303,16 +303,9 @@ public abstract class EncoderDecoder<C: EncoderDecoder.Config>(config: C): Encod
public val name: String = name.trim()

/** @suppress */
override fun equals(other: Any?): Boolean {
return other is Setting
&& other.name == name
}

override fun equals(other: Any?): Boolean = other is Setting && other.name == name
/** @suppress */
override fun hashCode(): Int {
return 17 * 31 + name.hashCode()
}

override fun hashCode(): Int = 17 * 31 + name.hashCode()
/** @suppress */
override fun toString(): String = "$name: $value"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.math.min

private const val MAX_ENCODE_OUT_SIZE: Long = Int.MAX_VALUE.toLong()

@Suppress("NOTHING_TO_INLINE")
@Throws(EncodingException::class)
@OptIn(ExperimentalContracts::class)
Expand All @@ -37,32 +39,25 @@ internal inline fun <C: Config> Decoder<C>.decode(
}

val size = config.decodeOutMaxSizeOrFail(input)
val ba = ByteArray(size)
val a = ByteArray(size)

var i = 0
try {
newDecoderFeed { decodedByte ->
try {
ba[i++] = decodedByte
} catch (e: IndexOutOfBoundsException) {
// Something is wrong with the encoder's pre-calculation
throw EncodingSizeException("Encoder's pre-calculation of Size[$size] was incorrect", e)
}
}.use { feed ->
action.invoke(feed)
}
newDecoderFeed(out = { b -> a[i++] = b }).use { feed -> action.invoke(feed) }
} catch (t: Throwable) {
ba.fill(0, toIndex = min(ba.size, i))
a.fill(0, toIndex = min(a.size, i))
if (t is IndexOutOfBoundsException && i >= size) {
// Something is wrong with the encoder's pre-calculation
throw EncodingSizeException("Encoder's pre-calculation of Size[$size] was incorrect", t)
}
throw t
}

return if (i == size) {
ba
} else {
val copy = ba.copyOf(i)
ba.fill(0, toIndex = i)
copy
}
if (i == size) return a

val copy = a.copyOf(i)
a.fill(0, i)
return copy
}

/**
Expand All @@ -80,8 +75,8 @@ internal inline fun <T: Any> Encoder<*>.encodeOutSizeOrFail(
}

val outSize = config.encodeOutSize(size.toLong())
if (outSize > Int.MAX_VALUE.toLong()) {
throw Config.outSizeExceedsMaxEncodingSizeException(outSize, Int.MAX_VALUE)
if (outSize > MAX_ENCODE_OUT_SIZE) {
throw Config.outSizeExceedsMaxEncodingSizeException(outSize, MAX_ENCODE_OUT_SIZE)
}

return block.invoke(outSize.toInt())
Expand All @@ -93,10 +88,7 @@ internal inline fun <C: Config> Encoder<C>.encode(
out: Encoder.OutFeed,
) {
if (data.isEmpty()) return

newEncoderFeed(out).use { feed ->
data.forEach { b -> feed.consume(b) }
}
newEncoderFeed(out).use { feed -> data.forEach { b -> feed.consume(b) } }
}

@Suppress("NOTHING_TO_INLINE")
Expand All @@ -105,8 +97,6 @@ internal inline fun EncoderDecoder.Feed<*>.closedException(): EncodingException
}

@Suppress("NOTHING_TO_INLINE", "UnusedReceiverParameter")
internal inline fun Config.calculatedOutputNegativeEncodingSizeException(
outSize: Number
): EncodingSizeException {
internal inline fun Config.calculatedOutputNegativeEncodingSizeException(outSize: Number): EncodingSizeException {
return EncodingSizeException("Calculated output of Size[$outSize] was negative")
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,10 @@ constructor(
* to output data once the [buffer] fills.
* */
public fun update(input: Int) {
buffer[count] = input

if (++count % blockSize == 0) {
flush.invoke(buffer)
count = 0
}
buffer[count++] = input
if (count != blockSize) return
flush.invoke(buffer)
count = 0
}

/**
Expand Down

0 comments on commit 69317fc

Please sign in to comment.