mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-06-02 04:09:20 +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() {
|
||||
benchmarkRule.measureRepeated {
|
||||
assertNull(
|
||||
RichTextParser().parseMediaUrl(
|
||||
RichTextParser().createMediaContent(
|
||||
"https://github.com/vitorpamplona/amethyst/releases/download/v0.83.10/amethyst-googleplay-universal-v0.83.10.apk",
|
||||
EmptyTagList,
|
||||
null,
|
||||
|
@ -24,7 +24,6 @@ import androidx.benchmark.junit4.BenchmarkRule
|
||||
import androidx.benchmark.junit4.measureRepeated
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.encoders.HexValidator
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
@ -37,58 +36,59 @@ import org.junit.runner.RunWith
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
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
|
||||
fun hexDecodeOurs() {
|
||||
benchmarkRule.measureRepeated {
|
||||
r.measureRepeated {
|
||||
com.vitorpamplona.quartz.encoders.Hex
|
||||
.decode(testHex)
|
||||
.decode(hex)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hexEncodeOurs() {
|
||||
val bytes =
|
||||
r.measureRepeated {
|
||||
com.vitorpamplona.quartz.encoders.Hex
|
||||
.decode(testHex)
|
||||
|
||||
benchmarkRule.measureRepeated {
|
||||
assertEquals(
|
||||
testHex,
|
||||
com.vitorpamplona.quartz.encoders.Hex
|
||||
.encode(bytes),
|
||||
)
|
||||
.encode(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hexDecodeBaseSecp() {
|
||||
benchmarkRule.measureRepeated {
|
||||
r.measureRepeated {
|
||||
fr.acinq.secp256k1.Hex
|
||||
.decode(testHex)
|
||||
.decode(hex)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hexEncodeBaseSecp() {
|
||||
val bytes =
|
||||
r.measureRepeated {
|
||||
fr.acinq.secp256k1.Hex
|
||||
.decode(testHex)
|
||||
|
||||
benchmarkRule.measureRepeated {
|
||||
assertEquals(
|
||||
testHex,
|
||||
fr.acinq.secp256k1.Hex
|
||||
.encode(bytes),
|
||||
)
|
||||
.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
|
||||
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 com.vitorpamplona.quartz.crypto.CryptoUtils
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
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)
|
||||
|
||||
object HexValidator {
|
||||
private fun isHexChar(c: Char): Boolean =
|
||||
when (c) {
|
||||
in '0'..'9' -> true
|
||||
in 'a'..'f' -> true
|
||||
in 'A'..'F' -> true
|
||||
else -> false
|
||||
}
|
||||
val lowerCaseHex = arrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f')
|
||||
val upperCaseHex = arrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F')
|
||||
|
||||
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 {
|
||||
if (hex == null) return false
|
||||
if (hex.isEmpty()) return false
|
||||
if (hex.length % 2 != 0) return false // must be even
|
||||
var isHex = true
|
||||
if (hex.length and 1 != 0) return false // must be even
|
||||
|
||||
for (c in hex) {
|
||||
if (!isHexChar(c)) {
|
||||
isHex = false
|
||||
break
|
||||
}
|
||||
for (c in hex.indices) {
|
||||
if (hexToByte[hex[c].code] < 0) return false
|
||||
}
|
||||
return isHex
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
fun decode(hex: String): ByteArray {
|
||||
// faster version of hex decoder
|
||||
require(hex.length % 2 == 0)
|
||||
val outSize = hex.length / 2
|
||||
val out = ByteArray(outSize)
|
||||
|
||||
for (i in 0 until outSize) {
|
||||
out[i] = (hexToBin(hex[2 * i]) * 16 + hexToBin(hex[2 * i + 1])).toByte()
|
||||
require(hex.length and 1 == 0)
|
||||
return ByteArray(hex.length / 2) {
|
||||
(hexToByte[hex[2 * it].code] shl 4 or hexToByte[hex[2 * it + 1].code]).toByte()
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun encode(input: ByteArray): String {
|
||||
val len = input.size
|
||||
val out = CharArray(len * 2)
|
||||
for (i in 0 until len) {
|
||||
out[i * 2] = hexCode[(input[i].toInt() shr 4) and 0xF]
|
||||
out[i * 2 + 1] = hexCode[input[i].toInt() and 0xF]
|
||||
val out = CharArray(input.size * 2)
|
||||
var outIdx = 0
|
||||
for (i in 0 until input.size) {
|
||||
val chars = byteToHex[input[i].toInt() and 0xFF]
|
||||
out[outIdx++] = (chars shr 8).toChar()
|
||||
out[outIdx++] = (chars and 0xFF).toChar()
|
||||
}
|
||||
return String(out)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user