Preprocessor: Use std::string instead of std::stringstream

std::stringstream has a measurable overhead for preprocessing - it
appears that operator<< does a tiny bit of extra work for appending
chars/strings and also can't be inlined in most cases on VS2015;
additionally, std::endl triggers a stream flush which also adds up.

Replacing this with std::string buffer gets the preprocessing time down
from 180ms to 135ms in one case, making it 1.33x faster.

Note that integer-to-string conversion is using std::to_string; in
theory this could be slower than sprintf or manual conversion, but I
haven't found these cases to affect preprocessing time in practice
(std::to_string would always use the short string buffer for
line/version numbers, and the number of calls is not too significant).
This commit is contained in:
Arseny Kapoulkine 2018-02-05 16:07:39 -08:00
parent 82ead04c39
commit 75cffdf92c

View File

@ -912,7 +912,7 @@ bool ProcessDeferred(
class SourceLineSynchronizer { class SourceLineSynchronizer {
public: public:
SourceLineSynchronizer(const std::function<int()>& lastSourceIndex, SourceLineSynchronizer(const std::function<int()>& lastSourceIndex,
std::stringstream* output) std::string* output)
: getLastSourceIndex(lastSourceIndex), output(output), lastSource(-1), lastLine(0) {} : getLastSourceIndex(lastSourceIndex), output(output), lastSource(-1), lastLine(0) {}
// SourceLineSynchronizer(const SourceLineSynchronizer&) = delete; // SourceLineSynchronizer(const SourceLineSynchronizer&) = delete;
// SourceLineSynchronizer& operator=(const SourceLineSynchronizer&) = delete; // SourceLineSynchronizer& operator=(const SourceLineSynchronizer&) = delete;
@ -927,7 +927,7 @@ public:
// used. We also need to output a newline to separate the output // used. We also need to output a newline to separate the output
// from the previous source string (if there is one). // from the previous source string (if there is one).
if (lastSource != -1 || lastLine != 0) if (lastSource != -1 || lastLine != 0)
*output << std::endl; *output += '\n';
lastSource = getLastSourceIndex(); lastSource = getLastSourceIndex();
lastLine = -1; lastLine = -1;
return true; return true;
@ -942,7 +942,7 @@ public:
syncToMostRecentString(); syncToMostRecentString();
const bool newLineStarted = lastLine < tokenLine; const bool newLineStarted = lastLine < tokenLine;
for (; lastLine < tokenLine; ++lastLine) { for (; lastLine < tokenLine; ++lastLine) {
if (lastLine > 0) *output << std::endl; if (lastLine > 0) *output += '\n';
} }
return newLineStarted; return newLineStarted;
} }
@ -956,8 +956,8 @@ private:
// A function for getting the index of the last valid source string we've // A function for getting the index of the last valid source string we've
// read tokens from. // read tokens from.
const std::function<int()> getLastSourceIndex; const std::function<int()> getLastSourceIndex;
// output stream for newlines. // output string for newlines.
std::stringstream* output; std::string* output;
// lastSource is the source string index (starting from 0) of the last token // lastSource is the source string index (starting from 0) of the last token
// processed. It is tracked in order for newlines to be inserted when a new // processed. It is tracked in order for newlines to be inserted when a new
// source string starts. -1 means we haven't started processing any source // source string starts. -1 means we haven't started processing any source
@ -988,27 +988,33 @@ struct DoPreprocessing {
parseContext.setScanner(&input); parseContext.setScanner(&input);
ppContext.setInput(input, versionWillBeError); ppContext.setInput(input, versionWillBeError);
std::stringstream outputStream; std::string outputBuffer;
SourceLineSynchronizer lineSync( SourceLineSynchronizer lineSync(
std::bind(&TInputScanner::getLastValidSourceIndex, &input), &outputStream); std::bind(&TInputScanner::getLastValidSourceIndex, &input), &outputBuffer);
parseContext.setExtensionCallback([&lineSync, &outputStream]( parseContext.setExtensionCallback([&lineSync, &outputBuffer](
int line, const char* extension, const char* behavior) { int line, const char* extension, const char* behavior) {
lineSync.syncToLine(line); lineSync.syncToLine(line);
outputStream << "#extension " << extension << " : " << behavior; outputBuffer += "#extension ";
outputBuffer += extension;
outputBuffer += " : ";
outputBuffer += behavior;
}); });
parseContext.setLineCallback([&lineSync, &outputStream, &parseContext]( parseContext.setLineCallback([&lineSync, &outputBuffer, &parseContext](
int curLineNum, int newLineNum, bool hasSource, int sourceNum, const char* sourceName) { int curLineNum, int newLineNum, bool hasSource, int sourceNum, const char* sourceName) {
// SourceNum is the number of the source-string that is being parsed. // SourceNum is the number of the source-string that is being parsed.
lineSync.syncToLine(curLineNum); lineSync.syncToLine(curLineNum);
outputStream << "#line " << newLineNum; outputBuffer += "#line ";
outputBuffer += std::to_string(newLineNum);
if (hasSource) { if (hasSource) {
outputStream << " "; outputBuffer += ' ';
if (sourceName != nullptr) { if (sourceName != nullptr) {
outputStream << "\"" << sourceName << "\""; outputBuffer += '\"';
outputBuffer += sourceName;
outputBuffer += '\"';
} else { } else {
outputStream << sourceNum; outputBuffer += std::to_string(sourceNum);
} }
} }
if (parseContext.lineDirectiveShouldSetNextLine()) { if (parseContext.lineDirectiveShouldSetNextLine()) {
@ -1016,33 +1022,36 @@ struct DoPreprocessing {
// directive. So the new line number for the current line is // directive. So the new line number for the current line is
newLineNum -= 1; newLineNum -= 1;
} }
outputStream << std::endl; outputBuffer += '\n';
// And we are at the next line of the #line directive now. // And we are at the next line of the #line directive now.
lineSync.setLineNum(newLineNum + 1); lineSync.setLineNum(newLineNum + 1);
}); });
parseContext.setVersionCallback( parseContext.setVersionCallback(
[&lineSync, &outputStream](int line, int version, const char* str) { [&lineSync, &outputBuffer](int line, int version, const char* str) {
lineSync.syncToLine(line); lineSync.syncToLine(line);
outputStream << "#version " << version; outputBuffer += "#version ";
outputBuffer += std::to_string(version);
if (str) { if (str) {
outputStream << " " << str; outputBuffer += ' ';
outputBuffer += str;
} }
}); });
parseContext.setPragmaCallback([&lineSync, &outputStream]( parseContext.setPragmaCallback([&lineSync, &outputBuffer](
int line, const glslang::TVector<glslang::TString>& ops) { int line, const glslang::TVector<glslang::TString>& ops) {
lineSync.syncToLine(line); lineSync.syncToLine(line);
outputStream << "#pragma "; outputBuffer += "#pragma ";
for(size_t i = 0; i < ops.size(); ++i) { for(size_t i = 0; i < ops.size(); ++i) {
outputStream << ops[i]; outputBuffer += ops[i].c_str();
} }
}); });
parseContext.setErrorCallback([&lineSync, &outputStream]( parseContext.setErrorCallback([&lineSync, &outputBuffer](
int line, const char* errorMessage) { int line, const char* errorMessage) {
lineSync.syncToLine(line); lineSync.syncToLine(line);
outputStream << "#error " << errorMessage; outputBuffer += "#error ";
outputBuffer += errorMessage;
}); });
int lastToken = EndOfInput; // lastToken records the last token processed. int lastToken = EndOfInput; // lastToken records the last token processed.
@ -1058,7 +1067,7 @@ struct DoPreprocessing {
// Don't emit whitespace onto empty lines. // Don't emit whitespace onto empty lines.
// Copy any whitespace characters at the start of a line // Copy any whitespace characters at the start of a line
// from the input to the output. // from the input to the output.
outputStream << std::string(ppToken.loc.column - 1, ' '); outputBuffer += std::string(ppToken.loc.column - 1, ' ');
} }
// Output a space in between tokens, but not at the start of a line, // Output a space in between tokens, but not at the start of a line,
@ -1068,13 +1077,13 @@ struct DoPreprocessing {
(unNeededSpaceTokens.find((char)token) == std::string::npos) && (unNeededSpaceTokens.find((char)token) == std::string::npos) &&
(unNeededSpaceTokens.find((char)lastToken) == std::string::npos) && (unNeededSpaceTokens.find((char)lastToken) == std::string::npos) &&
(noSpaceBeforeTokens.find((char)token) == std::string::npos)) { (noSpaceBeforeTokens.find((char)token) == std::string::npos)) {
outputStream << " "; outputBuffer += ' ';
} }
lastToken = token; lastToken = token;
outputStream << ppToken.name; outputBuffer += ppToken.name;
} while (true); } while (true);
outputStream << std::endl; outputBuffer += '\n';
*outputString = outputStream.str(); *outputString = std::move(outputBuffer);
bool success = true; bool success = true;
if (parseContext.getNumErrors() > 0) { if (parseContext.getNumErrors() > 0) {