SkriptLang / Skript

Skript is a Spigot plugin which allows server admins to customize their server easily, but without the hassle of programming a plugin or asking/paying someone to program a plugin for them.
https://docs.skriptlang.org
GNU General Public License v3.0
1.06k stars 367 forks source link

Script load speed optimization idea (Pattern Caching) #3003

Open ghost opened 4 years ago

ghost commented 4 years ago

Description

The idea is, when parsing a script, save every pattern (example: [(delete|disable)] %world%) that was used in the script in a separate file, like: plugins/Skript/cache/scriptname.skcache

and then the next time the script is loaded, first check through contents of its .skcache, then if it runs into syntax that is not covered by .skcache, only then check through all patterns that are registered, then when detecting the new pattern, add it to file too. (remove any patterns that do not exist in script anymore)

this way the script load time could get faster as it wouldn't have a need to check through all patterns from base skript and addons every time, it'd also make Skript more scalable, because currently if you have many addons that add a lot of syntaxes vs only a few addons / no addons, the script reloads significantly faster, with patterns being cached it could load similarly fast no matter the amount of syntaxes registered, due to that it'd check only the syntaxes that are used in the script (only first load / load after addition of new syntaxes to the script would be slower).

this is important for when you need your server to restart fast when your players are waiting or you need script reload during mid-game to not affect players too much with lag. On my server it takes about 6 minutes to load my scripts.

additionally, there could be made a points system for patterns, so every time a pattern is triggered, it would add +1 to it, patterns that have more points are being checked first. (more commonly used patterns being prioritized)

I was thinking of editing skript code and adding this in myself, but sadly I've got lost in its parsing code, hopefully someone would like to pick this project.

Moderocky commented 4 years ago

In principle, I think this sounds like a nice idea. :) However, it might make more sense to make the cache a collective file (similar to the variables CSV) so people don't get tempted to touch it/break it/delete it — I can imagine some people being confused by having a second folder which appears to have the same files in it and misusing it (i.e. putting their scripts in the cache section by mistake.)

Pikachu920 commented 4 years ago

I was discussing this in the skript dev chat a couple weeks ago. it's definitely possible but not as simple as just putting the patterns in a file. you would also need to serialize and correctly set init values and global state.

ghost commented 4 years ago

I was discussing this in the skript dev chat a couple weeks ago. it's definitely possible but not as simple as just putting the patterns in a file. you would also need to serialize and correctly set init values and global state.

why init values? if it'd still go through same process just with different set of patterns

Pikachu920 commented 4 years ago

@Govindass for example event-player storing the pattern event-<.+> is useless because now you lose the player part

ghost commented 4 years ago

@Govindass for example event-player storing the pattern event-<.+> is useless because now you lose the player part

why not store [event-]player

then you don't lose anything? I did not mean storing: player event-player

I meant storing the whole pattern, before it gets split into multiple.

EDIT: oh, I understand now, the event-<.+> is the pattern itself, yup that one is complicated

Pikachu920 commented 4 years ago

another idea: just store the class of the TriggerItem alongside the raw version of that parsing. Not the most efficient and expressions won't be cached but at least we can remove to trying parsing all effects. also, we can prefix the .skc file with a cache format version number so we can start it at 1 and increment it as more complex, less simplistic forms of caching are implemented.

bloggy commented 3 years ago

I want this :) we are wating minutes until skript loads all scripts.

TheDGOfficial commented 3 years ago

Njol wrote some code for ScriptInfo serialization if I remember correctly (commented out in source) though that is probably psuedo code and is from like 2011, it can be still useful as a starting point for people wanting to implement something like this.

I remember using various Java profilers to find out the most time consuming method, and it was SkriptParser#parse_i if I remember correctly. That function basically calls itself again (recursive design) instead of iterating (iterative design) and it is not tail recursive so it can’t be easily turned into a recursive algorithm.

Maybe splitting logic to smaller methods would help finding the problematic section or JIT at inlining; maybe it is completely unrelated, don’t know honestly.

Although that method is probably untouched, my profiler tests were on old days so it would help to get some profiler results from you guys with the latest stuff.

For profiling I would recommend YourKit and/or VisualVM. If YourKit be sure to sort methods by own time and not total time.

Meanwhile as a workaround, async script loading may (but not necessarily) speed up loading and it will not block the server (use the latest GH actions build of my PR) which will fix most problems like timeouts. Or just split out the scripts and reload one by one as a manual workaround.

bloggy commented 3 years ago

I am not a developer and many of the things you explained I don't really understand very well. I am really interested in a solution in this but I am unable to implement it. However, I think lots of skript users would love to see their server startup be much faster as it is right now. If you only have like 2 - 3 skripts on your server you will not even notice the loading time I guess, but when you are really into "skripting" and come up with heavy scripts with like 1000s of lines of code then you will know what I mean. Right now it takes about 7 minutes for my server to start and this is only because of skript parsing on startup. So any solution to this would be great :-) I'm a big fan of skript, been using it for many many years now.

Also, I usually don't reload the bigger skripts while the server is running but for me the main problem is the loading time / startup time when starting the spigot / paper server.

Maybe this is all wrong, but wouldn't it be possible to add a functionality like a cache where I can manually add skripts which I don't want to be syntax checked on every server start?

File: cache.sk

So when starting the server the skript parser would only load but pass any syntax etc. checks for the listed scripts?

TheDGOfficial commented 3 years ago

I know what you mean. I've wrote a lot of script code back in the days, too. Mine was not 7 minutes but like 3 minutes, though.

My opinion on this, is, while caching is great, it does not solve the slowness of parser. Even Javac (the Java compiler) has a incremental compilation, which is a sort of caching that only compiles modified files (not exactly like that, it compiles the affected files the modified files affecting it, too, but you know what I mean), but Javac itself is still pretty fast without that.

For example, whole Skript code, on my machine, in a clean git clone, takes like 3 minutes to compile (this not bare Javac time though; it includes gradle tasks and other stuff like dependency downloading, it maybe even less if we just count Javac). Do note that my computer is not a decent one. And whole Skript code has 118,037 lines, compare that with your scripts line counts.

bloggy commented 3 years ago

I have only about 40 - 50k lines of code and about 100 skript files in my SKRIPT folder. The most time consuming (when starting the server or reloading these skripts) skripts seem to be the ones with GUI stuff in them (just a feeling, I can not say for sure). For example I have a skript with many functions which contain gui stuff like that:

make a gui slot 1 of {_p} with {@enderpearl} with name "&4Set Default Location" with lore "&7Default Location: %{_defaultlocation}%", "&9Setzt die Default Location für alle Spieler" AND "&9zu deiner aktuellen Position" to run function Setting_DefaultLocation(1,{_id},{_p},false) using "left" click
            make a gui slot 1 of {_p} with {@enderpearl} with name "&4Set Default Location" with lore "&7Default Location: %{_defaultlocation}%", "&9Setzt die Default Location für alle Spieler" AND "&9zu deiner aktuellen Position" to run function Setting_DefaultLocation(1,{_id},{_p},true) using "right" click
            make a gui slot 2 of {_p} with {@enderpearl} with name "&4Set Team1 Location" with lore "&7Team 1 Location: %{_team1location}%", "&9Setzt die Team1 Location" AND "&9zu deiner aktuellen Position" to run function Setting_Team1Location(2,{_id},{_p},false) using "left" click
            make a gui slot 2 of {_p} with {@enderpearl} with name "&4Set Team1 Location" with lore "&7Team 1 Location: %{_team1location}%", "&9Setzt die Team1 Location" AND "&9zu deiner aktuellen Position" to run function Setting_Team1Location(2,{_id},{_p},true) using "right" click 
            make a gui slot 3 of {_p} with {@enderpearl} with name "&4Set Team2 Location" with lore "&7Team 2 Location: %{_team2location}%", "&9Setzt die Team2 Location" AND "&9zu deiner aktuellen Position" to run function Setting_Team2Location(3,{_id},{_p},false) using "left" click
            make a gui slot 3 of {_p} with {@enderpearl} with name "&4Set Team2 Location" with lore "&7Team 2 Location: %{_team2location}%", "&9Setzt die Team2 Location" AND "&9zu deiner aktuellen Position" to run function Setting_Team2Location(3,{_id},{_p},true) using "right" click 
            make a gui slot 4 of {_p} with PLAYER HEAD with name "&4Set Max Players" with lore "&7Max. Spieler: %{_maxplayers}%" AND "&9Definiert die maximale Spieler-Anzahl" to run function Setting_MaxPlayers(4,{_id},{_p},false) using "left" click
            make a gui slot 4 of {_p} with PLAYER HEAD with name "&4Set Max Players" with lore "&7Max. Spieler: %{_maxplayers}%" AND "&9Definiert die maximale Spieler-Anzahl" to run function Setting_MaxPlayers(4,{_id},{_p},true) using "right" click
            make a gui slot 5 of {_p} with BOOK with name "&4Set Team Selection" with lore "&7Team Selection: %{_teamselection}%", "&7true: &9Spieler darf Team wählen" AND "&7false: &9Spieler wird automatisch zugewiesen" to run function Setting_TeamSelection(5,{_id},{_p}) using "left" click

But I have also skripts without GUIs which take forever to load.

mrMaruda commented 3 years ago

I have only about 40 - 50k lines of code and about 100 skript files in my SKRIPT folder. The most time consuming (when starting the server or reloading these skripts) skripts seem to be the ones with GUI stuff in them (just a feeling, I can not say for sure). For example I have a skript with many functions which contain gui stuff like that: But I have also skripts without GUIs which take forever to load.

there is a solution for that: when you're making a slot with more complex itemstack (for example when you specify a name, amount, lore, enchantments, nbt, etc.) - you have to make a variable before that line - put the whole itemstack inside - and in tuske's syntax just use this variable instead, example:

set {_i} to ({@enderpearl} with name "&4Set Default Location" with lore "&7Default Location: %{_defaultlocation}%", "&9Setzt die Default Location für alle Spieler" AND "&9zu deiner aktuellen Position")
make a gui slot 1 of {_p} with {_i} to run function Setting_DefaultLocation(1,{_id},{_p},true) using "right" click

and boom +500 to length +10 to appearance -200 to loading speed

TheBentoBox commented 3 years ago

+1 to the above. I also have tens of thousands of lines of Skript loading and it only takes a bit over a minute for me, mostly done via using the above method to split up large lines and by avoiding some of the long syntaxes entirely, e.g. I do most particles and many sounds via console commands (sometimes triggered with custom skript-mirror effects) rather than Skript effects since the console commands are much faster to parse.

That's not all to say there shouldn't be optimizations made, but you could probably improve your load times by many minutes by simply optimizing the formatting of your scripts.

Moderocky commented 6 months ago

I was about to get started making this and then I realised that there might not be a point. Imagine I cache the parse structure of every script; if I change something I'll need to reload it from scratch, if I don't change something I don't need to reload it. This would only save time if you're reloading scripts that haven't actually changed.

When it comes to restarting the server it isn't safe to cache scripts as their post-parse structure, since the available elements (and their syntax patterns) could conceivably be different from what they were when you parsed the script, and there isn't a safe way we can keep track of that.

What I can suggest (which would be much simpler, safer and easier to implement) is to store the hashes of script files when loaded and then have some kind of /skript reload changed command that reloads only files that are different from what they used to be.