ZScript method call inlining

Moderator: GZDoom Developers

Locked
argv
Posts: 184
Joined: Tue Aug 30, 2016 4:47 pm

ZScript method call inlining

Post by argv »

Since one of my previous feature requests was denied, the only way to make something special happen whenever a field's value changes (without relying on the caller to do the special thing, at the risk of the developer forgetting to do so) is to hide it behind getter and setter methods.

Problem: getter methods are slow! To demonstrate, I've attached a pk3 that adds two classes, which demonstrate the difference. To use:
  1. Load vmkiller.pk3.
  2. Start a game with it.
  3. summon VMDragger
VMDragger stores the number 1 in a field named SimpleField, then on every tic, does this:

Code: Select all

int a;
for (let i = 0; i < 1000000; i++)
	a += SimpleField;
You'll observe that the frame rate drops considerably, due to the VM load. That's not the issue, though; it's just for comparison. To see the actual issue:
  1. remove VMDragger
  2. summon VMKiller
The VM promptly grinds to a halt under millions of method calls (as you can see in stat vm). The code, however, is only slightly different:

Code: Select all

int GetSimpleField() {
	return SimpleField;
}
…
int a;
for (let i = 0; i < 1000000; i++)
	a += GetSimpleField();
As you can see, the difference is that an accessor method is used instead of direct field access. Even though they do basically the same thing, there is a massive performance penalty for using the accessor method. Because of said performance penalty, it is a bad idea to use accessor methods in ZScript, even if they would otherwise be helpful.

To remedy this, please consider adding VM-level inlining (whether automatic or explicit) of trivial non-virtual methods.
Attachments
vmkiller.pk3
(341 Bytes) Downloaded 31 times
User avatar
Graf Zahl
Lead GZDoom+Raze Developer
Lead GZDoom+Raze Developer
Posts: 49056
Joined: Sat Jul 19, 2003 10:19 am
Location: Germany

Re: ZScript method call inlining

Post by Graf Zahl »

Find me someone with some knowledge in how to build compilers and has time to work on it and we may be in business. Right now this is out of the question for purely logistical reasons alone and these reasons won't change any time soon. Do you have any concrete example where this may become an issue?
User avatar
Gutawer
Posts: 469
Joined: Sat Apr 16, 2016 6:01 am
Preferred Pronouns: She/Her

Re: ZScript method call inlining

Post by Gutawer »

This seems like a textbook case of premature optimisation to me - are you really expecting to be calling a function like that 1 million times every single tick? As you've noted, even just adding SimpleField to a without a getter method lags the game out (in my opinion, to an already unacceptable amount of jitter). Personally I've never noticed a huge amount of lag from using getter/setter functions in my normal programming in ZScript, so this seems to me like "doing a simple task a very large amount of times will lag", which is reasonable. I'd be surprised if there was a substantial amount of tasks which couldn't be simplified that would actually cause issues with this :?.
argv
Posts: 184
Joined: Tue Aug 30, 2016 4:47 pm

Re: ZScript method call inlining

Post by argv »

Find me someone with some knowledge in how to build compilers and has time to work on it and we may be in business.
Fair enough.

Out of curiosity, are you open to someone (with copious free time :P) integrating some existing VM (Java/OpenJDK, V8 JavaScript, etc) into the engine? I seem to recall you having a problem with making such a VM able to extend and override C++ classes. What was that about?
Do you have any concrete example where this may become an issue?
A GUI toolkit, in which calling a setter implicitly causes widgets' layout to be recomputed. The only way to enforce the use of a setter is to also have a getter, and calling getters 5000 times per frame wrecks frame rate.

I haven't actually written such code, because I did this benchmark and saw how bad the performance hit would be.
This seems like a textbook case of premature optimisation to me - are you really expecting to be calling a function like that 1 million times every single tick?
No, but I am expecting that lots of accessors throughout will result in maybe 5k extra calls per *frame* (so, up to 20k/tic) in real-world code. 10k/tic calls increases VM time by 1ms/tic. Ouch.
User avatar
Rachael
Posts: 13530
Joined: Tue Jan 13, 2004 1:31 pm
Preferred Pronouns: She/Her
Contact:

Re: ZScript method call inlining

Post by Rachael »

argv wrote:
Find me someone with some knowledge in how to build compilers and has time to work on it and we may be in business.
Fair enough.

Out of curiosity, are you open to someone (with copious free time :P) integrating some existing VM (Java/OpenJDK, V8 JavaScript, etc) into the engine?
Let's not get ahead of ourselves, here. Those VM's are general purpose VM's that have no business being inside of a game like GZDoom. Not to mention the invitation of a MASSIVE amount of code bloat. Java? JavaScript? Any sane programmer is going to balk at this and for good reason. My answer to that is NO, and HELL... NO!

Also as one of the distributors for GZDoom, both for its dev builds and releases, I am vehemently against this idea because of the inevitable bloat in archive size these VM's will bring. To put it simply, even LLVM wasn't that bad!

If you have copious amounts of free time, as you said, why not honor the original request and play with Lemon? If you can put in C++ style class extensions and stuff there, your time would be better spent there. I realize you're trying to take a shortcut but the VM's you proposed are a nightmare in the making. Just ... no!
User avatar
Graf Zahl
Lead GZDoom+Raze Developer
Lead GZDoom+Raze Developer
Posts: 49056
Joined: Sat Jul 19, 2003 10:19 am
Location: Germany

Re: ZScript method call inlining

Post by Graf Zahl »

argv wrote:
Find me someone with some knowledge in how to build compilers and has time to work on it and we may be in business.
Fair enough.

Out of curiosity, are you open to someone (with copious free time :P) integrating some existing VM (Java/OpenJDK, V8 JavaScript, etc) into the engine?
Just one word: No! Or: NO!!! Java has one of the worst interfaces to native code ever cooked up and Javascript as a language is just too volatile. The driving objective behind it was "Anything goes, type what you want and I'll try to make sense of it." which will result in endless hidden bugs that can never be found by analyzing the code.
Aside from these general issues, both are way, wayyyy too bloated and unwieldy for in-game scripting.
I seem to recall you having a problem with making such a VM able to extend and override C++ classes. What was that about?
The issue with general purpose VMs normally is that the script-declared classes implicitly inherit from some internal class the using app has no access to. This cannot work for declaring actors in scripts - for that the script classes must fulfill two requirements:

1. Inheriting from a native class must be possible and the result must be seamless to both native and scripting code.
2. You must be able to override native virtual functions from scripts.

No third-party scripting solution can do that.
Do you have any concrete example where this may become an issue?
A GUI toolkit, in which calling a setter implicitly causes widgets' layout to be recomputed. The only way to enforce the use of a setter is to also have a getter, and calling getters 5000 times per frame wrecks frame rate.
Let's be clear: You should never design a GUI like that. It'd be better to set everything and then call a 'layout' function because otherwise you will end up recalculating the layout multiple times per frame and that will kill your frame rate no matter what if you do it more frequently.
No, but I am expecting that lots of accessors throughout will result in maybe 5k extra calls per *frame* (so, up to 20k/tic) in real-world code. 10k/tic calls increases VM time by 1ms/tic. Ouch.
See my last remark. Your system's design would be inherently flawed. Not only for efficiency but also for running the risk of circular re-layouting. Change one element, and the layouter will change others - which in turn will also change others - and so on. I doubt that any system works like that. Even OSs like iOS which have setters for their widget properties are mostly queuing such actions because doing them on the spot can very, very easily kill performance.
argv
Posts: 184
Joined: Tue Aug 30, 2016 4:47 pm

Re: ZScript method call inlining

Post by argv »

Rachael wrote:Let's not get ahead of ourselves, here. Those VM's are general purpose VM's that have no business being inside of a game like GZDoom. Not to mention the invitation of a MASSIVE amount of code bloat. Java? JavaScript? Any sane programmer is going to balk at this and for good reason. My answer to that is NO, and HELL... NO!
Seems to me that execution speed is of greater concern at run time than code size.

Easy for me to say, though. I'm not the one who has to pay for hosting it.
Also as one of the distributors for GZDoom, both for its dev builds and releases, I am vehemently against this idea because of the inevitable bloat in archive size these VM's will bring. To put it simply, even LLVM wasn't that bad!
Fair enough. There's no easy way around that problem.

Java 9 has a new way to make a stripped-down, application-specific JRE image, but the bare-bones image (just the java.base module, probably enough for game scripting) is still 7 MB (Win64) compressed (7z with “ultra” compression). Pretty impressive for Java, but it'd still double the size of a GZDoom binary distribution archive (3.2.0 x64 compresses down to 7.55 MB with 7z ultra).

On that note, if you're facing a bandwidth squeeze, why not distribute GZDoom in 7z format? The zip archive is 9.08 MB, so you'd save 1.53 MB per Windows x64 download.
If you have copious amounts of free time, as you said, why not honor the original request and play with Lemon?
I don't know what that is.
Graf Zahl wrote:Just one word: No! Or: NO!!! Java has one of the worst interfaces to native code ever cooked up
Huh? JNI? I admit it's been a long time since I integrated a C program (X-Chat, an IRC client) with JNI, but I don't remember it being that bad…
Javascript as a language is just too volatile. The driving objective behind it was "Anything goes, type what you want and I'll try to make sense of it." which will result in endless hidden bugs that can never be found by analyzing the code.
On that, I definitely agree. Dealing with JavaScript is unfortunately an occasional part of my job, and one of my least favorite, because of its chaotic, unpredictable nature.
The issue with general purpose VMs normally is that the script-declared classes implicitly inherit from some internal class the using app has no access to. This cannot work for declaring actors in scripts - for that the script classes must fulfill two requirements:

1. Inheriting from a native class must be possible and the result must be seamless to both native and scripting code.
2. You must be able to override native virtual functions from scripts.

No third-party scripting solution can do that.
SWIG can do that. At least, the website claims that it can.

You can do it by hand, too. For each C++ class that needs to be inherited from and overridable in the VM, make a stub C++ class that overrides all of the superclass virtual methods, to call into the VM:

Code: Select all

class Actor … {
	public:
	virtual void SomeMethod();
}

void Actor::SomeMethod() {
	// do stuff
}

class ScriptedActor : AActor {
	<VM object reference> _vm_obj;
	void _super_SomeMethod();
	public:
	virtual void SomeMethod() override;
}

void ScriptedActor::_super_SomeMethod() {
	Super::SomeMethod();
}

void ScriptedActor::SomeMethod() {
	// call into VM
}
Or, if VM objects are statically typed (like in Java), you could examine each VM subclass of a scripted class, determine which ones are overridden, store the list of overridden methods in a C++ variable somewhere, and adapt IFVIRTUAL to consult that list.
Let's be clear: You should never design a GUI like that. It'd be better to set everything and then call a 'layout' function because otherwise you will end up recalculating the layout multiple times per frame and that will kill your frame rate no matter what if you do it more frequently.
Of course. I have a GUIWidget::RequestLayout method that marks a widget and its ancestors as needing layout, causing the tree's layout to be recomputed next time it's drawn. I'm avoiding doing layout even once per frame, let alone more than once.

Trouble is, that needs to happen whenever one or more layout-relevant fields are changed, and it'd be much safer to just call it from setters than to rely on application code to separately call RequestLayout afterward.
User avatar
Rachael
Posts: 13530
Joined: Tue Jan 13, 2004 1:31 pm
Preferred Pronouns: She/Her
Contact:

Re: ZScript method call inlining

Post by Rachael »

Lemon is the language interpreter that is included with GZDoom. AFAIK it is the front-end to the compiler for ZScript before it gets translated into bytecode.

If you need to fuss around with the ZScript language, itself, you would tweak what gets sent to Lemon and how. I am not sure how much Randi and Graf modified the original library, if at all, but that is the reason why it is included with the GZDoom source.

This is its official home page.

Concerning GZDoom archives and 7zip: I already tried that with QZDoom once. The result was me going toe to toe with one of the laziest and most entitled idiots demanding that I redistribute in .zip format just for them. I'd rather just kzip the GZDoom archives and be done with it, since kzip archives can be opened with any traditional zip program anyway. I could say "get with the times" but that just doesn't go well in the Doom community. Bandwidth is not as much of a concern as space is - space is limited and I would prefer not to waste it needlessly on 80% of a library that never gets used. But it is not even close to the primary reason for rejecting the use of Java, anyhow.
argv
Posts: 184
Joined: Tue Aug 30, 2016 4:47 pm

Re: ZScript method call inlining

Post by argv »

Rachael wrote:But it is not even close to the primary reason for rejecting the use of Java, anyhow.
Your other reasons seem, quite frankly, misplaced. “Bloat” (I'm guessing that means RAM usage) is nowhere near as big a problem as is the execution speed of ZScript. That's why I'm here instead of happily coding a cool mod: I can't code it like it ought to be, because the VM is too slow.

I realize that something like Java would add memory usage (jshell on my machine, configured with a 128 MB max heap size and running some simple benchmarks, uses up to 190 MB in Task Manager before the GC kicks in), but that's a drop in the bucket compared to AAA games these days (Fallout 4 requires 8 GB to run comfortably), and that's not stopping people from playing them.

On the other hand, the Java VM these days is lightning fast. The Java equivalent of the million-adds example I gave, once the JIT compiler is warmed up, completes in 1 to 2 microseconds. The JIT has optimized away not only the method calls but the entire loop, and can perform such optimizations even for dynamically-loaded code.

Milliseconds, in my estimation, mean a hell of a lot more in a game than megabytes. You can install another gigabyte of RAM (seriously, 380 MB was really low for a game 10 years ago, let alone now), but you can't get more than 16ms (or, on a high-fps display, 7ms) to compute and draw each frame.
User avatar
Graf Zahl
Lead GZDoom+Raze Developer
Lead GZDoom+Raze Developer
Posts: 49056
Joined: Sat Jul 19, 2003 10:19 am
Location: Germany

Re: ZScript method call inlining

Post by Graf Zahl »

argv wrote:
Rachael wrote:Let's not get ahead of ourselves, here. Those VM's are general purpose VM's that have no business being inside of a game like GZDoom. Not to mention the invitation of a MASSIVE amount of code bloat. Java? JavaScript? Any sane programmer is going to balk at this and for good reason. My answer to that is NO, and HELL... NO!
Seems to me that execution speed is of greater concern at run time than code size.
There's far more important considerations:

- maintainability comes first.
- usability comes second
- raw execution speed comes a distant third. The fact is, that for none of the traditional use cases any of this matters. Even with scripting heavy mods it rarely becomes an issue. If you want to solve this, a better idea would be to invest time into getting a JIT compiler up and running instead of changing VMs to something unwieldy and unfitting.

The biggest issue with JITing the bytecode is actually how strings got implemented. They are a native type with dedicated registers and specific needs to get set up and taken down. No CPU can do this natively so getting strings JITed will a) bloat the code quite significantly and b) won't speed up these operations at all.

Java 9 has a new way to make a stripped-down, application-specific JRE image, but the bare-bones image (just the java.base module, probably enough for game scripting) is still 7 MB (Win64) compressed (7z with “ultra” compression). Pretty impressive for Java, but it'd still double the size of a GZDoom binary distribution archive (3.2.0 x64 compresses down to 7.55 MB with 7z ultra).
It's about time they did this - but in the end it doesn't solve any of our problem that all the Java classes live in a different world than the C++ classes.
On that note, if you're facing a bandwidth squeeze, why not distribute GZDoom in 7z format? The zip archive is 9.08 MB, so you'd save 1.53 MB per Windows x64 download.
You have no idea how inept people are at using file formats that do not have standard support by the operating system. As nice as it would be the mere fact that a third party decompressor needs to be installed can become a major roadblock for some.
Graf Zahl wrote:Just one word: No! Or: NO!!! Java has one of the worst interfaces to native code ever cooked up
Huh? JNI? I admit it's been a long time since I integrated a C program (X-Chat, an IRC client) with JNI, but I don't remember it being that bad…
[/quote]

Depends on what you consider 'bad'. It's far too verbose and low level. For ZScript it all can be done with a small set of macros - and calling a VM function is only a few lines of code.
SWIG can do that. At least, the website claims that it can.
Too complicated and out of scope. That's just a tool to help cover the deficiencies of other tools.

Or, if VM objects are statically typed (like in Java), you could examine each VM subclass of a scripted class, determine which ones are overridden, store the list of overridden methods in a C++ variable somewhere, and adapt IFVIRTUAL to consult that list.
[/quote]

Yeah, throw even more pointless work at the problem.
Trouble is, that needs to happen whenever one or more layout-relevant fields are changed, and it'd be much safer to just call it from setters than to rely on application code to separately call RequestLayout afterward.
Ok, understood. So effectively what you need are instance variables that can be read directly but not written to, aside from inside the class, correct?
If we boil it down to that the solution will be a lot easier than inlining setters and getters.
User avatar
Graf Zahl
Lead GZDoom+Raze Developer
Lead GZDoom+Raze Developer
Posts: 49056
Joined: Sat Jul 19, 2003 10:19 am
Location: Germany

Re: ZScript method call inlining

Post by Graf Zahl »

argv wrote: On the other hand, the Java VM these days is lightning fast. The Java equivalent of the million-adds example I gave, once the JIT compiler is warmed up, completes in 1 to 2 microseconds. The JIT has optimized away not only the method calls but the entire loop, and can perform such optimizations even for dynamically-loaded code.

Milliseconds, in my estimation, mean a hell of a lot more in a game than megabytes. You can install another gigabyte of RAM (seriously, 380 MB was really low for a game 10 years ago, let alone now), but you can't get more than 16ms (or, on a high-fps display, 7ms) to compute and draw each frame.

What part of "The VM does not work inside the required scope" is unclear to you?
I'm also not sure about the license. Can it even be done legally?
User avatar
Rachael
Posts: 13530
Joined: Tue Jan 13, 2004 1:31 pm
Preferred Pronouns: She/Her
Contact:

Re: ZScript method call inlining

Post by Rachael »

If you think you can make it work - fork it into a source port and prove it. It's very unlikely that we will have *anything* to do with Java, however, and we told you repeatedly we don't.

Also - someone once suggested I do an oversized [No] on a topic we're fed up with discussing. So here it is:
BigNo.png
BigNo.png (1.28 KiB) Viewed 710 times
That's our answer to the Java VM.
Locked

Return to “Closed Feature Suggestions [GZDoom]”