Skip to content

Commit f27a268

Browse files
authored
Merge pull request openvanilla#856 from ChiahongHong/neon
Add ARM NEON support for forward scans in ParselessPhraseDB
2 parents 5e8f8d6 + c95872f commit f27a268

4 files changed

Lines changed: 125 additions & 14 deletions

File tree

Source/Engine/ParselessLMBenchmark.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@
2525

2626
#include <cassert>
2727
#include <filesystem>
28+
#include <fstream>
29+
#include <string>
30+
#include <vector>
2831

2932
#include "ParselessLM.h"
3033

@@ -35,6 +38,23 @@ using ParselessLM = McBopomofo::ParselessLM;
3538
static const char* kDataPath = "data.txt";
3639
static const char* kUnigramSearchKey = "ㄕˋ-ㄕˊ";
3740

41+
std::vector<std::string> LoadRealKeys() {
42+
std::ifstream input(kDataPath);
43+
assert(input.is_open());
44+
45+
std::vector<std::string> keys;
46+
std::string line;
47+
std::getline(input, line);
48+
while (std::getline(input, line)) {
49+
const size_t separator = line.find(' ');
50+
if (separator != std::string::npos) {
51+
keys.emplace_back(line.substr(0, separator));
52+
}
53+
}
54+
assert(!keys.empty());
55+
return keys;
56+
}
57+
3858
static void BM_ParselessLMOpenClose(benchmark::State& state) {
3959
assert(std::filesystem::exists(kDataPath));
4060
for (auto _ : state) {
@@ -56,6 +76,49 @@ static void BM_ParselessLMFindUnigrams(benchmark::State& state) {
5676
}
5777
BENCHMARK(BM_ParselessLMFindUnigrams);
5878

79+
static void BM_ParselessLMHasUnigramsRealKeys(benchmark::State& state) {
80+
assert(std::filesystem::exists(kDataPath));
81+
ParselessLM lm;
82+
lm.open(kDataPath);
83+
const std::vector<std::string> keys = LoadRealKeys();
84+
auto key = keys.begin();
85+
for (auto _ : state) {
86+
benchmark::DoNotOptimize(lm.hasUnigrams(*key));
87+
if (++key == keys.end()) {
88+
key = keys.begin();
89+
}
90+
}
91+
lm.close();
92+
}
93+
BENCHMARK(BM_ParselessLMHasUnigramsRealKeys);
94+
95+
static void BM_ParselessLMFindUnigramsRealKeys(benchmark::State& state) {
96+
assert(std::filesystem::exists(kDataPath));
97+
ParselessLM lm;
98+
lm.open(kDataPath);
99+
const std::vector<std::string> keys = LoadRealKeys();
100+
auto key = keys.begin();
101+
for (auto _ : state) {
102+
benchmark::DoNotOptimize(lm.getUnigrams(*key));
103+
if (++key == keys.end()) {
104+
key = keys.begin();
105+
}
106+
}
107+
lm.close();
108+
}
109+
BENCHMARK(BM_ParselessLMFindUnigramsRealKeys);
110+
111+
static void BM_ParselessLMGetReadingsMissingValue(benchmark::State& state) {
112+
assert(std::filesystem::exists(kDataPath));
113+
ParselessLM lm;
114+
lm.open(kDataPath);
115+
for (auto _ : state) {
116+
benchmark::DoNotOptimize(lm.getReadings("missing"));
117+
}
118+
lm.close();
119+
}
120+
BENCHMARK(BM_ParselessLMGetReadingsMissingValue);
121+
59122
}; // namespace
60123

61124
BENCHMARK_MAIN();

Source/Engine/ParselessPhraseDB.cpp

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,22 @@ namespace {
4444

4545
#ifdef ENABLE_EXPERIMENTAL_SIMD_SUPPORT_NEON
4646

47+
int FirstNonZeroLane16(uint8x16_t value) {
48+
// value must be a comparison mask whose lanes are either 0x00 or 0xff.
49+
// Taking the maximum across the reversed masked lane indices locates the
50+
// first matching lane and avoids a scalar loop.
51+
alignas(16) static constexpr uint8_t kReverseLaneIndices[16] = {
52+
16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1,
53+
};
54+
const uint8x16_t laneIndices = vld1q_u8(kReverseLaneIndices);
55+
return 16 - static_cast<int>(vmaxvq_u8(vandq_u8(value, laneIndices)));
56+
}
57+
4758
int LastNonZeroLane16(uint8x16_t value) {
4859
// value must be a comparison mask whose lanes are either 0x00 or 0xff.
49-
// Reducing indexed lanes avoids a scalar loop that compilers may expand into
50-
// up to 16 umov and cbnz branch pairs.
60+
// Taking the maximum across the masked lane indices locates the last matching
61+
// lane and avoids a scalar loop that compilers may expand into up to 16 umov
62+
// and cbnz branch pairs.
5163
alignas(16) static constexpr uint8_t kLaneIndices[16] = {
5264
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
5365
};
@@ -57,6 +69,28 @@ int LastNonZeroLane16(uint8x16_t value) {
5769

5870
#endif
5971

72+
const char* FindNextCharacter(const char* position, const char* end,
73+
char character) {
74+
const char* cursor = position;
75+
76+
#ifdef ENABLE_EXPERIMENTAL_SIMD_SUPPORT_NEON
77+
const uint8x16_t characters = vdupq_n_u8(static_cast<uint8_t>(character));
78+
while (end - cursor >= 16) {
79+
const uint8x16_t block = vld1q_u8(reinterpret_cast<const uint8_t*>(cursor));
80+
const int positionInBlock = FirstNonZeroLane16(vceqq_u8(block, characters));
81+
if (positionInBlock != 16) {
82+
return cursor + positionInBlock;
83+
}
84+
cursor += 16;
85+
}
86+
#endif
87+
88+
while (cursor != end && *cursor != character) {
89+
++cursor;
90+
}
91+
return cursor;
92+
}
93+
6094
const char* FindLineStart(const char* begin, const char* position) {
6195
const char* cursor = position;
6296

@@ -135,11 +169,7 @@ std::vector<std::string_view> ParselessPhraseDB::findRows(
135169

136170
while (ptr + key.length() <= end_ &&
137171
memcmp(ptr, key.data(), key.length()) == 0) {
138-
const char* eol = ptr;
139-
140-
while (eol != end_ && *eol != '\n') {
141-
++eol;
142-
}
172+
const char* eol = FindNextCharacter(ptr, end_, '\n');
143173

144174
rows.emplace_back(ptr, eol - ptr);
145175
if (eol == end_) {
@@ -225,20 +255,15 @@ std::vector<std::string> ParselessPhraseDB::reverseFindRows(
225255
const char* ptr = recordBegin;
226256

227257
// skip over the key to find the field separator
228-
while (ptr < end_ && *ptr != ' ') {
229-
++ptr;
230-
}
258+
ptr = FindNextCharacter(ptr, end_, ' ');
231259
// skip over the field separator. there should be just one, but loop just in
232260
// case.
233261
while (ptr < end_ && *ptr == ' ') {
234262
++ptr;
235263
}
236264

237265
// now walk to the end of this record
238-
const char* recordEnd = ptr;
239-
while (recordEnd < end_ && *recordEnd != '\n') {
240-
++recordEnd;
241-
}
266+
const char* recordEnd = FindNextCharacter(ptr, end_, '\n');
242267

243268
if (ptr + value.length() < end_ &&
244269
memcmp(ptr, value.data(), value.length()) == 0) {

Source/Engine/ParselessPhraseDBBenchmark.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,16 @@ void BM_ParselessPhraseDBFindFirstMatchingLine(benchmark::State& state) {
100100
}
101101
BENCHMARK(BM_ParselessPhraseDBFindFirstMatchingLine);
102102

103+
void BM_ParselessPhraseDBReverseFindRows(benchmark::State& state) {
104+
const BenchmarkDataset dataset;
105+
const auto& database = dataset.database();
106+
107+
for (auto _ : state) {
108+
benchmark::DoNotOptimize(database.reverseFindRows("missing"));
109+
}
110+
}
111+
BENCHMARK(BM_ParselessPhraseDBReverseFindRows);
112+
103113
} // namespace
104114

105115
BENCHMARK_MAIN();

Source/Engine/ParselessPhraseDBTest.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,4 +242,17 @@ TEST(ParselessPhraseDBTest, LookUpByValue) {
242242
ASSERT_TRUE(rows.empty());
243243
}
244244

245+
TEST(ParselessPhraseDBTest, LookUpByValueWithLongRows) {
246+
std::string data = "a " + std::string(127, 'a') + "\n";
247+
data += "b target " + std::string(255, 'b') + "\n";
248+
data += "c " + std::string(511, 'c');
249+
ParselessPhraseDB db(data.c_str(), data.length());
250+
251+
EXPECT_EQ(db.reverseFindRows("target"),
252+
(std::vector<std::string>{
253+
"b target " + std::string(255, 'b'),
254+
}));
255+
EXPECT_TRUE(db.reverseFindRows("missing").empty());
256+
}
257+
245258
} // namespace McBopomofo

0 commit comments

Comments
 (0)