solady icon indicating copy to clipboard operation
solady copied to clipboard

⚡️ Optimize address to string

Open Philogy opened this issue 10 months ago • 3 comments

Description

Optimized address to hex string with some fancy bit operations. Has a fairly large gas trade-off ~360 bytes more by the looks of it, open questions:

  • worth the codesize trade off?
  • document how it works?

Checklist

  • [x] Ran forge fmt?
  • [x] Ran forge test?

Philogy avatar Feb 05 '25 01:02 Philogy

Also not sure what's wrong with the diff ci check, showing error:

fatal: a branch named 'main' already exists

Philogy avatar Feb 05 '25 01:02 Philogy

What made you think about this trick?

Vectorized avatar Feb 05 '25 02:02 Vectorized

I was benchmarking and trying to optimize address checksum computation for a project of mine. I was looking at a table of hex nibble to binary to ascii-binary table and noticed that you could relatively cheaply compute:

  • bitmap of whether each nibble will be a letter or not
  • and eventually how to make a padded nibble into it's ascii representation
'0' 0000 -> 00110000
'1' 0001 -> 00110001
'2' 0010 -> 00110010
'3' 0011 -> 00110011
'4' 0100 -> 00110100
'5' 0101 -> 00110101
'6' 0110 -> 00110110
'7' 0111 -> 00110111
'8' 1000 -> 00111000
'9' 1001 -> 00111001
'a' 1010 -> 01100001
'b' 1011 -> 01100010
'c' 1100 -> 01100011
'd' 1101 -> 01100100
'e' 1110 -> 01100101
'f' 1111 -> 01100110

I noticed that a-f all have bit 4 set, but to exclude 8 & 9 you need to also check bit 2 & 3, giving you: b4 && (b3 || b2), which you can do by shifting and masking the word a couple times.

For the ascii representation I noticed it's basically:

          b8   b7   b6   b5   b4   b3   b2   b1
digit:     0    0    1    1   [       x       ]
letter:    0    1    1    0   [   8 ^ x - 1   ]

So yeah once you have the nibbles spaced out, padded and you have a bitmap of which byte will be a letter or not it's relatively easy to get the ascii representation.

Philogy avatar Feb 05 '25 02:02 Philogy

Can cheaply create these masks JIT using the below formula:

function repeat(uint256 x, uint strideBits) internal pure returns (uint256) {
    return x * (type(uint256).max / ((1 << strideBits) - 1));
}

0xClandestine avatar Jul 13 '25 01:07 0xClandestine

Can cheaply create these masks JIT using the below formula:

function repeat(uint256 x, uint strideBits) internal pure returns (uint256) {
    return x * (type(uint256).max / ((1 << strideBits) - 1));
}

Using this method to generate masks saved about 50 bytes of codesize, at the expense of additional gas likely not worth it but interesting way to generate repeat constants nonetheless.

0xClandestine avatar Jul 13 '25 22:07 0xClandestine