Mark Gritter (markgritter) wrote,
Mark Gritter
markgritter

Selecting Strings in Solidity

For a personal project implementing procedurally generated content on Ethereum, I would like to find the most efficient way to accomplish the following in Solidity:


if ( n == 0 ) {
buf.append( "unicorn" );
} else if ( n == 1 ) {
buf.append( "raven" );
} else if ( n == 2 ) {
buf.append( "sparrow" );
} else if ( n == 3 ) {
...


Solidity puts string literals in an inefficient binary encoding in the compiled format. In memory, there is one 256-bit integer for the string length, and then as many 32-byte memory chunks as are necessary to hold the string. In the compiled form this is somewhat better with a compact representation of the length, but a 256-bit big-endian number representing the string:


/* "src/testchoice.sol":790:813 buf.append( "unicorn" ) */
tag_42
0x40
dup1
mload
swap1
dup2
add
0x40
mstore
0x7 <== length
dup2
mstore
0x756e69636f726e00000000000000000000000000000000000000000000000000 <== string literal
0x20
dup3
add
mstore
/* "src/testchoice.sol":790:793 buf */
dup5
swap1
/* "src/testchoice.sol":790:813 buf.append( "unicorn" ) */
0xffffffff
/* "src/testchoice.sol":790:800 buf.append */
tag_43
/* "src/testchoice.sol":790:813 buf.append( "unicorn" ) */
and
jump // in


The next obvious thing to try is a literal array. This would have some disadvantages for the final design, but it might be more efficient in gas usage:


string[11] memory animals = [ "unicorn", "raven", "sparrow", "scorpion",
"coyote", "eagle", "owl", "lizard",
"zebra", "duck", "kitten" ];
buf.append( animals[n] );


And a somewhat less-obvious route is to efficiently pack the strings into a single literal, and then extract the portion that I need. (I have to implement my own string append function anyway.)


string memory animals = "unicornravensparrowscorpioncoyoteeagleowllizardzebraduckkitten";
if ( n == 0 ) {
buf.appendPacked( animals, 0, 6);
} else if ( n == 1 ) {
buf.appendPacked( animals, 6, 5 );
} else if ( n == 2 ) {
buf.appendPacked( animals, 11, 7 );
...


How do they compare? I compiled each version in isolation as part of a contract, and tested each for gas usage using Remix's Javascript VM (at https://ethereum.github.io/browser-solidity/)

nested if: code size 4460 bytes, gas costs 22710, 22548, 22602

static array: code size 4198 bytes, gas costs 24682, 24682, 24682

packed literal: code size 3676 bytes, gas costs 22798, 22879, 22744

The packing makes a big difference on code size, and it runs nearly as efficiently as the original. The static array is significantly more expensive, though it does provide some code size benefits.
Tags: geek, programming
Subscribe
  • Post a new comment

    Error

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 0 comments