From 75cffdf92c691b428eb58c67c86baa32c938f6a7 Mon Sep 17 00:00:00 2001
From: Arseny Kapoulkine <arseny.kapoulkine@gmail.com>
Date: Mon, 5 Feb 2018 16:07:39 -0800
Subject: [PATCH] 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).
---
 glslang/MachineIndependent/ShaderLang.cpp | 65 +++++++++++++----------
 1 file changed, 37 insertions(+), 28 deletions(-)

diff --git a/glslang/MachineIndependent/ShaderLang.cpp b/glslang/MachineIndependent/ShaderLang.cpp
index c92d02391..21004fede 100644
--- a/glslang/MachineIndependent/ShaderLang.cpp
+++ b/glslang/MachineIndependent/ShaderLang.cpp
@@ -912,7 +912,7 @@ bool ProcessDeferred(
 class SourceLineSynchronizer {
 public:
     SourceLineSynchronizer(const std::function<int()>& lastSourceIndex,
-                           std::stringstream* output)
+                           std::string* output)
       : getLastSourceIndex(lastSourceIndex), output(output), lastSource(-1), lastLine(0) {}
 //    SourceLineSynchronizer(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
             // from the previous source string (if there is one).
             if (lastSource != -1 || lastLine != 0)
-                *output << std::endl;
+                *output += '\n';
             lastSource = getLastSourceIndex();
             lastLine = -1;
             return true;
@@ -942,7 +942,7 @@ public:
         syncToMostRecentString();
         const bool newLineStarted = lastLine < tokenLine;
         for (; lastLine < tokenLine; ++lastLine) {
-            if (lastLine > 0) *output << std::endl;
+            if (lastLine > 0) *output += '\n';
         }
         return newLineStarted;
     }
@@ -956,8 +956,8 @@ private:
     // A function for getting the index of the last valid source string we've
     // read tokens from.
     const std::function<int()> getLastSourceIndex;
-    // output stream for newlines.
-    std::stringstream* output;
+    // output string for newlines.
+    std::string* output;
     // 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
     // source string starts. -1 means we haven't started processing any source
@@ -988,27 +988,33 @@ struct DoPreprocessing {
         parseContext.setScanner(&input);
         ppContext.setInput(input, versionWillBeError);
 
-        std::stringstream outputStream;
+        std::string outputBuffer;
         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) {
                 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) {
             // SourceNum is the number of the source-string that is being parsed.
             lineSync.syncToLine(curLineNum);
-            outputStream << "#line " << newLineNum;
+            outputBuffer += "#line ";
+            outputBuffer += std::to_string(newLineNum);
             if (hasSource) {
-                outputStream << " ";
+                outputBuffer += ' ';
                 if (sourceName != nullptr) {
-                    outputStream << "\"" << sourceName << "\"";
+                    outputBuffer += '\"';
+                    outputBuffer += sourceName;
+                    outputBuffer += '\"';
                 } else {
-                    outputStream << sourceNum;
+                    outputBuffer += std::to_string(sourceNum);
                 }
             }
             if (parseContext.lineDirectiveShouldSetNextLine()) {
@@ -1016,33 +1022,36 @@ struct DoPreprocessing {
                 // directive. So the new line number for the current line is
                 newLineNum -= 1;
             }
-            outputStream << std::endl;
+            outputBuffer += '\n';
             // And we are at the next line of the #line directive now.
             lineSync.setLineNum(newLineNum + 1);
         });
 
         parseContext.setVersionCallback(
-            [&lineSync, &outputStream](int line, int version, const char* str) {
+            [&lineSync, &outputBuffer](int line, int version, const char* str) {
                 lineSync.syncToLine(line);
-                outputStream << "#version " << version;
+                outputBuffer += "#version ";
+                outputBuffer += std::to_string(version);
                 if (str) {
-                    outputStream << " " << str;
+                    outputBuffer += ' ';
+                    outputBuffer += str;
                 }
             });
 
-        parseContext.setPragmaCallback([&lineSync, &outputStream](
+        parseContext.setPragmaCallback([&lineSync, &outputBuffer](
             int line, const glslang::TVector<glslang::TString>& ops) {
                 lineSync.syncToLine(line);
-                outputStream << "#pragma ";
+                outputBuffer += "#pragma ";
                 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) {
                 lineSync.syncToLine(line);
-                outputStream << "#error " << errorMessage;
+                outputBuffer += "#error ";
+                outputBuffer += errorMessage;
         });
 
         int lastToken = EndOfInput; // lastToken records the last token processed.
@@ -1058,7 +1067,7 @@ struct DoPreprocessing {
                 // Don't emit whitespace onto empty lines.
                 // Copy any whitespace characters at the start of a line
                 // 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,
@@ -1068,13 +1077,13 @@ struct DoPreprocessing {
                 (unNeededSpaceTokens.find((char)token) == std::string::npos) &&
                 (unNeededSpaceTokens.find((char)lastToken) == std::string::npos) &&
                 (noSpaceBeforeTokens.find((char)token) == std::string::npos)) {
-                outputStream << " ";
+                outputBuffer += ' ';
             }
             lastToken = token;
-            outputStream << ppToken.name;
+            outputBuffer += ppToken.name;
         } while (true);
-        outputStream << std::endl;
-        *outputString = outputStream.str();
+        outputBuffer += '\n';
+        *outputString = std::move(outputBuffer);
 
         bool success = true;
         if (parseContext.getNumErrors() > 0) {
-- 
GitLab