mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-06-03 20:39:30 +02:00
Improving performance of the Hex encoder.
This commit is contained in:
parent
869debaf9d
commit
b9883093ac
@ -46,7 +46,7 @@ class RichTextParserBenchmark {
|
|||||||
fun parseApkUrl() {
|
fun parseApkUrl() {
|
||||||
benchmarkRule.measureRepeated {
|
benchmarkRule.measureRepeated {
|
||||||
assertNull(
|
assertNull(
|
||||||
RichTextParser().parseMediaUrl(
|
RichTextParser().createMediaContent(
|
||||||
"https://github.com/vitorpamplona/amethyst/releases/download/v0.83.10/amethyst-googleplay-universal-v0.83.10.apk",
|
"https://github.com/vitorpamplona/amethyst/releases/download/v0.83.10/amethyst-googleplay-universal-v0.83.10.apk",
|
||||||
EmptyTagList,
|
EmptyTagList,
|
||||||
null,
|
null,
|
||||||
|
@ -24,7 +24,6 @@ import androidx.benchmark.junit4.BenchmarkRule
|
|||||||
import androidx.benchmark.junit4.measureRepeated
|
import androidx.benchmark.junit4.measureRepeated
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import com.vitorpamplona.quartz.encoders.HexValidator
|
import com.vitorpamplona.quartz.encoders.HexValidator
|
||||||
import junit.framework.TestCase.assertEquals
|
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
@ -37,58 +36,59 @@ import org.junit.runner.RunWith
|
|||||||
*/
|
*/
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class HexBenchmark {
|
class HexBenchmark {
|
||||||
@get:Rule val benchmarkRule = BenchmarkRule()
|
@get:Rule val r = BenchmarkRule()
|
||||||
|
|
||||||
val testHex = "48a72b485d38338627ec9d427583551f9af4f016c739b8ec0d6313540a8b12cf"
|
val hex = "48a72b485d38338627ec9d427583551f9af4f016c739b8ec0d6313540a8b12cf"
|
||||||
|
val bytes =
|
||||||
|
fr.acinq.secp256k1.Hex
|
||||||
|
.decode(hex)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun hexDecodeOurs() {
|
fun hexDecodeOurs() {
|
||||||
benchmarkRule.measureRepeated {
|
r.measureRepeated {
|
||||||
com.vitorpamplona.quartz.encoders.Hex
|
com.vitorpamplona.quartz.encoders.Hex
|
||||||
.decode(testHex)
|
.decode(hex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun hexEncodeOurs() {
|
fun hexEncodeOurs() {
|
||||||
val bytes =
|
r.measureRepeated {
|
||||||
com.vitorpamplona.quartz.encoders.Hex
|
com.vitorpamplona.quartz.encoders.Hex
|
||||||
.decode(testHex)
|
.encode(bytes)
|
||||||
|
|
||||||
benchmarkRule.measureRepeated {
|
|
||||||
assertEquals(
|
|
||||||
testHex,
|
|
||||||
com.vitorpamplona.quartz.encoders.Hex
|
|
||||||
.encode(bytes),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun hexDecodeBaseSecp() {
|
fun hexDecodeBaseSecp() {
|
||||||
benchmarkRule.measureRepeated {
|
r.measureRepeated {
|
||||||
fr.acinq.secp256k1.Hex
|
fr.acinq.secp256k1.Hex
|
||||||
.decode(testHex)
|
.decode(hex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun hexEncodeBaseSecp() {
|
fun hexEncodeBaseSecp() {
|
||||||
val bytes =
|
r.measureRepeated {
|
||||||
fr.acinq.secp256k1.Hex
|
fr.acinq.secp256k1.Hex
|
||||||
.decode(testHex)
|
.encode(bytes)
|
||||||
|
|
||||||
benchmarkRule.measureRepeated {
|
|
||||||
assertEquals(
|
|
||||||
testHex,
|
|
||||||
fr.acinq.secp256k1.Hex
|
|
||||||
.encode(bytes),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
|
@Test
|
||||||
|
fun hexDecodeKotlin() {
|
||||||
|
r.measureRepeated { hex.hexToByteArray(HexFormat.Default) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
|
@Test
|
||||||
|
fun hexEncodeKotlin() {
|
||||||
|
r.measureRepeated { bytes.toHexString(HexFormat.Default) }
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun isHex() {
|
fun isHex() {
|
||||||
benchmarkRule.measureRepeated { HexValidator.isHex(testHex) }
|
r.measureRepeated { HexValidator.isHex(hex) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,8 @@ package com.vitorpamplona.quartz.encoders
|
|||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@ -69,4 +71,43 @@ class HexEncodingTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testIsHex() {
|
||||||
|
assertFalse("/0", HexValidator.isHex("/0"))
|
||||||
|
assertFalse("/.", HexValidator.isHex("/."))
|
||||||
|
assertFalse("!!", HexValidator.isHex("!!"))
|
||||||
|
assertFalse("::", HexValidator.isHex("::"))
|
||||||
|
assertFalse("@@", HexValidator.isHex("@@"))
|
||||||
|
assertFalse("GG", HexValidator.isHex("GG"))
|
||||||
|
assertFalse("FG", HexValidator.isHex("FG"))
|
||||||
|
assertFalse("`a", HexValidator.isHex("`a"))
|
||||||
|
assertFalse("gg", HexValidator.isHex("gg"))
|
||||||
|
assertFalse("fg", HexValidator.isHex("fg"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
|
@Test
|
||||||
|
fun testRandomsIsHex() {
|
||||||
|
for (i in 0..10000) {
|
||||||
|
val bytes = CryptoUtils.privkeyCreate()
|
||||||
|
val hex = bytes.toHexString(HexFormat.Default)
|
||||||
|
assertTrue(hex, HexValidator.isHex(hex))
|
||||||
|
val hexUpper = bytes.toHexString(HexFormat.UpperCase)
|
||||||
|
assertTrue(hexUpper, HexValidator.isHex(hexUpper))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
|
@Test
|
||||||
|
fun testRandomsUppercase() {
|
||||||
|
for (i in 0..1000) {
|
||||||
|
val bytes = CryptoUtils.privkeyCreate()
|
||||||
|
val hex = bytes.toHexString(HexFormat.UpperCase)
|
||||||
|
assertEquals(
|
||||||
|
bytes.toList(),
|
||||||
|
Hex.decode(hex).toList(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,65 +27,53 @@ fun ByteArray.toHexKey(): HexKey = Hex.encode(this)
|
|||||||
|
|
||||||
fun HexKey.hexToByteArray(): ByteArray = Hex.decode(this)
|
fun HexKey.hexToByteArray(): ByteArray = Hex.decode(this)
|
||||||
|
|
||||||
object HexValidator {
|
val lowerCaseHex = arrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f')
|
||||||
private fun isHexChar(c: Char): Boolean =
|
val upperCaseHex = arrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F')
|
||||||
when (c) {
|
|
||||||
in '0'..'9' -> true
|
|
||||||
in 'a'..'f' -> true
|
|
||||||
in 'A'..'F' -> true
|
|
||||||
else -> false
|
|
||||||
}
|
|
||||||
|
|
||||||
|
val hexToByte: IntArray =
|
||||||
|
IntArray(256) { -1 }.apply {
|
||||||
|
lowerCaseHex.forEachIndexed { index, char -> this[char.code] = index }
|
||||||
|
upperCaseHex.forEachIndexed { index, char -> this[char.code] = index }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encodes both chars in a single Int variable
|
||||||
|
val byteToHex =
|
||||||
|
IntArray(256) {
|
||||||
|
(lowerCaseHex[(it shr 4)].code shl 8) or lowerCaseHex[(it and 0xF)].code
|
||||||
|
}
|
||||||
|
|
||||||
|
object HexValidator {
|
||||||
fun isHex(hex: String?): Boolean {
|
fun isHex(hex: String?): Boolean {
|
||||||
if (hex == null) return false
|
if (hex == null) return false
|
||||||
if (hex.isEmpty()) return false
|
if (hex.isEmpty()) return false
|
||||||
if (hex.length % 2 != 0) return false // must be even
|
if (hex.length and 1 != 0) return false // must be even
|
||||||
var isHex = true
|
|
||||||
|
|
||||||
for (c in hex) {
|
for (c in hex.indices) {
|
||||||
if (!isHexChar(c)) {
|
if (hexToByte[hex[c].code] < 0) return false
|
||||||
isHex = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return isHex
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object Hex {
|
object Hex {
|
||||||
val hexCode =
|
|
||||||
arrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f')
|
|
||||||
|
|
||||||
// Faster if no calculations are needed.
|
|
||||||
private fun hexToBin(ch: Char): Int =
|
|
||||||
when (ch) {
|
|
||||||
in '0'..'9' -> ch - '0'
|
|
||||||
in 'a'..'f' -> ch - 'a' + 10
|
|
||||||
in 'A'..'F' -> ch - 'A' + 10
|
|
||||||
else -> throw IllegalArgumentException("illegal hex character: $ch")
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun decode(hex: String): ByteArray {
|
fun decode(hex: String): ByteArray {
|
||||||
// faster version of hex decoder
|
// faster version of hex decoder
|
||||||
require(hex.length % 2 == 0)
|
require(hex.length and 1 == 0)
|
||||||
val outSize = hex.length / 2
|
return ByteArray(hex.length / 2) {
|
||||||
val out = ByteArray(outSize)
|
(hexToByte[hex[2 * it].code] shl 4 or hexToByte[hex[2 * it + 1].code]).toByte()
|
||||||
|
|
||||||
for (i in 0 until outSize) {
|
|
||||||
out[i] = (hexToBin(hex[2 * i]) * 16 + hexToBin(hex[2 * i + 1])).toByte()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return out
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun encode(input: ByteArray): String {
|
fun encode(input: ByteArray): String {
|
||||||
val len = input.size
|
val out = CharArray(input.size * 2)
|
||||||
val out = CharArray(len * 2)
|
var outIdx = 0
|
||||||
for (i in 0 until len) {
|
for (i in 0 until input.size) {
|
||||||
out[i * 2] = hexCode[(input[i].toInt() shr 4) and 0xF]
|
val chars = byteToHex[input[i].toInt() and 0xFF]
|
||||||
out[i * 2 + 1] = hexCode[input[i].toInt() and 0xF]
|
out[outIdx++] = (chars shr 8).toChar()
|
||||||
|
out[outIdx++] = (chars and 0xFF).toChar()
|
||||||
}
|
}
|
||||||
return String(out)
|
return String(out)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user