Skip to content

Commit 14bfad2

Browse files
committed
Sync engine source with the macOS version
After openvanilla/McBopomofo#856 Also remove two unnecessary copy/link of .clang-format. We'll simply let clang-format find the top-level .clang-format at the project root. Also format ByteBlockBackedDictionaryTest.cpp.
1 parent 5704bf3 commit 14bfad2

8 files changed

Lines changed: 316 additions & 205 deletions

src/Engine/ByteBlockBackedDictionaryTest.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,12 @@ TEST(ByteBlockBackedDictionaryTest, Simple2) {
4747
}
4848

4949
TEST(ByteBlockBackedDictionaryTest, EncodingAgnostic1) {
50-
const auto *data = u8"smile 😊";
51-
const char *charData = reinterpret_cast<const char*>(data);
50+
const auto* data = u8"smile 😊";
51+
const char* charData = reinterpret_cast<const char*>(data);
5252
ByteBlockBackedDictionary dict;
5353
ASSERT_TRUE(dict.parse(charData, strlen(charData)));
54-
ASSERT_EQ(dict.getValues("smile").at(0), reinterpret_cast<const char*>(u8"😊"));
54+
ASSERT_EQ(dict.getValues("smile").at(0),
55+
reinterpret_cast<const char*>(u8"😊"));
5556
}
5657

5758
TEST(ByteBlockBackedDictionaryTest, EncodingAgnostic2) {

src/Engine/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,5 +126,15 @@ if (ENABLE_TEST)
126126
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/ParselessLMBenchmark
127127
)
128128
add_dependencies(runParselessLMBenchmark ParselessLMBenchmark)
129+
130+
add_executable(ParselessPhraseDBBenchmark
131+
ParselessPhraseDBBenchmark.cpp)
132+
target_link_libraries(ParselessPhraseDBBenchmark McBopomofoLMLib benchmark::benchmark)
133+
134+
add_custom_target(
135+
runParselessPhraseDBBenchmark
136+
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/ParselessPhraseDBBenchmark
137+
)
138+
add_dependencies(runParselessPhraseDBBenchmark ParselessPhraseDBBenchmark)
129139
endif ()
130140
endif ()

src/Engine/Mandarin/.clang-format

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/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();

src/Engine/ParselessPhraseDB.cpp

Lines changed: 96 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,97 @@
2828
#include <string>
2929
#include <vector>
3030

31+
#ifdef ENABLE_EXPERIMENTAL_SIMD_SUPPORT_NEON
32+
#if defined(__ARM_NEON)
33+
#include <arm_neon.h>
34+
35+
#include <cstdint>
36+
#else
37+
#error ARM NEON support required
38+
#endif
39+
#endif
40+
3141
namespace McBopomofo {
3242

43+
namespace {
44+
45+
#ifdef ENABLE_EXPERIMENTAL_SIMD_SUPPORT_NEON
46+
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+
58+
int LastNonZeroLane16(uint8x16_t value) {
59+
// value must be a comparison mask whose lanes are either 0x00 or 0xff.
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.
63+
alignas(16) static constexpr uint8_t kLaneIndices[16] = {
64+
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
65+
};
66+
const uint8x16_t laneIndices = vld1q_u8(kLaneIndices);
67+
return static_cast<int>(vmaxvq_u8(vandq_u8(value, laneIndices))) - 1;
68+
}
69+
70+
#endif
71+
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+
94+
const char* FindLineStart(const char* begin, const char* position) {
95+
const char* cursor = position;
96+
97+
#ifdef ENABLE_EXPERIMENTAL_SIMD_SUPPORT_NEON
98+
const uint8x16_t linefeeds = vdupq_n_u8(static_cast<uint8_t>('\n'));
99+
while (cursor - begin >= 16) {
100+
const char* blockStart = cursor - 16;
101+
const uint8x16_t block =
102+
vld1q_u8(reinterpret_cast<const uint8_t*>(blockStart));
103+
const int positionInBlock = LastNonZeroLane16(vceqq_u8(block, linefeeds));
104+
if (positionInBlock >= 0) {
105+
return blockStart + positionInBlock + 1;
106+
}
107+
cursor = blockStart;
108+
}
109+
#endif
110+
111+
while (cursor != begin) {
112+
--cursor;
113+
if (*cursor == '\n') {
114+
return cursor + 1;
115+
}
116+
}
117+
return begin;
118+
}
119+
120+
} // namespace
121+
33122
bool ParselessPhraseDB::ValidatePragma(const char* buf, size_t length) {
34123
if (length < SORTED_PRAGMA_HEADER.length()) {
35124
return false;
@@ -80,11 +169,7 @@ std::vector<std::string_view> ParselessPhraseDB::findRows(
80169

81170
while (ptr + key.length() <= end_ &&
82171
memcmp(ptr, key.data(), key.length()) == 0) {
83-
const char* eol = ptr;
84-
85-
while (eol != end_ && *eol != '\n') {
86-
++eol;
87-
}
172+
const char* eol = FindNextCharacter(ptr, end_, '\n');
88173

89174
rows.emplace_back(ptr, eol - ptr);
90175
if (eol == end_) {
@@ -114,20 +199,11 @@ const char* ParselessPhraseDB::findFirstMatchingLine(
114199

115200
while (top < bottom) {
116201
const char* mid = top + ((bottom - top) / 2);
117-
const char* ptr = mid;
118-
119-
if (ptr != begin_) {
120-
--ptr;
121-
}
122-
123-
while (ptr != begin_ && *ptr != '\n') {
124-
--ptr;
125-
}
202+
const char* ptr = FindLineStart(begin_, mid);
126203

127204
const char* prev = nullptr;
128-
if (*ptr == '\n') {
129-
prev = ptr;
130-
++ptr;
205+
if (ptr != begin_) {
206+
prev = ptr - 1;
131207
}
132208

133209
// ptr is now in the "current" line we're interested in.
@@ -153,15 +229,7 @@ const char* ParselessPhraseDB::findFirstMatchingLine(
153229
}
154230

155231
// Move the prev so that it reaches the previous line.
156-
if (prev != begin_) {
157-
--prev;
158-
}
159-
while (prev != begin_ && *prev != '\n') {
160-
--prev;
161-
}
162-
if (*prev == '\n') {
163-
++prev;
164-
}
232+
prev = FindLineStart(begin_, prev);
165233

166234
int prev_cmp = memcmp(prev, key.data(), key.length());
167235

@@ -187,20 +255,15 @@ std::vector<std::string> ParselessPhraseDB::reverseFindRows(
187255
const char* ptr = recordBegin;
188256

189257
// skip over the key to find the field separator
190-
while (ptr < end_ && *ptr != ' ') {
191-
++ptr;
192-
}
258+
ptr = FindNextCharacter(ptr, end_, ' ');
193259
// skip over the field separator. there should be just one, but loop just in
194260
// case.
195261
while (ptr < end_ && *ptr == ' ') {
196262
++ptr;
197263
}
198264

199265
// now walk to the end of this record
200-
const char* recordEnd = ptr;
201-
while (recordEnd < end_ && *recordEnd != '\n') {
202-
++recordEnd;
203-
}
266+
const char* recordEnd = FindNextCharacter(ptr, end_, '\n');
204267

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

0 commit comments

Comments
 (0)