|
| 1 | +/* |
| 2 | + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. |
| 3 | + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
| 4 | + */ |
| 5 | + |
| 6 | +package kotlinx.io.benchmark.android |
| 7 | + |
| 8 | +import kotlinx.io.Buffer |
| 9 | +import androidx.benchmark.junit4.BenchmarkRule |
| 10 | +import androidx.benchmark.junit4.measureRepeated |
| 11 | +import androidx.test.ext.junit.runners.AndroidJUnit4 |
| 12 | +import kotlinx.io.readString |
| 13 | +import kotlinx.io.writeString |
| 14 | +import org.junit.After |
| 15 | +import org.junit.Before |
| 16 | +import org.junit.Rule |
| 17 | +import org.junit.Test |
| 18 | +import org.junit.runner.RunWith |
| 19 | +import org.junit.runners.Parameterized |
| 20 | +import org.junit.runners.Parameterized.Parameters |
| 21 | + |
| 22 | +abstract class BufferRWBenchmarkBase { |
| 23 | + // Buffers are implemented as a list of segments, as soon as a segment is empty |
| 24 | + // it will be unlinked. By reading all previously written data, a segment will be |
| 25 | + // cleared and recycled, and the next time we will try to write data, a new segment |
| 26 | + // will be requested from the pool. Thus, without having any data in-flight, we will |
| 27 | + // benchmark not only read/write ops performance, but also segments allocation/reclamation. |
| 28 | + // Specific non-zero minGap's values don't significantly affect overall results, but it is |
| 29 | + // left as the parameter to allow fine-tuning in the future. |
| 30 | + var minGap: Int = 128 |
| 31 | + |
| 32 | + protected val buffer = Buffer() |
| 33 | + |
| 34 | + protected open fun padding(): ByteArray = ByteArray(minGap) |
| 35 | + |
| 36 | + @get:Rule |
| 37 | + val benchmarkRule = BenchmarkRule() |
| 38 | + |
| 39 | + @Before |
| 40 | + fun setupBuffers() { |
| 41 | + val padding = padding() |
| 42 | + buffer.write(padding) |
| 43 | + } |
| 44 | + |
| 45 | + @After |
| 46 | + fun clearBuffers() { |
| 47 | + buffer.clear() |
| 48 | + } |
| 49 | +} |
| 50 | + |
| 51 | +@RunWith(AndroidJUnit4::class) |
| 52 | +open class IntegerValuesBenchmark : BufferRWBenchmarkBase() { |
| 53 | + @Test |
| 54 | + fun byteRW() { |
| 55 | + benchmarkRule.measureRepeated { |
| 56 | + buffer.writeByte(0x42) |
| 57 | + buffer.readByte() |
| 58 | + } |
| 59 | + } |
| 60 | + |
| 61 | + @Test |
| 62 | + fun shortRW() { |
| 63 | + benchmarkRule.measureRepeated { |
| 64 | + buffer.writeShort(0x42) |
| 65 | + buffer.readShort() |
| 66 | + } |
| 67 | + } |
| 68 | + |
| 69 | + @Test |
| 70 | + fun intRW() { |
| 71 | + benchmarkRule.measureRepeated { |
| 72 | + buffer.writeInt(0x42) |
| 73 | + buffer.readInt() |
| 74 | + } |
| 75 | + } |
| 76 | + |
| 77 | + @Test |
| 78 | + fun longRW() { |
| 79 | + benchmarkRule.measureRepeated { |
| 80 | + buffer.writeLong(0x42) |
| 81 | + buffer.readLong() |
| 82 | + } |
| 83 | + } |
| 84 | +} |
| 85 | + |
| 86 | + |
| 87 | +@RunWith(Parameterized::class) |
| 88 | +open class Utf8Benchmark(val length: Int, val encoding: String) : BufferRWBenchmarkBase() { |
| 89 | + private val strings = mapOf( |
| 90 | + "ascii" to ("Um, I'll tell you the problem with the scientific power that you're using here, " |
| 91 | + + "it didn't require any discipline to attain it. You read what others had done and you " |
| 92 | + + "took the next step. You didn't earn the knowledge for yourselves, so you don't take any " |
| 93 | + + "responsibility for it. You stood on the shoulders of geniuses to accomplish something " |
| 94 | + + "as fast as you could, and before you even knew what you had, you patented it, and " |
| 95 | + + "packaged it, and slapped it on a plastic lunchbox, and now you're selling it, you wanna " |
| 96 | + + "sell it."), |
| 97 | + "utf8" to |
| 98 | + ("Սm, I'll 𝓽𝖾ll ᶌօ𝘂 ᴛℎ℮ 𝜚𝕣०bl𝖾m wі𝕥𝒽 𝘵𝘩𝐞 𝓼𝙘𝐢𝔢𝓷𝗍𝜄𝚏𝑖c 𝛠𝝾w𝚎𝑟 𝕥h⍺𝞃 𝛄𝓸𝘂'𝒓𝗲 υ𝖘𝓲𝗇ɡ 𝕙𝚎𝑟e, " |
| 99 | + + "𝛊𝓽 ⅆ𝕚𝐝𝝿'𝗍 𝔯𝙚𝙦ᴜ𝜾𝒓𝘦 𝔞𝘯𝐲 ԁ𝜄𝑠𝚌ι𝘱lι𝒏e 𝑡𝜎 𝕒𝚝𝖙𝓪і𝞹 𝔦𝚝. 𝒀ο𝗎 𝔯𝑒⍺𝖉 w𝐡𝝰𝔱 𝞂𝞽һ𝓮𝓇ƽ հ𝖺𝖉 ⅾ𝛐𝝅ⅇ 𝝰πԁ 𝔂ᴑᴜ 𝓉ﮨ၀𝚔 " |
| 100 | + + "т𝒽𝑒 𝗇𝕖ⅹ𝚝 𝔰𝒕е𝓅. 𝘠ⲟ𝖚 𝖉ⅰԁ𝝕'τ 𝙚𝚊r𝞹 𝘵Ꮒ𝖾 𝝒𝐧هwl𝑒𝖉ƍ𝙚 𝓯૦r 𝔂𝞼𝒖𝕣𝑠𝕖l𝙫𝖊𝓼, 𐑈о y𝘰𝒖 ⅆە𝗇't 𝜏α𝒌𝕖 𝛂𝟉ℽ " |
| 101 | + + "𝐫ⅇ𝗌ⲣ๐ϖ𝖘ꙇᖯ𝓲l𝓲𝒕𝘆 𝐟𝞼𝘳 𝚤𝑡. 𝛶𝛔𝔲 s𝕥σσ𝐝 ﮩ𝕟 𝒕𝗁𝔢 𝘴𝐡𝜎ᴜlⅾ𝓮𝔯𝚜 𝛐𝙛 ᶃ𝚎ᴨᎥս𝚜𝘦𝓈 𝓽𝞸 a𝒄𝚌𝞸mρl𝛊ꜱ𝐡 𝓈𝚘m𝚎𝞃𝔥⍳𝞹𝔤 𝐚𝗌 𝖋a𝐬𝒕 " |
| 102 | + + "αs γ𝛐𝕦 𝔠ﻫ𝛖lԁ, 𝚊π𝑑 Ь𝑒𝙛૦𝓇𝘦 𝓎٥𝖚 ⅇvℯ𝝅 𝜅ո𝒆w w𝗵𝒂𝘁 ᶌ੦𝗎 h𝐚𝗱, 𝜸ﮨ𝒖 𝓹𝝰𝔱𝖾𝗇𝓽𝔢ⅆ і𝕥, 𝚊𝜛𝓭 𝓹𝖺ⅽϰ𝘢ℊеᏧ 𝑖𝞃, " |
| 103 | + + "𝐚𝛑ꓒ 𝙨l𝔞р𝘱𝔢𝓭 ɩ𝗍 ہ𝛑 𝕒 pl𝛂ѕᴛ𝗂𝐜 l𝞄ℼ𝔠𝒽𝑏ﮪ⨯, 𝔞ϖ𝒹 n𝛔w 𝛾𝐨𝞄'𝗿𝔢 ꜱ℮ll𝙞nɡ ɩ𝘁, 𝙮𝕠𝛖 w𝑎ℼ𝚗𝛂 𝕤𝓮ll 𝙞𝓉."), |
| 104 | + // The first 't' is actually a '𝓽' |
| 105 | + "sparse" to ("Um, I'll 𝓽ell you the problem with the scientific power that you're using here, " |
| 106 | + + "it didn't require any discipline to attain it. You read what others had done and you " |
| 107 | + + "took the next step. You didn't earn the knowledge for yourselves, so you don't take any " |
| 108 | + + "responsibility for it. You stood on the shoulders of geniuses to accomplish something " |
| 109 | + + "as fast as you could, and before you even knew what you had, you patented it, and " |
| 110 | + + "packaged it, and slapped it on a plastic lunchbox, and now you're selling it, you wanna " |
| 111 | + + "sell it."), |
| 112 | + "2bytes" to "\u0080\u07ff", |
| 113 | + "3bytes" to "\u0800\ud7ff\ue000\uffff", |
| 114 | + "4bytes" to "\ud835\udeca", |
| 115 | + // high surrogate, 'a', low surrogate, and 'a' |
| 116 | + "bad" to "\ud800\u0061\udc00\u0061" |
| 117 | + ) |
| 118 | + |
| 119 | + private var string: String = "" |
| 120 | + |
| 121 | + public companion object { |
| 122 | + @JvmStatic |
| 123 | + @Parameters(name = "{0},{1}") |
| 124 | + fun data(): Collection<Array<Any>> { |
| 125 | + val lengths = listOf(20, 2000, 200000) |
| 126 | + val encodings = listOf("ascii", "utf8", "sparse", "2bytes", "3bytes", "4bytes", "bad") |
| 127 | + |
| 128 | + return buildList { |
| 129 | + for (l in lengths) { |
| 130 | + for (e in encodings) { |
| 131 | + add(arrayOf(l, e)) |
| 132 | + } |
| 133 | + } |
| 134 | + } |
| 135 | + } |
| 136 | + } |
| 137 | + |
| 138 | + private fun constructString(): String { |
| 139 | + val part = strings[encoding] ?: throw IllegalArgumentException("Unsupported encoding: $encoding") |
| 140 | + val builder = StringBuilder(length + 1000) |
| 141 | + while (builder.length < length) { |
| 142 | + builder.append(part) |
| 143 | + } |
| 144 | + builder.setLength(length) |
| 145 | + return builder.toString() |
| 146 | + } |
| 147 | + |
| 148 | + override fun padding(): ByteArray { |
| 149 | + val baseString = constructString() |
| 150 | + val baseStringByteArray = baseString.encodeToByteArray() |
| 151 | + if (baseStringByteArray.size >= minGap) { |
| 152 | + return baseStringByteArray |
| 153 | + } |
| 154 | + val builder = StringBuilder((minGap * 1.5).toInt()) |
| 155 | + while (builder.length < minGap) { |
| 156 | + builder.append(baseString) |
| 157 | + } |
| 158 | + return builder.toString().encodeToByteArray() |
| 159 | + } |
| 160 | + |
| 161 | + @Before |
| 162 | + fun setupString() { |
| 163 | + string = constructString() |
| 164 | + } |
| 165 | + |
| 166 | + @Test |
| 167 | + fun readWriteString() { |
| 168 | + benchmarkRule.measureRepeated { |
| 169 | + val s = buffer.size |
| 170 | + buffer.writeString(string) |
| 171 | + buffer.readString(buffer.size - s) |
| 172 | + } |
| 173 | + } |
| 174 | +} |
0 commit comments