Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

store constant index in instruction #155

Merged
merged 1 commit into from
Feb 1, 2022
Merged

store constant index in instruction #155

merged 1 commit into from
Feb 1, 2022

Conversation

ggmichaelgo
Copy link
Contributor

@ggmichaelgo ggmichaelgo commented Sep 2, 2021

What are you trying to accomplish?

Main issue: #84

In order for us to implement If tag and For tag in Liquid-C, we need to be able to freely travel the instruction pointer.
Currently, Liquid-C uses an instruction pointer and a constants pointer, and this PR will remove the constants pointer.

What approach did you choose and why?

Currently, the instruction row is structured like this:
image

With this refactor, we will be storing the constant's index value in the instruction:
image

** We need to keep our instruction to be or below 24 bit in order to store compiled Liquid template in storage services.

Performance Improvement on simple rendering

With this refactor, we are seeing a no-speed improvement with simple benchmark testing. (gist link)

main branch

Warming up --------------------------------------
       render_liquid   289.000  i/100ms
   render_liquid_pre   332.000  i/100ms
Calculating -------------------------------------
       render_liquid      2.924k (± 0.5%) i/s -     14.739k in   5.039984s
   render_liquid_pre      3.276k (± 0.7%) i/s -     16.600k in   5.067964s

PR branch

Warming up --------------------------------------
       render_liquid   297.000  i/100ms
   render_liquid_pre   334.000  i/100ms
Calculating -------------------------------------
       render_liquid      2.959k (± 0.3%) i/s -     14.850k in   5.018144s
   render_liquid_pre      3.305k (± 0.7%) i/s -     16.700k in   5.053282s

Affect on Performance

While parsing, we will be storing the constant's index value into constants_table, therefore we won't be storing duplicate values in the constants array.
With this change, we are looking up and inserting value to a hashtable while vm_assembler_write_ruby_constant.

This will slightly affect our parsing time:

Before:

/home/michael/.rvm/rubies/ruby-3.0.2/bin/ruby ./performance.rb c benchmark lax

Running benchmark for 10 seconds (with 5 seconds warmup).

Warming up --------------------------------------
              parse:    10.000  i/100ms
             render:    17.000  i/100ms
     parse & render:     5.000  i/100ms
Calculating -------------------------------------
              parse:     92.906  (± 1.1%) i/s -    930.000  in  10.010951s
             render:    172.139  (± 1.2%) i/s -      1.734k in  10.074941s
     parse & render:     56.965  (± 0.0%) i/s -    570.000  in  10.006306s

Performance side-effect:

/home/michael/.rvm/rubies/ruby-3.0.2/bin/ruby ./performance.rb c benchmark lax

Running benchmark for 10 seconds (with 5 seconds warmup).

Warming up --------------------------------------
              parse:     9.000  i/100ms
             render:    17.000  i/100ms
     parse & render:     5.000  i/100ms
Calculating -------------------------------------
              parse:     87.346  (± 1.1%) i/s -    882.000  in  10.098912s
             render:    170.742  (± 1.2%) i/s -      1.717k in  10.057790s
     parse & render:     54.575  (± 0.0%) i/s -    550.000  in  10.078152s

Notes:
Ops to update to use constant index over constant pointer:

  • OP_WRITE_NODE
  • OP_PUSH_CONST
  • OP_FIND_STATIC_VAR
  • OP_LOOKUP_CONST_KEY
  • OP_LOOKUP_COMMAND
  • OP_FILTER

TODO:

  • Remove const_ptr
  • Fix constants and constants_table getting out of sync (this was happening when variable_strict_parse_rescue rolling back constants array)

@ggmichaelgo ggmichaelgo force-pushed the mg-const-table branch 7 times, most recently from 0f0f8af to e9fd3c5 Compare September 10, 2021 04:24
@ggmichaelgo ggmichaelgo changed the title store constant address in instruction store constant index in instruction Sep 10, 2021
@ggmichaelgo ggmichaelgo force-pushed the mg-const-table branch 5 times, most recently from 06f440e to a9c841e Compare September 24, 2021 10:51
@ggmichaelgo ggmichaelgo force-pushed the mg-const-table branch 3 times, most recently from 49368f9 to 7f78392 Compare October 13, 2021 00:56
@ggmichaelgo ggmichaelgo marked this pull request as ready for review October 19, 2021 18:11
Copy link
Contributor

@macournoyer macournoyer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WOWOWOWOW! THIS IS AMAZING @ggmichaelgo 🔥 🤯 🔥

I have a couple comments/questions. But the OP_FILTER num_args looks like a blocker to me (which is why I marked as "Request changes").

I still need to wrap my head around the assembler merging logic (vm_assembler_concat), I didn't know about that.

But overall... This is awesome!

(Did I mention this is amazing?)

Comment on lines 242 to 246
if (vm_assembler_opcode_has_constant(*ip)) {
uint16_t constant_index = (ip[1] << 8) | ip[2];
constant = constants[constant_index];
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a hot spot, the VM loop is the most critical part of the code. You should try to avoid executing this if on each turn in the loop.

I would move this inside the instructions that need it. Perhaps you could refactor to an inlined function. Or even code duplication would be OK here IMO, because perf is so important inside the loop.

if (ip[-1] == OP_FILTER) {
filter_name = *const_ptr++;
filter_name = RARRAY_AREF(constant, 0);
num_args = RARRAY_AREF(constant, 1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think num_args should be stored in the constants table, it should be in the instruction (like before). The same filter can be called with a different number of args in a given template:

{{ 'a.jpg' | img_tag }} // 0
{{ 'a.jpg' | img_tag: '10x10', preload: true }} // 2

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, this looks like something a test should have caught. Perhaps a test is missing for ^

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling the same filter with a different number of arguements should be fine...

I had to store the num_args in the constants_table since I couldn't fit it in the instruction...
I had to come up with a cheeky solution where I store the filter_name and num_args together in the constants_table:
Before:

Insturction: [OP_FILTER][num_args] # 8 + 8 bit
Constants:   [filter_name] # 8 bit

After:

Insturction: [OP_FILTER][constant_index] # 8 + 16 bit (24 bit limit)
Constants: [[filter_name, num_args]]

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aaaah got it! I had the wrong assumption that filter names were reused in the constant table 👍

*instructions++ = OP_BUILTIN_FILTER;
*instructions++ = builtin_index;
*instructions++ = arg_count + 1; // include input
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note: you'll need to move this back outside the if when moving back arg_count into the instruction.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be specifically for built_in filters.
Built in filter instruction:

[OP_FILTER][BUILT_IN_INDEX][ARG_COUNT] # 8 + 8 + 8 bit

Filter instruction:

[OP_FILTER][Constant Index] # 8 + 16 bit (constant = [filter_name, num_args])

@@ -43,6 +43,7 @@ extern filter_desc_t builtin_filters[];
typedef struct vm_assembler {
c_buffer_t instructions;
c_buffer_t constants;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need this?

Seems to me it's duplicating data already in constants_table.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah... good call... I used to store the hash value of the constants, but now I am using constant as key in the constants_table.
Since the keys in st_table are sorted by entries, we can remove constants array. (I will have to double check how st_table works)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have started to remove the constants, and it is becoming a large PR just by removing it... Let's create a separate PR to remove the constants.
Also, we are storing the VALUE as keys in the constants_table, and those are pointers to actual objects, so we won't be leaving many memory footprints.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually... I am a bit afraid if JRuby or TruffleRuby will behave the same...

@dylanahsmith
Copy link
Contributor

I've added @peterzhu2118 as a reviewer, since this work would ideally fit in nicely with his unmerged work on supporting serializing compiled templates. Similarly, I've added you as a reviewer for his first unmerged PR in that chain of work.

@ggmichaelgo ggmichaelgo force-pushed the mg-const-table branch 3 times, most recently from 3df6bfd to 2a64a2c Compare January 16, 2022 21:13
@ggmichaelgo ggmichaelgo changed the base branch from master to exclude-tmp-from-rubocop January 16, 2022 21:13
@ggmichaelgo ggmichaelgo changed the base branch from exclude-tmp-from-rubocop to master January 17, 2022 17:57
Copy link
Contributor

@macournoyer macournoyer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! 🚀

if (ip[-1] == OP_FILTER) {
filter_name = *const_ptr++;
filter_name = RARRAY_AREF(constant, 0);
num_args = RARRAY_AREF(constant, 1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aaaah got it! I had the wrong assumption that filter names were reused in the constant table 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants