-
Notifications
You must be signed in to change notification settings - Fork 160
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
Made ScriptString store length in data header, use to speed up API functions somewhat #2187
Made ScriptString store length in data header, use to speed up API functions somewhat #2187
Conversation
d694ea5
to
71bb753
Compare
Hmm, so the situation is ambiguous. Used following script to test.
Results, in approximate fps:
Which means that appending a string char by char became about x1.6 slower, but iterating over it x3.6 faster. |
1939a9e
to
08aae0b
Compare
Hmm, so, quick experiment shows Append test rise to ~1400 fps, x2 speed up compared to 3.6.1. This may be a way to go, only have to reorganize the code... EDIT: also have some idea about what to do with invalid utf-8. |
This potentially allows to access any metadata while passing string pointer around the script functions. As a consequence: never wrap external buffer, always allocate your own.
Instead of giving out a pointer to ScriptString internals, have a dedicated ScriptString::Buffer struct that already has a buffer allocated according to ScriptString's rules (with meta header). ScriptString provides a method for creating such buffer, and for creating a new ScriptString object from the Buffer. This has an advantage of one less memory copy compared to passing a const char* into a factory method, which may make some difference when modifying very long strings.
08aae0b
to
e2beb30
Compare
New variant, which reuses known unicode-compatible lengths of substrings (in Append, AppendChar, etc). Results, in approximate fps:
(*) AppendChar test fps jumps too much, perhaps due to lots of memory reallocations, but later settles at around 1500 fps. In the end it seems that appending a character in this test became x2 faster and iterating over string x3.6 faster. I doubt this may get much faster while having immutable strings which have to be reallocated on Append etc. In regards to getting a character by index, something that should be kept in mind, this became slower in Unicode mode, as utf-8 does not allow to jump to an index, it has to parse string char by char to find where char N resides. In script this is complicated by not being able to continue from where the last character was found (when iterating in a loop). This likely may be improved only by introducing iterators of some kind (?). |
Hmm, I did an experiment that saves last found char index and offset in a string's header, and then uses them if the requested index is equal or higher than the saved one. |
Given the above, I think I might have done a mistake when transitioning to Unicode. It could have been beneficial to leave I also wonder how long should the string be on average when this problem becomes noticeable. I'd like to compare performance between ASCII and Unicode game modes, in both cases: before this PR and after this PR, to see roughly how gains by optimizing storing speed and fast access compare between each other. UPDATE: First of all, I found out that there's a mistake in the engine where it deals with some operations, it always uses "unicode" versions of functions, even in ascii mode, which makes operations slower than they could be. That's something that may (should?) be fixed separately. Now, comparing "fast char access" in ascii mode and char access in unicode mode, the gain is following:
This experiment shows that:
|
Or, we might perhaps add a property |
That too, although I've been thinking about backwards compatibility. But then, all the pre-3.6.0 games are run in ASCII mode, so the API compatibility issue is mostly related to games started in 3.6.0+ and having Unicode mode. On the other hand, having iterators is still a must, as there are cases when you need to parse a unicode string too (various speech modules, and others which work with "human text"). So this would be only a part of solution. PS. Updated the previous comment with some test results. |
So, to summarize:
Extra:
|
When you write this, just to see if I understood, this is not "paid" by string in the game data that are like in a Overall I have no idea when you put this if this is significant memory or not - it feels like it isn't, I don't remember seeing so many String objects being hold for a long period in memory, and as many small String objects as globals. In any case I don't have a strong opinion, I went to check if my JSON parser could be sped up by this, but turns I don't have an use-case that really requires the speed from here.
That looks fine too, I think by this you mean something that would be more explicit about this sequential nature, I don't know what the API is but it's fine to get it in ags4. Anyway, this looks ready for merge. |
This is correct, all this is relevant strictly to managed String objects, and only in runtime mem. BTW, on a curious note, the old ags scummvm port (I've been checking it again lately) had every string in the script allocated and passed around as a ScriptString which wrapped a scummvm's own string in itself. This was done for everything including string literals in script mem. Right now it's impossible to do the same for AGS, because of plugin API being able to call script functions, as there's no way for plugins to know what kind of object they received, and how to unwrap it to get text data. |
This speeds up utf-8 string iteration by remembering last requested char index and buffer offset. This seems to be the only solution in the absence of proper ScriptString iterators in script API. Costs 4 extra bytes per managed String object. I intentionally limit these 2 indexes to uint16_t (64k char/byte) to save bit of mem. On average Strings are not that long, in the worst case things may be sped up by splitting into substrings in the script. This change does not impose any dependencies, does not change save format, and may be safely reverted anytime.
2b42ffd
to
1ae98d9
Compare
So, I added a variant of Chars iteration speed up with 4 extra bytes per String (2 bytes for 2 uint16). This limits the speed up fix to 64k bytes, after which iteration becomes slower again. Added comments in code, explaining why this is done and its limitation. Updated fps test result:
Could someone help testing this PR to make sure that String functions work well? |
I can produce a test demo by Saturday using
Essentially, run some unit tests for string correctness, run a performance comparison on my json parser (parse some json many times per frame and measure fps). I don’t have an existing text heavy, regular AGS game right now. |
Hm, I did not know there are unit tests, but been thinking that it would be good to have some "test scripts" repo in ags organization. |
Oh, that would be great. Löve project has a Test Suite as a separate project, this would help us work separately from main AGS so it can start as a simple project - probably a single AGS game, without CI, something simple we will add things, and at some point it can mature to have automation or not, but right now just something we can explore as a smaller project. Alternatively it can work also as just as a place to archive the test games we create for the PRs, maybe both. |
The purpose is to at least keep string iteration relatively fast in ASCII game mode, for backwards compatibility. The proper solution would be to use precalculated string length and use iterator, but that's not a task for 3.6.0 patches. See PR #2187 done for 3.6.1. Also fixed String.Length for plugins in Unicode mode.
Fix #2177 .
Bring ScriptString to the form similar to DynamicArray and UserStruct, where string's meta-info is stored in the same allocated buffer, prepended before the actual text data. This is for general consistency and to be able to retrieve extra info from the received pointer in script functions.
ScriptString's header has two values:
This is because some operations may require either one or another.
EDIT: later added 2 uint16 values which store last requested char index and corresponding char offset. This is done to speed sequential Chars[] iteration up. Because of utf-8 strings the direct access at index is no longer possible, so this seem to be the only possible solution in the absence of a string iterator (this is something left for future ags4 dev). Also see comments in code and in this thread for details.
In all the String's member functions assume that the first argument is the ScriptString, and has this info prepended. Therefore, don't call strlen on arg1, but retrieve precalculated values.
Unfortunately we cannot do the same with the second string arg in functions like String.Append, because we do not know where it came from (it could be a string literal, etc). This cannot be resolved unless we either overhaul how strings are handled in script vm, or provide overloaded methods which accept explicit String argument.
UPDATE
Used following script for a performance test (under spoiler).
After trying couple of variants, the latest fps results look like this:
(*) AppendChar test fps jumps too much, perhaps due to lots of memory reallocations, but later settles at around 1500 fps.
In the end it seems that appending a character in this test became x2 faster and iterating over string x3.6 faster.
UPDATE 2
After adding a iteration speed up fix, the fps results look like: