Switch LineReader from fgets() to just fread().
authorNico Weber <nicolasweber@gmx.de>
Thu, 10 May 2012 05:20:52 +0000 (22:20 -0700)
committerNico Weber <nicolasweber@gmx.de>
Thu, 10 May 2012 05:39:03 +0000 (22:39 -0700)
fgets() needs to search for a \n in the input, and LineReader's
strstr() did the same work again. By using fread() directly, this
duplicate work can be saved.

(I tried using readline() / fgetln() for the same saving, but those
functions are not standard, and were slower than the code in this CL.)

Results from running build_log_perftest, on OS X:
Before: min 369ms  max 372ms  avg 370.4ms
After:  min 243ms  max 247ms  avg 244.8ms

On Windows:
Before: min 796ms  max 904ms  avg 858.0ms
After:  min 359ms  max 375ms  avg 371.4ms

On Linux:
Before: min 161ms  max 169ms  avg 164.8ms
Before: min 130ms  max 137ms  avg 132.6ms

src/build_log.cc

index 01fb001..5803ada 100644 (file)
@@ -113,24 +113,52 @@ void BuildLog::Close() {
 
 class LineReader {
  public:
-  explicit LineReader(FILE* file) : file_(file) {}
+  explicit LineReader(FILE* file)
+    : file_(file), buf_end_(buf_), line_start_(buf_), line_end_(NULL) {}
 
   // Reads a \n-terminated line from the file passed to the constructor.
   // On return, *line_start points to the beginning of the next line, and
   // *line_end points to the \n at the end of the line. If no newline is seen
   // in a fixed buffer size, *line_end is set to NULL. Returns false on EOF.
   bool ReadLine(char** line_start, char** line_end) {
-    if (!fgets(buf_, sizeof(buf_), file_))
-      return false;
+    if (line_start_ >= buf_end_ || !line_end_) {
+      // Buffer empty, refill.
+      size_t size_read = fread(buf_, 1, sizeof(buf_), file_);
+      if (!size_read)
+        return false;
+      line_start_ = buf_;
+      buf_end_ = buf_ + size_read;
+    } else {
+      // Advance to next line in buffer.
+      line_start_ = line_end_ + 1;
+    }
 
-    *line_start = buf_;
-    *line_end = strchr(buf_, '\n');
+    line_end_ = (char*)memchr(line_start_, '\n', buf_end_ - line_start_);
+    if (!line_end_) {
+      // No newline. Move rest of data to start of buffer, fill rest.
+      size_t already_consumed = line_start_ - buf_;
+      size_t size_rest = (buf_end_ - buf_) - already_consumed;
+      memmove(buf_, line_start_, size_rest);
+
+      size_t read = fread(buf_ + size_rest, 1, sizeof(buf_) - size_rest, file_);
+      buf_end_ = buf_ + size_rest + read;
+      line_start_ = buf_;
+      line_end_ = (char*)memchr(line_start_, '\n', buf_end_ - line_start_);
+    }
+
+    *line_start = line_start_;
+    *line_end = line_end_;
     return true;
   }
 
  private:
   FILE* file_;
   char buf_[256 << 10];
+  char* buf_end_;  // Points one past the last valid byte in |buf_|.
+
+  char* line_start_;
+  // Points at the next \n in buf_ after line_start, or NULL.
+  char* line_end_;
 };
 
 bool BuildLog::Load(const string& path, string* err) {