4 ms·
Walter, could you explain why lexing was a bottleneck? That's very surprising to me. You don't re-lex template instantiations do you?
by shasta 13y ago
Walter, could you explain why lexing was a bottleneck? That's very surprising to me. You don't re-lex template instantiations do you?
- WalterBright 13y agoLexing has been a bottleneck in every compiler I've built. The only answer I have is that ASTs are a lot smaller than source code. Templates are stored as ASTs. They are not re-lexed.
- acqq 13y agoDo you consider lexing only "reading the file, finding out if the sequence of characters is the keyword of the literal or the comment" or something more? I admit I lex the source which is already in memory, and there lexing takes less than all other processing (in CPU use). Also in my case the source is always smaller in memory than any AST.
- WalterBright 13y agoLexing is converting the source text into a token stream. If lexing takes relatively smaller times for you, perhaps you have bottlenecks in the later passes?
- shasta 13y agoI'm just having a hard time understanding how you could be having complaints about the compile times if you have a fast lexer and that lexer is a significant percentage of the total time. Can't your lexer handle a million lines of code in a few seconds? How big are these code bases?
- WalterBright 13y agoIt does handle a million lines in a few seconds - it's just that the rest of the compiler's work goes even faster. The top cycle sucker is Lexer::scan(). Here's the source: https://github.com/D-Programming-Language/dmd/blob/master/src/lexer.c https://github.com/D-Programming-Language/dmd/blob/master/sr... See line 440. It's entirely possible I've missed something glaringly obvious - have a go at it and see if you spot anything. Oh, and "complaints" is a relative term. DMD is incredibly fast at compiling compared to other compilers - it's just that I want it to go even faster. Anything less than instantaneous I regard as "needs improvement." When D gets a design win at a company, I'll often ask what put D out in front of the competition. "Compile speed" is usually mentioned. Compile speed has a huge effect on programmer productivity.
- jeremiep 13y agoLooks like the same kind of problem that plagues interpreter loops: that top-level switch statement is causing a branch misprediction on almost every hit. The solution i've seen was to have each opcode handler determine what the next handler would be and directly jump there.
- acqq 13y agoThe problem is that most of the time the main switch of lex isn't entered in the loop, instead it is called from the many different points in the parser (a token is considered, something is done, then there's a call for a next token). The only case I see at the moment where there's loop over the switch is when doing the whitespaces, that can maybe be slightly improved. The whole topic of lexing turning out to be slow is really a thing that should be measured. Really intriguing.
- jeremiep 13y agoIndeed. At the very least, that scan() function could be broken down into smaller parts to be individually profiled.
- nkurz 13y agoGlancing at the source, I think the whitespace parsing could be improved. Since whitespace will rarely if ever (?) be followed by more whitespace, and since most other things are likely to be followed by whitespace, just having both "parse_expecting_whitespace()" and "parse_probably_not_whitespace()" should improve the prediction. Other tokens probably have similar but less strong "preferences". It's possible that a smart enough branch predictor will learn these on its own, but changing things so that each token ends with its own predicted branch would likely be a win. Since the branch predictor uses the address of the branch as the 'key', the general goal is to have a separate branch instruction (if, switch) in each place where the probabilities are different enough to favor a different prediction. A wrong prediction costs about 15 cycles, but a correctly predicted branch costs less than 1, so you can put in quite a lot of these and still come out ahead. Perhaps just ending every token handler with a best guess at what comes next? if (T->ptr[0] == most_likely_next) handle_most_likely_next(T); else scan(T); I don't have the syntax in my head, by I think you can use 'perf' to record mispredicted indirect branches and then display that sorted by caller.
- acqq 13y agoPlease excuse me for even asking, did you propagate your bump-the-pointer greedy allocator to the lexer (that is, what's behind "mem.malloc" in the code you linked to)? And do you have some idea why would lexing otherwise take so much time for you to notice?
- WalterBright 13y agoYes, but the lexer doesn't do too much allocating. I try to avoid allocing in the lexer because of speed issues.