Skip to content

Commit bfd15ff

Browse files
Double-buffering for JSON async stream (#1571)
Decreases time needed to process single JSON, and reduces memory footprint size. Relates-To: DATASDK-57 Signed-off-by: Andrey Kashcheev <[email protected]>
1 parent 60d9454 commit bfd15ff

File tree

4 files changed

+52
-37
lines changed

4 files changed

+52
-37
lines changed

olp-cpp-sdk-dataservice-read/src/repositories/AsyncJsonStream.cpp

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2023 HERE Europe B.V.
2+
* Copyright (C) 2023-2025 HERE Europe B.V.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,51 +19,57 @@
1919

2020
#include "AsyncJsonStream.h"
2121

22-
#include <cstring>
23-
2422
namespace olp {
2523
namespace dataservice {
2624
namespace read {
2725
namespace repository {
2826

29-
RapidJsonByteStream::Ch RapidJsonByteStream::Peek() const {
30-
std::unique_lock<std::mutex> lock(mutex_);
31-
cv_.wait(lock, [=]() { return !Empty(); });
32-
return buffer_[count_];
27+
RapidJsonByteStream::Ch RapidJsonByteStream::Peek() {
28+
if (ReadEmpty()) {
29+
SwapBuffers();
30+
}
31+
return read_buffer_[count_];
3332
}
3433

3534
RapidJsonByteStream::Ch RapidJsonByteStream::Take() {
36-
std::unique_lock<std::mutex> lock(mutex_);
37-
cv_.wait(lock, [=]() { return !Empty(); });
38-
return buffer_[count_++];
35+
if (ReadEmpty()) {
36+
SwapBuffers();
37+
}
38+
full_count_++;
39+
return read_buffer_[count_++];
3940
}
4041

41-
size_t RapidJsonByteStream::Tell() const { return count_; }
42+
size_t RapidJsonByteStream::Tell() const { return full_count_; }
4243

4344
// Not implemented
4445
char* RapidJsonByteStream::PutBegin() { return 0; }
4546
void RapidJsonByteStream::Put(char) {}
4647
void RapidJsonByteStream::Flush() {}
4748
size_t RapidJsonByteStream::PutEnd(char*) { return 0; }
4849

49-
bool RapidJsonByteStream::Empty() const { return count_ == buffer_.size(); }
50+
bool RapidJsonByteStream::ReadEmpty() const {
51+
return count_ == read_buffer_.size();
52+
}
53+
bool RapidJsonByteStream::WriteEmpty() const { return write_buffer_.empty(); }
5054

5155
void RapidJsonByteStream::AppendContent(const char* content, size_t length) {
5256
std::unique_lock<std::mutex> lock(mutex_);
5357

54-
if (Empty()) {
55-
buffer_.resize(length);
56-
std::memcpy(buffer_.data(), content, length);
57-
count_ = 0;
58-
} else {
59-
const auto buffer_size = buffer_.size();
60-
buffer_.resize(buffer_size + length);
61-
std::memcpy(buffer_.data() + buffer_size, content, length);
62-
}
58+
const auto buffer_size = write_buffer_.size();
59+
write_buffer_.reserve(buffer_size + length);
60+
write_buffer_.insert(write_buffer_.end(), content, content + length);
6361

6462
cv_.notify_one();
6563
}
6664

65+
void RapidJsonByteStream::SwapBuffers() {
66+
std::unique_lock<std::mutex> lock(mutex_);
67+
cv_.wait(lock, [&]() { return !WriteEmpty(); });
68+
std::swap(read_buffer_, write_buffer_);
69+
write_buffer_.clear();
70+
count_ = 0;
71+
}
72+
6773
AsyncJsonStream::AsyncJsonStream()
6874
: current_stream_(std::make_shared<RapidJsonByteStream>()),
6975
closed_{false} {}
@@ -97,7 +103,7 @@ void AsyncJsonStream::CloseStream(boost::optional<client::ApiError> error) {
97103
return;
98104
}
99105
current_stream_->AppendContent("\0", 1);
100-
error_ = error;
106+
error_ = std::move(error);
101107
closed_ = true;
102108
}
103109

olp-cpp-sdk-dataservice-read/src/repositories/AsyncJsonStream.h

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2023 HERE Europe B.V.
2+
* Copyright (C) 2023-2025 HERE Europe B.V.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -32,13 +32,13 @@ namespace dataservice {
3232
namespace read {
3333
namespace repository {
3434

35-
// Json byte stream class. Implements rapidjson input stream concept.
35+
/// Json byte stream class. Implements rapidjson input stream concept.
3636
class RapidJsonByteStream {
3737
public:
3838
typedef char Ch;
3939

4040
/// Read the current character from stream without moving the read cursor.
41-
Ch Peek() const;
41+
Ch Peek();
4242

4343
/// Read the current character from stream and moving the read cursor to next
4444
/// character.
@@ -47,19 +47,24 @@ class RapidJsonByteStream {
4747
/// Get the current read cursor.
4848
size_t Tell() const;
4949

50-
// Not needed for reading.
50+
/// Not needed for reading.
5151
char* PutBegin();
5252
void Put(char);
5353
void Flush();
5454
size_t PutEnd(char*);
5555

56-
bool Empty() const;
56+
bool ReadEmpty() const;
57+
bool WriteEmpty() const;
5758

5859
void AppendContent(const char* content, size_t length);
5960

6061
private:
62+
void SwapBuffers();
63+
6164
mutable std::mutex mutex_;
62-
std::vector<char> buffer_; // Current buffer
65+
std::vector<char> read_buffer_; // Current buffer
66+
std::vector<char> write_buffer_; // Current buffer
67+
size_t full_count_{0}; // Bytes read from the buffer
6368
size_t count_{0}; // Bytes read from the buffer
6469
mutable std::condition_variable cv_; // Condition for next portion of content
6570
};

olp-cpp-sdk-dataservice-read/tests/AsyncJsonStreamTest.cpp

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2023 HERE Europe B.V.
2+
* Copyright (C) 2023-2025 HERE Europe B.V.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -47,16 +47,18 @@ TEST(AsyncJsonStreamTest, NormalFlow) {
4747

4848
EXPECT_EQ(current_stream->Peek(), '\0');
4949
EXPECT_EQ(current_stream->Take(), '\0');
50-
EXPECT_TRUE(current_stream->Empty());
50+
EXPECT_TRUE(current_stream->ReadEmpty());
5151

5252
EXPECT_EQ(new_current_stream->Peek(), '2');
5353
EXPECT_EQ(new_current_stream->Take(), '2');
5454
EXPECT_EQ(new_current_stream->Take(), '3');
5555
EXPECT_EQ(new_current_stream->Take(), '4');
56-
EXPECT_TRUE(new_current_stream->Empty());
56+
EXPECT_TRUE(new_current_stream->ReadEmpty());
5757

5858
stream.AppendContent("5", 1);
59-
EXPECT_FALSE(new_current_stream->Empty());
59+
// Read buffer is empty here because swap is on Take/Peek
60+
EXPECT_FALSE(new_current_stream->WriteEmpty());
61+
EXPECT_TRUE(new_current_stream->ReadEmpty());
6062

6163
stream.CloseStream(olp::client::ApiError::Cancelled());
6264

@@ -73,11 +75,11 @@ TEST(AsyncJsonStreamTest, NormalFlow) {
7375
EXPECT_TRUE(stream.GetError()->GetErrorCode() ==
7476
olp::client::ErrorCode::Cancelled);
7577

76-
EXPECT_TRUE(new_current_stream->Empty());
78+
EXPECT_TRUE(new_current_stream->ReadEmpty());
7779
stream.AppendContent("17", 2);
78-
EXPECT_TRUE(new_current_stream->Empty());
80+
EXPECT_TRUE(new_current_stream->ReadEmpty());
7981
stream.ResetStream("4", 1);
80-
EXPECT_TRUE(new_current_stream->Empty());
82+
EXPECT_TRUE(new_current_stream->ReadEmpty());
8183
}
8284

8385
} // namespace

olp-cpp-sdk-dataservice-read/tests/PartitionsRepositoryTest.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2019-2024 HERE Europe B.V.
2+
* Copyright (C) 2019-2025 HERE Europe B.V.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -2029,7 +2029,9 @@ TEST_F(PartitionsRepositoryTest, StreamPartitions) {
20292029
[&](const repository::AsyncJsonStream& async_stream) -> std::string {
20302030
std::string result;
20312031
auto stream = async_stream.GetCurrentStream();
2032-
while (!stream->Empty()) {
2032+
// Enforce buffers swap
2033+
OLP_SDK_CORE_UNUSED(stream->Peek());
2034+
while (!stream->ReadEmpty()) {
20332035
result += stream->Take();
20342036
}
20352037
return result;

0 commit comments

Comments
 (0)