-
Notifications
You must be signed in to change notification settings - Fork 139
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
Lots and lots of things #2924
Lots and lots of things #2924
Conversation
8c5afc6
to
c385fbf
Compare
Great 🎉 (I remember adapting those tests when reordering fields wasn't fun)
Mmh, this will increase the serialized size probably significantly. #1531 has pretty limited value for Java, I was mostly thinking of it as a Ruby-specific convenience (since only Ruby defines these OTOH it's likely nice to have
Mmh, I wonder if we need it for Java and in the serialized output.
Do you have a description of the problem? I'd like to understand better why it's needed or if there might be any alternative (for example implicit ordering). |
@@ -92,13 +92,17 @@ public abstract class Nodes { | |||
|
|||
public static final Node[] EMPTY_ARRAY = {}; | |||
|
|||
public final int nodeId; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think for now it would be best to not include this because it adds 8 or 0 bytes (due to 64-bit word alignment) for each Node, and AFAIK currently it has no purpose in Java.
Specifically (AFAIK) there is no public Ruby API to access the node_id
or use it, only RubyVM stuff, which is CRuby-specific.
I feel the node_id stuff in CRuby was done in a rather rushed way, I think it would be worth to gather the requirements, design a proper API for this in Prism, and clarify what information the Ruby implementations need to keep and provide.
For instance TruffleRuby already keeps the startOffset & length of every node, isn't that already enough to identify a Node?
Is it the case for all nodes? A quick repro to show what I mean: #include <stdio.h>
struct A {
int a;
// int nodeID;
};
struct B {
struct A a;
int b;
int* ptr;
};
int main(int argc, char const *argv[]) {
printf("%ld\n", sizeof(struct B));
return 0;
} So without |
I'm not sure this is true. For most files it will probably add 1 byte for every node that didn't already have flags on it. But calls, strings, integers, parameters, etc. all already had flags on them, and now they're part of the same integer.
Yes, it matches the
It's a combination of a lot of things. Here is the code in Rails that's relevant: https://github.com/rails/rails/blob/4fa56814f18fd3da49c83931fa773caa727d8096/actionview/lib/action_view/template.rb#L232-L248. Every instruction in YARV keeps around an associated node_id. So I don't want to go the route of making another pass through the AST in order to assign them, because that would mean an entire other pass before we get to compile. If we instead did it while compiling, then the node id order is entirely dependent on the compiler, and we'd need to replicate that some how in order to find the next node. I don't love the feature, but at this point it's one of the last things blocking us getting merged into CRuby, so I'd like to get this moving.
Yeah it's the case for all nodes. All nodes have 16 bytes for type, 16 bytes for flags, then a 32-bit hole, then a 64-bit pointer to the start of the node.
Because we're exposing
I think it would be good to go through this exercise, but I'd really like to get prism merged sooner rather than later, so I'd like to do this after. I don't think it will be a problem to refactor this later, as it's basically a continuation of what is already there.
It's not enough, unfortunately. The ERB use case makes this not sufficient because nodes can move when ERB compiles them. |
Thank you for 23f3a1d, I think it's the best trade-off for now. I will try to take a look at the Rails use-case to understand it better and whether it could be done another way. |
Could you clarify this part:
What do you mean by moving? |
Ah right, prism/templates/include/prism/ast.h.erb Lines 126 to 150 in ad5efb2
pm_node_t (via pm_location_t ) and I forgot that C makes the whole size of the struct a multiple of its widest scalar member (i.e. a pointer here, so multiple of 8 bytes and there was 4 byte padding before this change).
If the |
I took a look at serialized size stats with only semantics fields and:
after:
So that's about a 19% regression for I would assume this increase comes from The common flags might increase serialize size because they take 2 bits more and make other flags start at 4 instead of 1, making it more likely to be >=128 and need two bytes serialized. But I think that's pretty minimal so I'll measure how much is that, that's probably not worth optimizing or making things more complex. |
end | ||
super(node) | ||
end | ||
end | ||
end | ||
|
||
class Node | ||
def newline? # :nodoc: | ||
@newline ? true : false | ||
def newline_flag? # :nodoc: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not ideal to rename this one and have the common flag be called newline?
, because it changes semantics and probably nobody should use the latter directly: the latter is the "potential but not necessarily newline" while the former is the real newline flag, i.e. "this node is the sole node on that line with the flag".
How about renaming the common flag as POTENTIAL_NEWLINE or so?
…LDS is set * Note that we could shift the flags by 2 on serialize & deserialize but it does not seems worth it as it does not save serialized size in any significant amount, i.e. average was 0.799 before ruby#2924. * $ bundle exec rake serialized_size:topgems Before: Total sizes for top 100 gems: total source size: 90207647 total serialized size: 69477115 total serialized/total source: 0.770 Stats of ratio serialized/source per file: average: 0.844 median: 0.825 1st quartile: 0.597 3rd quartile: 1.064 min - max: 0.078 - 3.792 After: Total sizes for top 100 gems: total source size: 90207647 total serialized size: 66150209 total serialized/total source: 0.733 Stats of ratio serialized/source per file: average: 0.800 median: 0.779 1st quartile: 0.568 3rd quartile: 1.007 min - max: 0.076 - 3.675
I wish I had this updated on JRuby to say what the impact is for growing the serialized size. There are two cases and we should consider the balance of both:
This second option has been brought up but it seems to be an idea and not necessarily a goal? FFI means we need something consistent so I guess that is sort of a form of 2 at least for consuming it as a gem? From my perspective we have already done other things to grow the size (like only having a callnode vs fcall,vcall,call) so this is just more of the same. I will not have time to look into this for probably two weeks so I cannot access what this cost (which for all we know may be nothing). I am not expressing a recommendation. |
Fixing the increase turned out to be easy, I made a PR: #2956 We could potentially support the full serialization format for Loader.java/Nodes.java, but since there is no use case currently it would just be additional complexity (and it would notably need some different package to avoid Java class clashes if we want to load them in the same process). |
@eregon yeah I am just saying for option 2 in my comment they would need to be the same unless you decided to load multiple formats which I agree adds complexity for something which is just a future (and I am not suggesting we support full serialization but something which all three impls can load). It might not be worth it or it might which is why I asked. I am not sure if this will actually add significant or possibly even measurable time. deserialization is pretty fast in comparison to building the tree. I think we should measure this. I would love for us to reduce the size of this as small as possible but in retrospect I have been surprised how fast deserialization has been. I think we should measure the impact. |
BLEH. I cannot even install my current build of jruby-prism-gem because gcc 14 gives an error about using transposed calloc args. Trying to override this with --with-cflags seems to not be picking this up (which could be something JRuby specific since it is uncommon to be compiling C in a JRuby gem). |
ok another issue is I am not tracking serialize vs building AST but from what I remember most time is making the tree and a significant part of that was in the newline visitor. I guess when I do have time regardless of how we resolve this I will break this down more. |
…LDS is set * Note that we could shift the flags by 2 on serialize & deserialize but it does not seems worth it as it does not save serialized size in any significant amount, i.e. average was 0.799 before #2924. * $ bundle exec rake serialized_size:topgems Before: Total sizes for top 100 gems: total source size: 90207647 total serialized size: 69477115 total serialized/total source: 0.770 Stats of ratio serialized/source per file: average: 0.844 median: 0.825 1st quartile: 0.597 3rd quartile: 1.064 min - max: 0.078 - 3.792 After: Total sizes for top 100 gems: total source size: 90207647 total serialized size: 66150209 total serialized/total source: 0.733 Stats of ratio serialized/source per file: average: 0.800 median: 0.779 1st quartile: 0.568 3rd quartile: 1.007 min - max: 0.076 - 3.675
This PR does a lot of stuff, because it was all interconnected and it was easier to do as a group.
bin/prism error
utility for creating new files of this kind to make it slightly more manageable.location
parameter has been moved to the front (aftersource
andnode_id
) so keep the common fields at the beginning. The actual node initializers should be seen as private API for all intents and purposes, because they will contain the most churn whenever something changes.newline
orstatic_literal
). This was already present in the C and Rust APIs, but wasn't exposed to the Ruby, JS, or Java APIs. This is now present everywhere. This addresses a long-standing issue request (fixes A superclass/module/interface/flag for static literals #1531). Now every node has a commonstatic_literal?
method that can be used to check if something is a static literal. These flags are now exposed in the inspect output as well.Node#breadth_first_search
is added for convenience, I've written this out a bunch of times before.node_id
is now exposed in every API for every node. I was very hesitant to add this functionality to prism, because I was worried about memory usage and performance. However, I don't see any other way around it that will work with the same usage pattern for the way Rails uses node id. I was getting away with it for CRuby by using (line, column) but for Rails it changes based on the ERB being compiled and I don't see how to get it otherwise. Happily, it doesn't actually take up any more space in the C API because we had a 32-bit hole in all of our nodes on 64-bit systems because of pointer alignment, so it doesn't actually increase the size of the nodes. This fixes Supportnode_id
#2383.