Skip to content

Commit 2498c57

Browse files
committed
Optimize FileReadStream and BasicIStreamWrapper.
On (my) linux, perftest reports: - ~40% gain for FileReadStream (Take() loop), - ~10% gain for ReaderParse_DummyHandler_FileReadStream. With the same logic applied to BasicIStreamWrapper, which thus can now also be created with a user buffer, performances align with those of FileReadStream (same buffer size). The "unbuffered" versions (added for FileReadStream) work solely with the internal peekBuffer (Ch[4]) and are measured in perftest. When performances don't matter much, they can avoid the use of large stack/heap buffers.
1 parent 30d92a6 commit 2498c57

File tree

3 files changed

+196
-57
lines changed

3 files changed

+196
-57
lines changed

include/rapidjson/filereadstream.h

Lines changed: 53 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
#include "stream.h"
1919
#include <cstdio>
20+
#include <cstring>
2021

2122
#ifdef __clang__
2223
RAPIDJSON_DIAG_PUSH
@@ -35,21 +36,42 @@ class FileReadStream {
3536
public:
3637
typedef char Ch; //!< Character type (byte).
3738

39+
//! Constructor.
40+
/*!
41+
\param fp File pointer opened for read.
42+
*/
43+
FileReadStream(std::FILE* fp) : fp_(fp), buffer_(peekBuffer_), size_(sizeof(peekBuffer_) / sizeof(Ch)), pos_(), len_(), count_()
44+
{
45+
RAPIDJSON_ASSERT(fp_ != 0);
46+
}
47+
3848
//! Constructor.
3949
/*!
4050
\param fp File pointer opened for read.
4151
\param buffer user-supplied buffer.
4252
\param bufferSize size of buffer in bytes. Must >=4 bytes.
4353
*/
44-
FileReadStream(std::FILE* fp, char* buffer, size_t bufferSize) : fp_(fp), buffer_(buffer), bufferSize_(bufferSize), bufferLast_(0), current_(buffer_), readCount_(0), count_(0), eof_(false) {
45-
RAPIDJSON_ASSERT(fp_ != 0);
46-
RAPIDJSON_ASSERT(bufferSize >= 4);
47-
Read();
54+
FileReadStream(std::FILE* fp, Ch *buffer, size_t size) : fp_(fp), buffer_(buffer), size_(size), pos_(), len_(), count_() {
55+
RAPIDJSON_ASSERT(fp_ != 0 && buffer_ != 0 && size_ > 0);
56+
if (RAPIDJSON_UNLIKELY(size_ < sizeof(peekBuffer_) / sizeof(Ch))) {
57+
size_ = sizeof(peekBuffer_) / sizeof(Ch);
58+
buffer_ = peekBuffer_;
59+
}
4860
}
4961

50-
Ch Peek() const { return *current_; }
51-
Ch Take() { Ch c = *current_; Read(); return c; }
52-
size_t Tell() const { return count_ + static_cast<size_t>(current_ - buffer_); }
62+
Ch Peek() const {
63+
if (RAPIDJSON_UNLIKELY(pos_ == len_) && !Read())
64+
return static_cast<Ch>('\0');
65+
return buffer_[pos_];
66+
}
67+
68+
Ch Take() {
69+
if (RAPIDJSON_UNLIKELY(pos_ == len_) && !Read())
70+
return static_cast<Ch>('\0');
71+
return buffer_[pos_++];
72+
}
73+
74+
size_t Tell() const { return count_ + pos_; }
5375

5476
// Not implemented
5577
void Put(Ch) { RAPIDJSON_ASSERT(false); }
@@ -59,35 +81,36 @@ class FileReadStream {
5981

6082
// For encoding detection only.
6183
const Ch* Peek4() const {
62-
return (current_ + 4 <= bufferLast_) ? current_ : 0;
84+
if (len_ - pos_ < 4) {
85+
if (pos_) {
86+
len_ -= pos_;
87+
std::memmove(buffer_, buffer_ + pos_, len_);
88+
count_ += pos_;
89+
pos_ = 0;
90+
}
91+
len_ += std::fread(buffer_ + len_, sizeof(Ch), size_ - len_, fp_);
92+
if (len_ < 4)
93+
return 0;
94+
}
95+
return &buffer_[pos_];
6396
}
6497

6598
private:
66-
void Read() {
67-
if (current_ < bufferLast_)
68-
++current_;
69-
else if (!eof_) {
70-
count_ += readCount_;
71-
readCount_ = std::fread(buffer_, 1, bufferSize_, fp_);
72-
bufferLast_ = buffer_ + readCount_ - 1;
73-
current_ = buffer_;
74-
75-
if (readCount_ < bufferSize_) {
76-
buffer_[readCount_] = '\0';
77-
++bufferLast_;
78-
eof_ = true;
79-
}
80-
}
99+
FileReadStream();
100+
FileReadStream(const FileReadStream&);
101+
FileReadStream& operator=(const FileReadStream&);
102+
103+
size_t Read() const {
104+
count_ += pos_;
105+
pos_ = 0;
106+
len_ = std::fread(buffer_, sizeof(Ch), size_, fp_);
107+
return len_;
81108
}
82109

83110
std::FILE* fp_;
84-
Ch *buffer_;
85-
size_t bufferSize_;
86-
Ch *bufferLast_;
87-
Ch *current_;
88-
size_t readCount_;
89-
size_t count_; //!< Number of characters read
90-
bool eof_;
111+
Ch peekBuffer_[4], *buffer_;
112+
size_t size_;
113+
mutable size_t pos_, len_, count_;
91114
};
92115

93116
RAPIDJSON_NAMESPACE_END

include/rapidjson/istreamwrapper.h

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
#include "stream.h"
1919
#include <iosfwd>
20+
#include <cstring>
2021

2122
#ifdef __clang__
2223
RAPIDJSON_DIAG_PUSH
@@ -48,26 +49,33 @@ template <typename StreamType>
4849
class BasicIStreamWrapper {
4950
public:
5051
typedef typename StreamType::char_type Ch;
51-
BasicIStreamWrapper(StreamType& stream) : stream_(stream), count_(), peekBuffer_() {}
5252

53-
Ch Peek() const {
54-
typename StreamType::int_type c = stream_.peek();
55-
return RAPIDJSON_LIKELY(c != StreamType::traits_type::eof()) ? static_cast<Ch>(c) : static_cast<Ch>('\0');
56-
}
53+
BasicIStreamWrapper(StreamType& stream) : stream_(stream), buffer_(peekBuffer_), size_(sizeof(peekBuffer_) / sizeof(Ch)), pos_(), len_(), count_() {}
5754

58-
Ch Take() {
59-
typename StreamType::int_type c = stream_.get();
60-
if (RAPIDJSON_LIKELY(c != StreamType::traits_type::eof())) {
61-
count_++;
62-
return static_cast<Ch>(c);
55+
BasicIStreamWrapper(StreamType& stream, Ch *buffer, size_t size) : stream_(stream), buffer_(buffer), size_(size), pos_(), len_(), count_() {
56+
RAPIDJSON_ASSERT(buffer_ != 0 && static_cast<std::streamsize>(size_) > 0);
57+
if (RAPIDJSON_UNLIKELY(size_ < sizeof(peekBuffer_) / sizeof(Ch))) {
58+
size_ = sizeof(peekBuffer_) / sizeof(Ch);
59+
buffer_ = peekBuffer_;
6360
}
64-
else
65-
return '\0';
61+
}
62+
63+
Ch Peek() const {
64+
if (RAPIDJSON_UNLIKELY(pos_ == len_) && !Read())
65+
return static_cast<Ch>('\0');
66+
return buffer_[pos_];
67+
}
68+
69+
Ch Take() {
70+
if (RAPIDJSON_UNLIKELY(pos_ == len_) && !Read())
71+
return static_cast<Ch>('\0');
72+
return buffer_[pos_++];
6673
}
6774

6875
// tellg() may return -1 when failed. So we count by ourself.
69-
size_t Tell() const { return count_; }
76+
size_t Tell() const { return count_ + pos_; }
7077

78+
// Not implemented
7179
Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; }
7280
void Put(Ch) { RAPIDJSON_ASSERT(false); }
7381
void Flush() { RAPIDJSON_ASSERT(false); }
@@ -76,29 +84,42 @@ class BasicIStreamWrapper {
7684
// For encoding detection only.
7785
const Ch* Peek4() const {
7886
RAPIDJSON_ASSERT(sizeof(Ch) == 1); // Only usable for byte stream.
79-
int i;
80-
bool hasError = false;
81-
for (i = 0; i < 4; ++i) {
82-
typename StreamType::int_type c = stream_.get();
83-
if (c == StreamType::traits_type::eof()) {
84-
hasError = true;
85-
stream_.clear();
86-
break;
87+
if (len_ - pos_ < 4) {
88+
if (pos_) {
89+
len_ -= pos_;
90+
std::memmove(buffer_, buffer_ + pos_, len_);
91+
count_ += pos_;
92+
pos_ = 0;
93+
}
94+
if (!stream_.read(buffer_ + len_, static_cast<std::streamsize>(size_ - len_))) {
95+
len_ += static_cast<size_t>(stream_.gcount());
96+
if (len_ < 4)
97+
return 0;
8798
}
88-
peekBuffer_[i] = static_cast<Ch>(c);
99+
else
100+
len_ = size_;
89101
}
90-
for (--i; i >= 0; --i)
91-
stream_.putback(peekBuffer_[i]);
92-
return !hasError ? peekBuffer_ : 0;
102+
return &buffer_[pos_];
93103
}
94104

95105
private:
96106
BasicIStreamWrapper(const BasicIStreamWrapper&);
97107
BasicIStreamWrapper& operator=(const BasicIStreamWrapper&);
98108

109+
size_t Read() const {
110+
count_ += pos_;
111+
pos_ = 0;
112+
if (!stream_.read(buffer_, static_cast<std::streamsize>(size_)))
113+
len_ = static_cast<size_t>(stream_.gcount());
114+
else
115+
len_ = size_;
116+
return len_;
117+
}
118+
99119
StreamType& stream_;
100-
size_t count_; //!< Number of characters read. Note:
101-
mutable Ch peekBuffer_[4];
120+
Ch peekBuffer_[4], *buffer_;
121+
size_t size_;
122+
mutable size_t pos_, len_, count_;
102123
};
103124

104125
typedef BasicIStreamWrapper<std::istream> IStreamWrapper;

test/perftest/rapidjsontest.cpp

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@
2121
#include "rapidjson/prettywriter.h"
2222
#include "rapidjson/stringbuffer.h"
2323
#include "rapidjson/filereadstream.h"
24+
#include "rapidjson/istreamwrapper.h"
2425
#include "rapidjson/encodedstream.h"
2526
#include "rapidjson/memorystream.h"
2627

28+
#include <fstream>
29+
2730
#ifdef RAPIDJSON_SSE2
2831
#define SIMD_SUFFIX(name) name##_SSE2
2932
#elif defined(RAPIDJSON_SSE42)
@@ -451,6 +454,16 @@ TEST_F(RapidJson, FileReadStream) {
451454
}
452455
}
453456

457+
TEST_F(RapidJson, FileReadStream_Unbuffered) {
458+
for (size_t i = 0; i < kTrialCount; i++) {
459+
FILE *fp = fopen(filename_, "rb");
460+
FileReadStream s(fp);
461+
while (s.Take() != '\0')
462+
;
463+
fclose(fp);
464+
}
465+
}
466+
454467
TEST_F(RapidJson, SIMD_SUFFIX(ReaderParse_DummyHandler_FileReadStream)) {
455468
for (size_t i = 0; i < kTrialCount; i++) {
456469
FILE *fp = fopen(filename_, "rb");
@@ -463,6 +476,88 @@ TEST_F(RapidJson, SIMD_SUFFIX(ReaderParse_DummyHandler_FileReadStream)) {
463476
}
464477
}
465478

479+
TEST_F(RapidJson, SIMD_SUFFIX(ReaderParse_DummyHandler_FileReadStream_Unbuffered)) {
480+
for (size_t i = 0; i < kTrialCount; i++) {
481+
FILE *fp = fopen(filename_, "rb");
482+
FileReadStream s(fp);
483+
BaseReaderHandler<> h;
484+
Reader reader;
485+
reader.Parse(s, h);
486+
fclose(fp);
487+
}
488+
}
489+
490+
TEST_F(RapidJson, IStreamWrapper) {
491+
for (size_t i = 0; i < kTrialCount; i++) {
492+
std::ifstream is(filename_);
493+
char buffer[65536];
494+
IStreamWrapper isw(is, buffer, sizeof(buffer));
495+
while (isw.Take() != '\0')
496+
;
497+
is.close();
498+
}
499+
}
500+
501+
TEST_F(RapidJson, IStreamWrapper_Unbuffered) {
502+
for (size_t i = 0; i < kTrialCount; i++) {
503+
std::ifstream is(filename_);
504+
IStreamWrapper isw(is);
505+
while (isw.Take() != '\0')
506+
;
507+
is.close();
508+
}
509+
}
510+
511+
TEST_F(RapidJson, IStreamWrapper_Setbuffered) {
512+
for (size_t i = 0; i < kTrialCount; i++) {
513+
std::ifstream is;
514+
char buffer[65536];
515+
is.rdbuf()->pubsetbuf(buffer, sizeof(buffer));
516+
is.open(filename_);
517+
IStreamWrapper isw(is);
518+
while (isw.Take() != '\0')
519+
;
520+
is.close();
521+
}
522+
}
523+
524+
TEST_F(RapidJson, SIMD_SUFFIX(ReaderParse_DummyHandler_IStreamWrapper)) {
525+
for (size_t i = 0; i < kTrialCount; i++) {
526+
std::ifstream is(filename_);
527+
char buffer[65536];
528+
IStreamWrapper isw(is, buffer, sizeof(buffer));
529+
BaseReaderHandler<> h;
530+
Reader reader;
531+
reader.Parse(isw, h);
532+
is.close();
533+
}
534+
}
535+
536+
TEST_F(RapidJson, SIMD_SUFFIX(ReaderParse_DummyHandler_IStreamWrapper_Unbuffered)) {
537+
for (size_t i = 0; i < kTrialCount; i++) {
538+
std::ifstream is(filename_);
539+
IStreamWrapper isw(is);
540+
BaseReaderHandler<> h;
541+
Reader reader;
542+
reader.Parse(isw, h);
543+
is.close();
544+
}
545+
}
546+
547+
TEST_F(RapidJson, SIMD_SUFFIX(ReaderParse_DummyHandler_IStreamWrapper_Setbuffered)) {
548+
for (size_t i = 0; i < kTrialCount; i++) {
549+
std::ifstream is;
550+
char buffer[65536];
551+
is.rdbuf()->pubsetbuf(buffer, sizeof(buffer));
552+
is.open(filename_);
553+
IStreamWrapper isw(is);
554+
BaseReaderHandler<> h;
555+
Reader reader;
556+
reader.Parse(isw, h);
557+
is.close();
558+
}
559+
}
560+
466561
TEST_F(RapidJson, StringBuffer) {
467562
StringBuffer sb;
468563
for (int i = 0; i < 32 * 1024 * 1024; i++)

0 commit comments

Comments
 (0)