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

python: optimize skipEverything() #383

Merged
merged 1 commit into from
Jun 24, 2015

Conversation

techee
Copy link
Contributor

@techee techee commented Jun 18, 2015

Most of the time there's no start of a string which means all the 10
strcmp()s are done for every character of the input. This is very expensive:
before this patch this function alone takes 55% of the parser time.
When comparing by character (and avoiding further comparison if the first
character doesn't match), this function takes only 11% of the parser time
so the performance of the parser nearly doubles.

@techee
Copy link
Contributor Author

techee commented Jun 18, 2015

Actually the performance exactly doubles:

n: old time
m: new time

before the change the split between the function and the rest is:
0.55 * n + 0.45 * n

after optimizing it:
0.11 * m + 0.89 * m

The rest takes the same amount of time before and after so

0.89 * m = 0.45 * n

so

n/m = 2

End of secondary school math :-).

@masatake
Copy link
Member

Great!

Could you tell me how do you find the hot spot?
Can I ask you to write it to somewhere in docs/tips.rst?

@ffes
Copy link
Member

ffes commented Jun 19, 2015

I think the old code is better readable because of the literal strings, but the improvement is too big not to merge this. So could you add one or two line of comment (or extend the existing one above the block) to clarify a bit, including the strings it is looking for?

And can you add a test case to the Units directory for this?

match = 1;
cp += 1;
}
else if (*cp != 'r' && *cp != 'R' && (c1 == 'r' || c1 == 'R'))
Copy link
Member

Choose a reason for hiding this comment

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

I'd rather move this before and simply advance cp (well, compute the offset rather), and then test for ' and ", as this check is the same in both paths.

Something like this

if (!match && (*cp == 'u' || *cp == 'r' || *cp == 'b' ||
               *cp == 'U' || *cp == 'R' || *cp == 'B'))
{
    unsigned int i = 1;

    if (*cp != 'r' && *cp != 'R' && (cp[i] == 'r' || cp[i] == 'R'))
        i++;
    if (cp[i] == '\'' || cp[i] == '"')
    {
        match = 1;
        cp += i;
    }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@b4n Thanks, it looks nicer, I'll use this.

@b4n
Copy link
Member

b4n commented Jun 19, 2015

LGTM

@techee
Copy link
Contributor Author

techee commented Jun 19, 2015

@masatake I've just written a geany wiki page describing the profiling method I use here (thanks for motivating me to finally do that, I've been planning to write something for a long time):

https://wiki.geany.org/howtos/profiling/gperftools

The picture there is from this very issue. Actually I found this issue in Geany when I noticed the Python parser was about twice as slow as the C parser. For ctags the easiest way to test will be the first described method (without the signals).

I think this issue is rather rare in the parsers - the parsers that use tokens shouldn't suffer from something like this because the token is built up by reading character by character and no multiple comparisons like that happen. In general the most performance-critical part of the parsers is the input-skipping code because this is what runs most of the time (function/variable/etc. declarations/definitions take a small part of sources and the rest like function bodies has to be skipped).

@ffes I agree it's a bit harder to read - I'll add some comments as you suggest and will try to come up with some test (have to check first if there isn't some test already testing this).

@masatake
Copy link
Member

@techee, than you. I will put the link to the page in docs/tips.rst.

Most of the time there's no start of a string which means all the 10
strcmp()s are done for every character of the input. This is very expensive:
before this patch this function alone takes 55% of the parser time.
When comparing by character (and avoiding further comparison if the first
character doesn't match), this function takes only 11% of the parser time
so the performance of the parser nearly doubles.

In addition check for the "rb" prefix which is possible in Python 3.
@techee techee force-pushed the python_optimize branch from 40a832b to 98e2521 Compare June 20, 2015 09:41
@techee
Copy link
Contributor Author

techee commented Jun 20, 2015

OK, I have noticed that in the original code the "rb" Python 3 prefix is missing

https://docs.python.org/3.5/reference/lexical_analysis.html#string-and-bytes-literals

so I added it (which complicates things a little more).

About the test - I actually don't know how to test it. After looking at the Python specification for some time and the way the parser works I think even if something like rb"whatever" was parsed as an identifier "rb" and string "whatever", the parser would work alright. But it's of course possible I missed some special case.

@b4n
Copy link
Member

b4n commented Jun 20, 2015

About the test - I actually don't know how to test it. After looking at the Python specification for some time and the way the parser works I think even if something like rb"whatever" was parsed as an identifier "rb" and string "whatever", the parser would work alright. But it's of course possible I missed some special case.

Indeed. The only case I could imagine breaking would be if such a string appears somewhere it would be mistaken for an identifier, but I can't think of any valid code doing this.

So yeah, we could add a test including all kind of strings and that would probably be good, but I doubt it would really test for much.

So IMO the fact no test breaks is basically good enough.

@masatake
Copy link
Member

Ready to merge?

@techee
Copy link
Contributor Author

techee commented Jun 22, 2015

@masatake From my side yes (someone should review the updated patch though).

masatake added a commit that referenced this pull request Jun 24, 2015
python: optimize skipEverything()
@masatake masatake merged commit 61ac9e7 into universal-ctags:master Jun 24, 2015
@masatake
Copy link
Member

Thank you.

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.

4 participants