Skip to content

Commit 2412e5f

Browse files
willvaleJBenda
andauthored
Optimisation: Replace linear searches with binary searches for more consistent performance. (#138)
In a middle-sized story in a release build, I was seeing good performance at one end (100us or so per getline) vs. bad performance at the other end (300us). This change puts getline under 100us for the entire story. * Define file format with C/C++ structs rather than code. * Describe file sections in header so we don't need to scan the whole thing on load. * Remove vestiges of Endian-swapping and make read_list_flag a free function. * Bump format version. * Fix UTF-8 test (needed to use prefix) * Rename compiler's _containers stream to _instructions since that's what it stores. * Remove iterate_containers, add find_container_for, find_container_id, container_data and container_offset. * Implement find_container_for and find_offset_for with upper_bound. * Store expanded information about containers (container_data) including tree structure. * Rewrite jump using new toolkit - update ip, unwind stack, then generate new stack with search/tree walk. * Move little bit of container entry logic out of globals_impl::visit. NB: This might be too much change? My 2p is that the speed improvements are worthwhile and the changes to the file format get it into a better shape which is more clearly defined and easier to extend. I spotted more avenues for optimisation but trying not to get too distracted by them :) --------- Co-authored-by: Julian Benda <julian.m.benda@gmail.com>
1 parent 94863f7 commit 2412e5f

23 files changed

Lines changed: 820 additions & 690 deletions

.github/workflows/build.yml

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -313,45 +313,6 @@ jobs:
313313
echo or upstream/master depending on your setup
314314
fi
315315
316-
reporting:
317-
name: "Pull Request Report"
318-
# if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'brwarner/inkcpp' }}
319-
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'master' }}
320-
runs-on: ubuntu-latest
321-
needs: [compilation, clang-format]
322-
permissions:
323-
pull-requests: write
324-
steps:
325-
# Download Ink Proof Results
326-
- uses: actions/download-artifact@v4
327-
with:
328-
pattern: result-*
329-
path: "results"
330-
merge-multiple: true
331-
332-
# Create comment text
333-
- name: Create Comment Text File
334-
shell: bash
335-
run: |
336-
echo "### Ink Proof Results" >> comment.txt
337-
echo "" >> comment.txt
338-
echo "These results are obtained by running the [Ink-Proof Testing Suite](https://github.com/chromy/ink-proof) on the compiled binaries in this pull request." >> comment.txt
339-
echo "" >> comment.txt
340-
echo "| System | Results |" >> comment.txt
341-
echo "| --- | --- |" >> comment.txt
342-
FILES="results/*.txt"
343-
for f in $FILES
344-
do
345-
echo "Reading results from $f"
346-
cat "$f" >> comment.txt
347-
done
348-
349-
# Post Comment
350-
- uses: marocchino/sticky-pull-request-comment@v2.9.0
351-
with:
352-
recreate: true
353-
path: comment.txt
354-
355316
pages:
356317
permissions:
357318
contents: write

.github/workflows/pr-report.yml

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
name: PR Report
2+
on:
3+
workflow_run:
4+
workflows: ["build"]
5+
types: [completed]
6+
7+
jobs:
8+
reporting:
9+
name: "Pull Request Report"
10+
# Only run for pull requests targeting master (not push/dispatch runs)
11+
if: >
12+
github.event.workflow_run.event == 'pull_request' &&
13+
github.event.workflow_run.head_branch != 'master'
14+
runs-on: ubuntu-latest
15+
permissions:
16+
pull-requests: write
17+
actions: read
18+
19+
steps:
20+
# Download Ink Proof Results from the triggering workflow run
21+
- uses: actions/download-artifact@v4
22+
with:
23+
pattern: result-*
24+
path: "results"
25+
merge-multiple: true
26+
run-id: ${{ github.event.workflow_run.id }}
27+
github-token: ${{ secrets.GITHUB_TOKEN }}
28+
29+
# Retrieve the PR number from the workflow run
30+
- name: Get PR number
31+
id: get-pr
32+
uses: actions/github-script@v7
33+
with:
34+
script: |
35+
const prs = await github.rest.pulls.list({
36+
owner: context.repo.owner,
37+
repo: context.repo.repo,
38+
state: 'open',
39+
head: `${context.payload.workflow_run.head_repository.owner.login}:${context.payload.workflow_run.head_branch}`
40+
});
41+
if (prs.data.length === 0) {
42+
core.setFailed('No matching open PR found');
43+
return;
44+
}
45+
core.setOutput('pr_number', prs.data[0].number);
46+
47+
# Create comment text
48+
- name: Create Comment Text File
49+
shell: bash
50+
run: |
51+
echo "### Ink Proof Results" >> comment.txt
52+
echo "" >> comment.txt
53+
echo "These results are obtained by running the [Ink-Proof Testing Suite](https://github.com/chromy/ink-proof) on the compiled binaries in this pull request." >> comment.txt
54+
echo "" >> comment.txt
55+
echo "| System | Results |" >> comment.txt
56+
echo "| --- | --- |" >> comment.txt
57+
FILES="results/*.txt"
58+
for f in $FILES
59+
do
60+
echo "Reading results from $f"
61+
cat "$f" >> comment.txt
62+
done
63+
64+
# Post Comment
65+
- uses: marocchino/sticky-pull-request-comment@v2.9.0
66+
with:
67+
recreate: true
68+
number: ${{ steps.get-pr.outputs.pr_number }}
69+
path: comment.txt

inkcpp/container_operations.cpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ void operation<Command::READ_COUNT_VAR, value_type::divert, void>::operator()(
2121
)
2222
{
2323
container_t id;
24-
bool success
25-
= _story.get_container_id(_story.instructions() + vals[0].get<value_type::divert>(), id);
24+
bool success = _story.find_container_id(vals[0].get<value_type::divert>(), id);
2625
inkAssert(success, "failed to find container to read visit count!");
2726
stack.push(value{}.set<value_type::int32>(static_cast<int32_t>(_visit_counts.visits(id))));
2827
}
@@ -32,14 +31,14 @@ void operation<Command::TURNS, value_type::divert, void>::operator()(
3231
)
3332
{
3433
container_t id;
35-
bool success
36-
= _story.get_container_id(_story.instructions() + vals[0].get<value_type::divert>(), id);
34+
bool success = _story.find_container_id(vals[0].get<value_type::divert>(), id);
3735
inkAssert(success, "failed to find container to read turn count!");
3836
stack.push(value{}.set<value_type::int32>(static_cast<int32_t>(_visit_counts.turns(id))));
3937
}
4038

41-
void operation<
42-
Command::CHOICE_COUNT, value_type::none, void>::operator()(basic_eval_stack& stack, value*)
39+
void operation<Command::CHOICE_COUNT, value_type::none, void>::operator()(
40+
basic_eval_stack& stack, value* vals
41+
)
4342
{
4443
stack.push(value{}.set<value_type::int32>(static_cast<int32_t>(_runner.num_choices())));
4544
}

inkcpp/globals_impl.cpp

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ globals_impl::globals_impl(const story_impl* story)
2020
, _visit_counts(visit_count(), visit_count_null_value)
2121
, _owner(story)
2222
, _runners_start(nullptr)
23-
, _lists(story->list_meta(), story->get_header())
23+
, _lists(story->list_meta())
2424
, _globals_initialized(false)
2525
{
2626
_visit_counts.resize(_num_containers);
@@ -50,12 +50,9 @@ void globals_impl::init_static_list_flags()
5050
}
5151
}
5252

53-
void globals_impl::visit(uint32_t container_id, bool entering_at_start)
53+
void globals_impl::visit(uint32_t container_id)
5454
{
55-
if ((! (_owner->container_flag(container_id) & CommandFlag::CONTAINER_MARKER_ONLY_FIRST))
56-
|| entering_at_start) {
57-
_visit_counts.set(container_id, {_visit_counts[container_id].visits + 1, 0});
58-
}
55+
_visit_counts.set(container_id, {_visit_counts[container_id].visits + 1, 0});
5956
}
6057

6158
uint32_t globals_impl::visits(uint32_t container_id) const
@@ -276,7 +273,7 @@ size_t globals_impl::snap(unsigned char* data, const snapper& snapper) const
276273
ptr = snap_write(ptr, _turn_cnt, data != nullptr);
277274
ptr += _visit_counts.snap(data ? ptr : nullptr, snapper);
278275
for (unsigned i = 0; i < _visit_counts.capacity(); ++i) {
279-
ptr = snap_write(ptr, _owner->container_hash(i), data != nullptr);
276+
ptr = snap_write(ptr, _owner->container_data(i)._hash, data != nullptr);
280277
}
281278
ptr += _strings.snap(data ? ptr : nullptr, snapper);
282279
ptr += _lists.snap(data ? ptr : nullptr, snapper);
@@ -307,7 +304,11 @@ const unsigned char* globals_impl::snap_load(const unsigned char* ptr, const loa
307304
hash_t path;
308305
ptr = snap_read(ptr, path);
309306
container_t c_id;
310-
bool found = _owner->get_container_id(_owner->find_offset_for(path), c_id);
307+
ip_t container_ip = _owner->find_offset_for(path);
308+
bool found = container_ip != nullptr
309+
&& _owner->find_container_id(
310+
static_cast<uint32_t>(container_ip - _owner->instructions()), c_id
311+
);
311312
if (! loader.migratable) {
312313
inkAssert(found, "Invalid container id reference.");
313314
inkAssert(c_id == i, "tracked containere are not allowed to move, expect we migrate");
@@ -332,8 +333,8 @@ const unsigned char* globals_impl::snap_load(const unsigned char* ptr, const loa
332333

333334
bool globals_impl::migrate_new_globals(globals_impl& new_globals, const char* list_metadata)
334335
{
335-
bool success = _variables.migrate(new_globals._variables)
336-
&& ((! _lists) || _lists.migrate(list_metadata, _owner->get_header()));
336+
bool success
337+
= _variables.migrate(new_globals._variables) && ((! _lists) || _lists.migrate(list_metadata));
337338
if (! success) {
338339
return false;
339340
}

inkcpp/globals_impl.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,7 @@ class globals_impl final
6565

6666
public:
6767
// Records a visit to a container
68-
/// @param start_cmd iff the visit was initiatet through a MARKER_START_CONTAINER
69-
void visit(uint32_t container_id, bool entering_at_start);
68+
void visit(uint32_t container_id);
7069

7170
// Checks the number of visits to a container
7271
uint32_t visits(uint32_t container_id) const;

inkcpp/header.cpp

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,24 @@
1010
namespace ink::internal
1111
{
1212

13-
header header::parse_header(const char* data)
13+
bool header::verify() const
1414
{
15-
header res;
16-
const char* ptr = data;
17-
res.endien = *reinterpret_cast<const header::endian_types*>(ptr);
18-
ptr += sizeof(header::endian_types);
19-
20-
using v_t = decltype(header::ink_version_number);
21-
using vcpp_t = decltype(header::ink_bin_version_number);
22-
23-
if (res.endien == header::endian_types::same) {
24-
res.ink_version_number = *reinterpret_cast<const v_t*>(ptr);
25-
ptr += sizeof(v_t);
26-
res.ink_bin_version_number = *reinterpret_cast<const vcpp_t*>(ptr);
15+
if (endian() == endian_types::none) {
16+
inkFail("Header magic number was wrong!");
17+
return false;
18+
}
2719

28-
} else if (res.endien == header::endian_types::differ) {
29-
res.ink_version_number = swap_bytes(*reinterpret_cast<const v_t*>(ptr));
30-
ptr += sizeof(v_t);
31-
res.ink_bin_version_number = swap_bytes(*reinterpret_cast<const vcpp_t*>(ptr));
32-
} else {
33-
inkFail("Failed to parse endian encoding! %#04x", res.endien);
20+
if (endian() == endian_types::differ) {
21+
inkFail("Can't load content with different endian-ness!");
22+
return false;
3423
}
3524

36-
if (res.ink_bin_version_number != InkBinVersion) {
25+
if (ink_bin_version_number != InkBinVersion) {
3726
inkFail("InkCpp-version mismatch: file was compiled with different InkCpp-version!");
27+
return false;
3828
}
39-
return res;
29+
30+
return true;
4031
}
32+
4133
} // namespace ink::internal

inkcpp/list_table.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ void list_table::copy_lists(const data_t* src, data_t* dst)
3434
}
3535
}
3636

37-
list_table::list_table(const char* data, const ink::internal::header& header)
37+
list_table::list_table(const char* data)
3838
: _valid{false}
3939
{
4040
if (data == nullptr) {
@@ -43,7 +43,7 @@ list_table::list_table(const char* data, const ink::internal::header& header)
4343
list_flag flag;
4444
const char* ptr = data;
4545
int start = 0;
46-
while ((flag = header.read_list_flag(ptr)) != null_flag) {
46+
while ((flag = read_list_flag(ptr)) != null_flag) {
4747
// start of new list
4848
if (static_cast<int16_t>(_list_end.size()) == flag.list_id) {
4949
start = _list_end.size() == 0 ? 0 : _list_end.back();
@@ -1013,9 +1013,9 @@ float* cost_matrix(const MatchListValues& lh, const MatchListValues& rh, float d
10131013
return matrix;
10141014
}
10151015

1016-
bool list_table::migrate(const char* old_list_metadata, const ink::internal::header& header)
1016+
bool list_table::migrate(const char* old_list_metadata)
10171017
{
1018-
list_table old_ref_table(old_list_metadata, header);
1018+
list_table old_ref_table(old_list_metadata);
10191019
for (const auto& x : _data) {
10201020
old_ref_table._data.push() = x;
10211021
}

inkcpp/list_table.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,9 @@ class list_table : public snapshot_interface
113113
list create_permament();
114114
list& add_inplace(list& lh, list_flag rh);
115115

116-
list_table(const char* data, const ink::internal::header&);
116+
list_table(const char* data);
117117
// binary list metadata of currently loaded list
118-
bool migrate(const char* old_list_metadata, const ink::internal::header& header);
118+
bool migrate(const char* old_list_metadata);
119119

120120
explicit list_table()
121121
: _entrySize{0}

0 commit comments

Comments
 (0)